From 9c5296a65be0e05b89d9022d240bc8e108d77514 Mon Sep 17 00:00:00 2001 From: Tony Kuker Date: Sat, 22 Apr 2023 19:46:26 -0500 Subject: [PATCH 01/87] Update version for next development version --- cpp/shared/piscsi_version.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/shared/piscsi_version.cpp b/cpp/shared/piscsi_version.cpp index c24f5958..63dfe058 100644 --- a/cpp/shared/piscsi_version.cpp +++ b/cpp/shared/piscsi_version.cpp @@ -14,8 +14,8 @@ // The following should be updated for each release const int piscsi_major_version = 23; // Last two digits of year -const int piscsi_minor_version = 4; // Month -const int piscsi_patch_version = 1; // Patch number - increment for each update +const int piscsi_minor_version = 5; // Month +const int piscsi_patch_version = -1; // Patch number - increment for each update using namespace std; From cfcfbc43017ef6b96422be4942fd58441b81a7f3 Mon Sep 17 00:00:00 2001 From: GoodSpeed001 <103572047+GoodSpeed001@users.noreply.github.com> Date: Sat, 29 Apr 2023 23:07:17 +0100 Subject: [PATCH 02/87] Update requirements.txt (#1156) Remove bogus pkg-resources==0.0.0 entry --- python/oled/requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/python/oled/requirements.txt b/python/oled/requirements.txt index 139b09f5..3f97cda2 100644 --- a/python/oled/requirements.txt +++ b/python/oled/requirements.txt @@ -1,4 +1,3 @@ -pkg-resources==0.0.0 adafruit-circuitpython-ssd1306==2.12.11 Pillow==9.3.0 protobuf==3.20.2 From 09d0598c2f47088c8b7cfb85a34b5366fab64d7d Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Mon, 1 May 2023 19:21:17 -0700 Subject: [PATCH 03/87] Remove use of deprecated before_first_request decorator (#1162) --- python/web/src/web.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/python/web/src/web.py b/python/web/src/web.py index 5ac8ea22..270e3dfc 100644 --- a/python/web/src/web.py +++ b/python/web/src/web.py @@ -1369,15 +1369,16 @@ def healthcheck(): return "", 200 -@APP.before_first_request +@APP.before_request def detect_locale(): """ Get the detected locale to use for UI string translations. This requires the Flask app to have started first. """ - session["language"] = get_locale() - piscsi_cmd.locale = session["language"] - file_cmd.locale = session["language"] + if "language" not in session.keys(): + session["language"] = get_locale() + piscsi_cmd.locale = session["language"] + file_cmd.locale = session["language"] @APP.before_request From 1c52dba979de66b9175b653b375d4c394bd9957c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 May 2023 19:33:53 -0700 Subject: [PATCH 04/87] Bump flask from 2.2.2 to 2.3.2 in /python/web (#1160) --- python/web/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/web/requirements.txt b/python/web/requirements.txt index 83045903..e8bcea51 100644 --- a/python/web/requirements.txt +++ b/python/web/requirements.txt @@ -1,5 +1,5 @@ bjoern==3.2.2 -Flask==2.2.2 +Flask==2.3.2 Jinja2==3.1.2 protobuf==3.20.2 requests==2.28.1 From b28da7001d2e2b345ae5a89d4716ec9b35824c21 Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Tue, 16 May 2023 12:19:40 -0700 Subject: [PATCH 05/87] Always assign locale in detect_locale() (#1163) * Always assign locale in detect_locale() * Bump version of Debian used in CI builds from from buster to bullseye * Update code comment --------- Co-authored-by: nucleogenic --- docker/docker-compose.ci.yml | 2 +- python/web/src/web.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docker/docker-compose.ci.yml b/docker/docker-compose.ci.yml index 40bd95e6..2687eaa5 100644 --- a/docker/docker-compose.ci.yml +++ b/docker/docker-compose.ci.yml @@ -16,7 +16,7 @@ services: context: .. dockerfile: docker/web/Dockerfile args: - - OS_VERSION=buster + - OS_VERSION=bullseye volumes: - ./volumes/images:/home/pi/images:delegated init: true diff --git a/python/web/src/web.py b/python/web/src/web.py index 270e3dfc..b4c7d661 100644 --- a/python/web/src/web.py +++ b/python/web/src/web.py @@ -1373,12 +1373,13 @@ def healthcheck(): def detect_locale(): """ Get the detected locale to use for UI string translations. - This requires the Flask app to have started first. + Assign the language string to objects to be used for requests. """ if "language" not in session.keys(): session["language"] = get_locale() - piscsi_cmd.locale = session["language"] - file_cmd.locale = session["language"] + + piscsi_cmd.locale = session["language"] + file_cmd.locale = session["language"] @APP.before_request From 4580dd222e45565c0e585c6e27f5720397af856d Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Tue, 16 May 2023 21:09:12 -0700 Subject: [PATCH 06/87] PiSCSI name in OLED splash screen (#1169) --- python/oled/resources/splash_start_32.bmp | Bin 574 -> 658 bytes python/oled/resources/splash_start_64.bmp | Bin 1086 -> 1170 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/python/oled/resources/splash_start_32.bmp b/python/oled/resources/splash_start_32.bmp index 5c7549ed3caea5432f1aec041655e862106ec5bd..e3493eca5f79599492a625c8c78d65b5a9dbaad6 100644 GIT binary patch literal 658 zcmZ?roy5cd29tnf4G=c~u>ue?0wEXx<%PH*7^D=U{67N&1c8+}xd#<=zgF<$eNibLPwro-{T#pCdDl_nbL!MsnuN1`wa;fCP~L2gLsY;>#Sc0P@d(_%nFY*pFEp z0O|+wf%<`bwzPr+4}g52`SKq=$l0)^B^)>aOE1K)&R}IR+Nyn+<{H u*B?Gm0W@F2z`%U70W;VK1`Lu7l7>b|e0c^wp#4<#9~)Yj{v{MYgyaFr54c1C diff --git a/python/oled/resources/splash_start_64.bmp b/python/oled/resources/splash_start_64.bmp index 4448f28d249507d59f65b35b713bab70e5bee6e3..3316e0d7275504cadc88638d0d2bd973e043705a 100644 GIT binary patch literal 1170 zcmZ?roy5Wb29tnf4G=c~u>%k@0wEXx<%PH*mcEs7K~tVK;z79Zs-Qo8SMT;yAzLj1O!!Y-|=4 z1;=|%962*{=FASBG&VM$BQuWooH=kta^}ni5TECO1d#s+#Qy=}%N(!(^3QId?H`hk45w1NW;S}!KnMdu+zbpL%mfqw;r~DYqE?}SP&9xhy$Ve_bfnAg z2L&V&s7GRBV>SQ-9SN{EAaQ_9iuwBwK9;dbOFkg+L9)M3PR4w*vanHFTAG6449S_3 z4b{vy8#5aK`3xX_vjH2B{~5?ne*ogQ8}I@7#vu6w!x{4YeFi}N#>OE1K)&R}IR+Ny zn+<{H*B?Gm0W@F2z`%U70W;VK1`Lu7l7>b|e0c^wp#4<#9~)Yj{vGM*k5c~w0R2?A AbpQYW From fa475d8b12f6135eb851f90c9b78bf077c6d3ae4 Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Sun, 21 May 2023 15:27:50 -0700 Subject: [PATCH 07/87] Proper handling of custom image dirs, #1170 (#1171) --- python/common/src/piscsi/sys_cmds.py | 8 ++++---- python/web/src/templates/index.html | 4 ++-- python/web/src/web.py | 7 +++++-- python/web/src/web_utils.py | 15 +++++++++------ 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/python/common/src/piscsi/sys_cmds.py b/python/common/src/piscsi/sys_cmds.py index 0da185d8..c21cc8f4 100644 --- a/python/common/src/piscsi/sys_cmds.py +++ b/python/common/src/piscsi/sys_cmds.py @@ -102,12 +102,12 @@ class SysCmds: return False @staticmethod - def disk_space(): + def disk_space(path): """ - Returns a (dict) with (int) total (int) used (int) free - This is the disk space information of the volume where this app is running + Takes (str) path with the path to the dir / mount point to inspect + Returns a (dict) with (int) total (int) used (int) free disk space """ - total, used, free = disk_usage(__file__) + total, used, free = disk_usage(path) return {"total": total, "used": used, "free": free} @staticmethod diff --git a/python/web/src/templates/index.html b/python/web/src/templates/index.html index b95165a1..6e2c8b39 100644 --- a/python/web/src/templates/index.html +++ b/python/web/src/templates/index.html @@ -218,7 +218,7 @@
{% for subdir, group in formatted_image_files.items() %} -
+
{{ subdir }} @@ -371,7 +371,7 @@ {% endfor %}
{% endif %} -

{{ _("%(disk_space)s MiB disk space remaining on the system", disk_space=env["free_disk_space"]) }}

+

{{ _("%(disk_space)s MiB disk space remaining for images", disk_space=env["free_disk_space"]) }}


diff --git a/python/web/src/web.py b/python/web/src/web.py index b4c7d661..40eb6c14 100644 --- a/python/web/src/web.py +++ b/python/web/src/web.py @@ -96,10 +96,11 @@ def get_env_info(): "ip_addr": ip_addr, "host": host, "system_name": sys_cmd.get_pretty_host(), - "free_disk_space": int(sys_cmd.disk_space()["free"] / 1024 / 1024), + "free_disk_space": int(sys_cmd.disk_space(server_info["image_dir"])["free"] / 1024 / 1024), "locale": get_locale(), "version": server_info["version"], "image_dir": server_info["image_dir"], + "image_root_dir": Path(server_info["image_dir"]).name, "netatalk_configured": sys_cmd.running_proc("afpd"), "macproxy_configured": sys_cmd.running_proc("macproxy"), "cd_suffixes": tuple(server_info["sccd"]), @@ -217,7 +218,9 @@ def index(): image_files = file_cmd.list_images() config_files = file_cmd.list_config_files() ip_addr, host = sys_cmd.get_ip_and_host() - formatted_image_files = format_image_list(image_files["files"], device_types) + formatted_image_files = format_image_list( + image_files["files"], Path(server_info["image_dir"]).name, device_types + ) attached_images = [] units = 0 diff --git a/python/web/src/web_utils.py b/python/web/src/web_utils.py index d085510f..8678dc4e 100644 --- a/python/web/src/web_utils.py +++ b/python/web/src/web_utils.py @@ -160,9 +160,12 @@ def get_image_description(file_suffix): return file_suffix -def format_image_list(image_files, device_types=None): +def format_image_list(image_files, rootdir, device_types=None): """ - Takes a (list) of (dict) image_files and optional (list) device_types + Takes: + - (list) of (dict) image_files + - (str) rootdir, the name of the images root dir + - optional (list) device_types Returns a formatted (dict) with groups of image_files per subdir key """ @@ -174,16 +177,16 @@ def format_image_list(image_files, device_types=None): subdir_path = findall("^.*/", image["name"]) if subdir_path: subdir = subdir_path[0] - if f"images/{subdir}" in subdir_image_files.keys(): - subdir_image_files[f"images/{subdir}"].append(image) + if f"{rootdir}/{subdir}" in subdir_image_files.keys(): + subdir_image_files[f"{rootdir}/{subdir}"].append(image) else: - subdir_image_files[f"images/{subdir}"] = [image] + subdir_image_files[f"{rootdir}/{subdir}"] = [image] else: root_image_files.append(image) formatted_image_files = dict(sorted(subdir_image_files.items())) if root_image_files: - formatted_image_files["images/"] = root_image_files + formatted_image_files[f"{rootdir}/"] = root_image_files return formatted_image_files From 1ce6fd1d5514ea2a89962948726ba0e4c8b54c11 Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Sun, 21 May 2023 15:32:19 -0700 Subject: [PATCH 08/87] Improve invalid path error handling, and escape single quotes in JS (#1174) --- python/web/src/templates/index.html | 6 +++--- python/web/src/web_utils.py | 16 +++++++++++----- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/python/web/src/templates/index.html b/python/web/src/templates/index.html index 6e2c8b39..fb3a5b32 100644 --- a/python/web/src/templates/index.html +++ b/python/web/src/templates/index.html @@ -341,17 +341,17 @@ {% endif %} -
+
-
+
-
+
diff --git a/python/web/src/web_utils.py b/python/web/src/web_utils.py index 8678dc4e..862a3caf 100644 --- a/python/web/src/web_utils.py +++ b/python/web/src/web_utils.py @@ -300,11 +300,17 @@ def is_safe_path(file_name): Returns True if the path is safe Returns False if the path is either absolute, or tries to traverse the file system """ - if file_name.is_absolute() or ".." in str(file_name) or str(file_name)[0] == "~": - return { - "status": False, - "msg": _("No permission to use path '%(file_name)s'", file_name=file_name), - } + error_message = "" + if file_name.is_absolute(): + error_message = _("Path must not be absolute") + elif "../" in str(file_name): + error_message = _("Path must not traverse the file system") + elif str(file_name)[0] == "~": + error_message = _("Path must not start in the home directory") + + if error_message: + logging.error("Not an allowed path: %s", str(file_name)) + return {"status": False, "msg": error_message} return {"status": True, "msg": ""} From 7ee45779e10a2a77e183e664dde5092e0df07f92 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 May 2023 22:15:56 -0700 Subject: [PATCH 09/87] Bump requests from 2.26.0 to 2.31.0 in /python/common (#1176) --- python/common/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/common/requirements.txt b/python/common/requirements.txt index 3a69e2ed..45ccd776 100644 --- a/python/common/requirements.txt +++ b/python/common/requirements.txt @@ -1,2 +1,2 @@ protobuf==3.19.5 -requests==2.26.0 +requests==2.31.0 From c08667a81d8b62add0127ca48d54bd58ff424329 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 May 2023 22:16:36 -0700 Subject: [PATCH 10/87] Bump requests from 2.28.1 to 2.31.0 in /python/web (#1177) --- python/web/requirements-dev.txt | 2 +- python/web/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/web/requirements-dev.txt b/python/web/requirements-dev.txt index 06ff7a82..fd2bb2a1 100644 --- a/python/web/requirements-dev.txt +++ b/python/web/requirements-dev.txt @@ -3,4 +3,4 @@ pytest-httpserver==1.0.6 black==22.8.0 flake8==5.0.4 watchdog==2.1.9 -requests==2.28.1 +requests==2.31.0 diff --git a/python/web/requirements.txt b/python/web/requirements.txt index e8bcea51..d5e01521 100644 --- a/python/web/requirements.txt +++ b/python/web/requirements.txt @@ -2,7 +2,7 @@ bjoern==3.2.2 Flask==2.3.2 Jinja2==3.1.2 protobuf==3.20.2 -requests==2.28.1 +requests==2.31.0 simplepam==0.1.5 flask_babel==2.0.0 ua-parser==0.16.1 \ No newline at end of file From ee658c359d5b012f507cfe7c5b65f94b59bc11ea Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Sat, 24 Jun 2023 13:20:15 -0700 Subject: [PATCH 11/87] Clarify docs and error strings for scsimon (#1175) * Better usage hints in scsimon man page * In error message, clarify that piscsi needs to be shut down --- cpp/hal/gpiobus_raspberry.cpp | 4 ++-- doc/scsimon.1 | 4 +++- doc/scsimon_man_page.txt | 22 ++++++++++++++-------- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/cpp/hal/gpiobus_raspberry.cpp b/cpp/hal/gpiobus_raspberry.cpp index c3dfe3ca..fb3ba249 100644 --- a/cpp/hal/gpiobus_raspberry.cpp +++ b/cpp/hal/gpiobus_raspberry.cpp @@ -183,7 +183,7 @@ bool GPIOBUS_Raspberry::Init(mode_e mode) // GPIO chip open fd = open("/dev/gpiochip0", 0); if (fd == -1) { - LOGERROR("Unable to open /dev/gpiochip0. Is PiSCSI or RaSCSI already running?") + LOGERROR("Unable to open /dev/gpiochip0. If PiSCSI is running, please shut it down first.") return false; } @@ -199,7 +199,7 @@ bool GPIOBUS_Raspberry::Init(mode_e mode) // Get event request if (ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, &selevreq) == -1) { - LOGERROR("Unable to register event request. Is PiSCSI or RaSCSI already running?") + LOGERROR("Unable to register event request. If PiSCSI is running, please shut it down first.") close(fd); return false; } diff --git a/doc/scsimon.1 b/doc/scsimon.1 index 48a76230..7b971803 100644 --- a/doc/scsimon.1 +++ b/doc/scsimon.1 @@ -18,9 +18,11 @@ To quit scsimon, press Control + C. None .SH EXAMPLES -Launch scsimon to capture all SCSI traffic available to the PiSCSI hardware: +Make sure you've stopped the piscsi service. Then launch scsimon to capture all SCSI traffic available to the PiSCSI hardware: scsimon +If you're trying to capture a specific scenario, you'll want to wait to start scsimon until immediately before the scenario. + .SH SEE ALSO scsictl(1), piscsi(1), scsidump(1) diff --git a/doc/scsimon_man_page.txt b/doc/scsimon_man_page.txt index 97b7e1bf..f708fbfa 100644 --- a/doc/scsimon_man_page.txt +++ b/doc/scsimon_man_page.txt @@ -2,20 +2,22 @@ !! ------ The native file is scsimon.1. Re-run 'make docs' after updating -scsimon(1) General Commands Manual scsimon(1) +scsimon(1) General Commands Manual scsimon(1) NAME - scsimon - Acts as a data capture tool for all traffic on the SCSI bus. Data is stored in a Value Change Dump (VCD) file. + scsimon - Acts as a data capture tool for all traffic on the SCSI bus. Data is stored in a Value Change Dump + (VCD) file. SYNOPSIS scsimon DESCRIPTION - scsimon monitors all of the traffic on the SCSI bus, using a PiSCSI device. The data is cached in memory while the tool is - running. A circular buffer is used so that only the most recent 1,000,000 transactions are stored. The tool will continue - to run until the user presses CTRL-C, or the process receives a SIGINT signal. + scsimon monitors all of the traffic on the SCSI bus, using a PiSCSI device. The data is cached in memory while + the tool is running. A circular buffer is used so that only the most recent 1,000,000 transactions are stored. + The tool will continue to run until the user presses CTRL-C, or the process receives a SIGINT signal. - The logged data is stored in a file called "log.vcd" in the current working directory from where scsimon was launched. + The logged data is stored in a file called "log.vcd" in the current working directory from where scsimon was + launched. Currently, scsimon doesn't accept any arguments. @@ -25,12 +27,16 @@ OPTIONS None EXAMPLES - Launch scsimon to capture all SCSI traffic available to the PiSCSI hardware: + Make sure you've stopped the piscsi service. Then launch scsimon to capture all SCSI traffic available to the + PiSCSI hardware: scsimon + If you're trying to capture a specific scenario, you'll want to wait to start scsimon until immediately before + the scenario. + SEE ALSO scsictl(1), piscsi(1), scsidump(1) Full documentation is available at: - scsimon(1) + scsimon(1) From c9209c300468c2702c46b75822bea998efc5e960 Mon Sep 17 00:00:00 2001 From: Nemanja Lukic Date: Wed, 5 Jul 2023 06:42:24 +0200 Subject: [PATCH 12/87] Spanish translation --- .../translations/es/LC_MESSAGES/messages.po | 149 +++++++++--------- 1 file changed, 78 insertions(+), 71 deletions(-) diff --git a/python/web/src/translations/es/LC_MESSAGES/messages.po b/python/web/src/translations/es/LC_MESSAGES/messages.po index 1322689d..1cc35bce 100644 --- a/python/web/src/translations/es/LC_MESSAGES/messages.po +++ b/python/web/src/translations/es/LC_MESSAGES/messages.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: PiSCSI\n" "Report-Msgid-Bugs-To: https://github.com/PiSCSI/piscsi/issues\n" -"POT-Creation-Date: 2023-04-09 10:06+0200\n" -"PO-Revision-Date: 2023-04-09 13:16+0200\n" +"POT-Creation-Date: 2023-07-05 06:25+0200\n" +"PO-Revision-Date: 2023-07-05 06:36+0200\n" "Last-Translator: n4al \n" "Language-Team: es N/A\n" "Language: es\n" @@ -48,7 +48,7 @@ msgstr "No se puede mover el archivo a: %(target_path)s" #: src/return_code_mapper.py:24 #, python-format msgid "Created CD-ROM ISO image with arguments \"%(value)s\"" -msgstr "Creada la imagen ISO del CD-ROM con argumentos “%(value)s”" +msgstr "Creada la imagen ISO del CD-ROM con argumentos \"%(value)s\"" #: src/return_code_mapper.py:26 #, python-format @@ -146,85 +146,85 @@ msgstr "" "La interfaz web de PiSCSI no ha obtenido una respuesta válida de PiSCSI. Por " "favor, vuelva a intentarlo. Si el problema persiste, informe de un error." -#: src/web.py:252 +#: src/web.py:255 msgid "PiSCSI Control Page" msgstr "Página de control PiSCSI" -#: src/web.py:304 +#: src/web.py:307 msgid "PiSCSI Create Drive" msgstr "PiSCSI Crear unidad" -#: src/web.py:320 +#: src/web.py:323 msgid "PiSCSI File Upload" msgstr "Carga de archivos PiSCSI" -#: src/web.py:346 +#: src/web.py:349 #, python-format msgid "You must log in with valid credentials for a user in the '%(group)s' group" msgstr "" "Debe conectarse con credenciales válidas para un usuario del grupo ‘%(group)s’" -#: src/web.py:399 src/web.py:444 +#: src/web.py:402 src/web.py:447 #, python-format msgid "No properties data for drive %(drive_name)s" msgstr "No hay datos de propiedades para la unidad %(drive_name)s" -#: src/web.py:421 +#: src/web.py:424 #, python-format msgid "Image file with properties created: %(file_name)s" msgstr "Archivo de imagen con propiedades creado: %(file_name)s" -#: src/web.py:522 +#: src/web.py:525 msgid "PiSCSI Image Info" msgstr "Información de imagen PiSCSI" -#: src/web.py:529 +#: src/web.py:532 #, python-format msgid "An error occurred when getting disk info: %(error)s" msgstr "Se ha producido un error al obtener la información del disco: %(error)s" -#: src/web.py:543 +#: src/web.py:546 #, python-format msgid "%(app)s is not a recognized PiSCSI app" msgstr "%(app)s no es una aplicación PiSCSI reconocida" -#: src/web.py:570 +#: src/web.py:573 msgid "PiSCSI Manual" msgstr "PiSCSI Manual" -#: src/web.py:577 +#: src/web.py:580 #, python-format msgid "An error occurred when accessing manual page: %(error)s" msgstr "Se ha producido un error al acceder a la página del manual: %(error)s" -#: src/web.py:593 +#: src/web.py:596 msgid "PiSCSI System Logs" msgstr "Registros del sistema PiSCSI" -#: src/web.py:601 +#: src/web.py:604 #, python-format msgid "An error occurred when fetching logs: %(error)s" msgstr "Se ha producido un error al obtener los registros: %(error)s" -#: src/web.py:615 +#: src/web.py:618 #, python-format msgid "Log level set to %(value)s" msgstr "Nivel de registro fijado en %(value)s" -#: src/web.py:632 src/web.py:695 +#: src/web.py:635 src/web.py:698 msgid "No SCSI ID specified" msgstr "No se ha especificado el SCSI ID" -#: src/web.py:673 +#: src/web.py:676 #, python-format msgid "Attached %(device_type)s to SCSI ID %(id_number)s LUN %(unit_number)s" msgstr "Conectado %(device_type)s a SCSI ID %(id_number)s LUN %(unit_number)s" -#: src/web.py:697 +#: src/web.py:700 msgid "No image file to insert" msgstr "No hay archivo de imagen para insertar" -#: src/web.py:737 +#: src/web.py:740 #, python-format msgid "" "Attached %(file_name)s as %(device_type)s to SCSI ID %(id_number)s LUN " @@ -233,132 +233,132 @@ msgstr "" "Conectado %(file_name)s como %(device_type)s a SCSI ID %(id_number)s LUN " "%(unit_number)s" -#: src/web.py:754 +#: src/web.py:757 msgid "Detached all SCSI devices" msgstr "Se han desconectado todos los dispositivos SCSI" -#: src/web.py:770 +#: src/web.py:773 #, python-format msgid "Detached SCSI ID %(id_number)s LUN %(unit_number)s" msgstr "SCSI ID desconectado %(id_number)s LUN %(unit_number)s" -#: src/web.py:792 +#: src/web.py:795 #, python-format msgid "Ejected SCSI ID %(id_number)s LUN %(unit_number)s" msgstr "Expulsado SCSI ID %(id_number)s LUN %(unit_number)s" -#: src/web.py:811 +#: src/web.py:814 msgid "PiSCSI Device Info" msgstr "Información del dispositivo PiSCSI" -#: src/web.py:815 +#: src/web.py:818 msgid "No devices attached" msgstr "No hay dispositivos conectados" -#: src/web.py:831 +#: src/web.py:834 #, python-format msgid "Reserved SCSI ID %(id_number)s" msgstr "SCSI ID reservado %(id_number)s" -#: src/web.py:849 +#: src/web.py:852 #, python-format msgid "Released the reservation for SCSI ID %(id_number)s" msgstr "Liberada la reserva para SCSI ID %(id_number)s" -#: src/web.py:868 +#: src/web.py:871 #, python-format msgid "System name changed to '%(name)s'." msgstr "El nombre del sistema ha cambiado a ‘%(name)s’." -#: src/web.py:869 +#: src/web.py:872 msgid "System name reset to default." msgstr "Nombre del sistema restablecido por defecto." -#: src/web.py:871 +#: src/web.py:874 msgid "Failed to change system name." msgstr "Error al cambiar el nombre del sistema." -#: src/web.py:925 +#: src/web.py:928 #, python-format msgid "%(iso_type)s is not a valid CD-ROM format." msgstr "%(iso_type)s no es un formato de CD-ROM válido." -#: src/web.py:940 +#: src/web.py:943 #, python-format msgid "The following error occurred when creating the CD-ROM image: %(error)s" msgstr "" "Se ha producido el siguiente error al crear la imagen del CD-ROM: %(error)s" -#: src/web.py:947 +#: src/web.py:950 #, python-format msgid "CD-ROM image %(file_name)s with type %(iso_type)s was created." msgstr "Se creó %(file_name)s de imagen de CD-ROM con el tipo %(iso_type)s." -#: src/web.py:977 src/web.py:1021 +#: src/web.py:980 src/web.py:1024 msgid "Unknown destination" msgstr "Destino desconocido" -#: src/web.py:986 +#: src/web.py:989 #, python-format msgid "The following error occurred when downloading: %(error)s" msgstr "Se ha producido el siguiente error al descargar: %(error)s" -#: src/web.py:1065 src/web.py:1078 +#: src/web.py:1068 src/web.py:1081 #, python-format msgid "%(drive_format)s is not a valid hard disk format." msgstr "%(drive_format)s no es un formato de disco duro válido." -#: src/web.py:1123 +#: src/web.py:1126 #, python-format msgid "Image file with properties created: %(file_name)s%(drive_format)s" msgstr "Archivo de imagen con propiedades creadas: %(file_name)s%(drive_format)s" -#: src/web.py:1133 +#: src/web.py:1136 #, python-format msgid "Image file created: %(file_name)s%(drive_format)s" msgstr "Archivo de imagen creado: %(file_name)s%(drive_format)s" -#: src/web.py:1194 +#: src/web.py:1197 #, python-format msgid "Image file with properties deleted: %(file_name)s" msgstr "Archivo de imagen con propiedades borrado: %(file_name)s" -#: src/web.py:1203 +#: src/web.py:1206 #, python-format msgid "Image file deleted: %(file_name)s" msgstr "Archivo de imagen borrado: %(file_name)s" -#: src/web.py:1241 +#: src/web.py:1244 #, python-format msgid "Image file with properties renamed to: %(file_name)s" msgstr "Archivo de imagen con propiedades renombrado a: %(file_name)s" -#: src/web.py:1250 +#: src/web.py:1253 #, python-format msgid "Image file renamed to: %(file_name)s" msgstr "Archivo de imagen renombrado a: %(file_name)s" -#: src/web.py:1288 +#: src/web.py:1291 #, python-format msgid "Copy of image file with properties saved as: %(file_name)s" msgstr "" "Copia del archivo de imagen con las propiedades guardado como: %(file_name)s" -#: src/web.py:1297 +#: src/web.py:1300 #, python-format msgid "Copy of image file saved as: %(file_name)s" msgstr "Copia del archivo de imagen guardado como: %(file_name)s" -#: src/web.py:1350 +#: src/web.py:1353 #, python-format msgid "Changed Web Interface language to %(locale)s" msgstr "Se ha cambiado el lenguaje de la Interfaz Web a %(locale)s" -#: src/web.py:1361 +#: src/web.py:1364 msgid "The requested theme does not exist." msgstr "El tema solicitado no existe." -#: src/web.py:1364 +#: src/web.py:1367 #, python-format msgid "Theme changed to '%(theme)s'." msgstr "Tema cambiado a ‘%(theme)s’." @@ -419,33 +419,40 @@ msgstr "Imagen de disco extraíble" msgid "Magneto-Optical Disk Image" msgstr "Imagen de disco magneto-óptico" -#: src/web_utils.py:260 +#: src/web_utils.py:263 msgid "You must log in to use this function" msgstr "Debe conectarse para utilizar esta función" -#: src/web_utils.py:274 +#: src/web_utils.py:277 #, python-format msgid "Configure the network bridge for %(interface)s first: " msgstr "Configure primero el puente de red para %(interface)s: " -#: src/web_utils.py:303 -#, python-format -msgid "No permission to use path '%(file_name)s'" -msgstr "No hay permiso para usar la ruta ‘%(file_name)s’" +#: src/web_utils.py:305 +msgid "Path must not be absolute" +msgstr "La ruta no puede ser absoluta" -#: src/web_utils.py:324 +#: src/web_utils.py:307 +msgid "Path must not traverse the file system" +msgstr "La ruta no debe atravesar el sistema de archivos" + +#: src/web_utils.py:309 +msgid "Path must not start in the home directory" +msgstr "La ruta no debe comenzar en el directorio personal" + +#: src/web_utils.py:333 msgid "The file already exists!" msgstr "¡El archivo ya existe!" -#: src/web_utils.py:332 +#: src/web_utils.py:341 msgid "Unable to write the file to disk!" msgstr "¡No se puede escribir el archivo en el disco!" -#: src/web_utils.py:340 +#: src/web_utils.py:349 msgid "Transferred file corrupted!" msgstr "¡Archivo transferido corrompido!" -#: src/web_utils.py:342 +#: src/web_utils.py:351 msgid "File upload successful!" msgstr "¡La carga de archivos se ha realizado con éxito!" @@ -478,7 +485,7 @@ msgstr "Autenticación de la interfaz web deshabilitada" #, python-format msgid "See Wiki for more information" msgstr "" -"Consulte Wiki para obtener más información" +"Consulte Wiki para obtener más información" #: src/templates/base.html:64 msgid "PiSCSI" @@ -606,7 +613,7 @@ msgstr "" "que tenga que proporcionar los controladores de dispositivo y/o los parámetros " "de configuración adecuados para que funcionen correctamente. Si desea que se " "modifiquen los datos, o si tiene otros dispositivos que añadir a la lista, cree " -"un ticket de incidencia en GitHub." +"un ticket de incidencia en GitHub." #: src/templates/drives.html:6 msgid "Hard Disk Drives" @@ -822,7 +829,7 @@ msgid "" "Select a valid SCSI ID and LUN to " "attach to. Unless you know what you're doing, always use LUN 0." msgstr "" -"Seleccione un SCSI ID válido y LUN al que " +"Seleccione un SCSI ID válido y LUN al que " "conectar. A menos que sepa lo que está haciendo, utilice siempre el LUN 0." #: src/templates/index.html:200 @@ -873,8 +880,8 @@ msgstr "Desconocido" #: src/templates/index.html:344 #, python-format -msgid "Enter new file name for: %(file_name)s" -msgstr "Introduzca un nuevo nombre de archivo para: %(file_name)s" +msgid "Enter a new file name:" +msgstr "Introduzca un nuevo nombre de archivo:" #: src/templates/index.html:347 src/templates/index.html:756 msgid "Rename" @@ -882,8 +889,8 @@ msgstr "Renombrar" #: src/templates/index.html:349 #, python-format -msgid "Save copy of %(file_name)s as:" -msgstr "Guardar copia de %(file_name)s como:" +msgid "Enter a file name for the copy:" +msgstr "Introduzca un nombre de archivo para la copia:" #: src/templates/index.html:352 msgid "Copy" @@ -904,8 +911,8 @@ msgstr "Info" #: src/templates/index.html:374 #, python-format -msgid "%(disk_space)s MiB disk space remaining on the system" -msgstr "%(disk_space)s MiB de espacio en disco restante en el sistema" +msgid "%(disk_space)s MiB disk space remaining for images" +msgstr "%(disk_space)s MiB de espacio en disco restante para imágenes" #: src/templates/index.html:382 msgid "Transfer Files to the PiSCSI" @@ -988,7 +995,7 @@ msgid "" "Please refer to wiki documentation to " "learn more about the supported image file types." msgstr "" -"Consulte documentación de la wiki para " +"Consulte documentación de la wiki para " "obtener más información sobre los tipos de archivos de imagen admitidos." #: src/templates/index.html:515 @@ -1046,7 +1053,7 @@ msgid "" "href=\"%(url)s\" target=\"_blank\">Macproxy." msgstr "" "Para navegar por la web moderna, instale un proxy web vintage como Macproxy." +"href=\"%(url)s\" target=\"_blank\">Macproxy." #: src/templates/index.html:586 #, python-format @@ -1054,7 +1061,7 @@ msgid "" "Read more about supported device types on the wiki." msgstr "" -"Más información sobre tipos de dispositivos " +"Más información sobre tipos de dispositivos " "compatibles en la wiki." #: src/templates/index.html:593 @@ -1106,7 +1113,7 @@ msgid "" "The System Name is the \"pretty\" hostname if set, with a fallback to the " "regular hostname." msgstr "" -"El Nombre del Sistema es el nombre de host “bonito” si está configurado, con un " +"El Nombre del Sistema es el nombre de host \"bonito\" si está configurado, con un " "fallback al nombre de host normal." #: src/templates/index.html:731 From fa2b02b222df2b48bd1398b4e59bc74e98d4432e Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Mon, 17 Jul 2023 17:23:10 -0700 Subject: [PATCH 13/87] Bump netatalk 2.x version to 230701 (#1185) --- easyinstall.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easyinstall.sh b/easyinstall.sh index 33c2fdb6..53c10ce2 100755 --- a/easyinstall.sh +++ b/easyinstall.sh @@ -735,7 +735,7 @@ function createFileSharingDir() { # Downloads, compiles, and installs Netatalk (AppleShare server) function installNetatalk() { - NETATALK_VERSION="230302" + NETATALK_VERSION="230701" NETATALK_CONFIG_PATH="/etc/netatalk" NETATALK_OPTIONS="--cores=$CORES --share-name='$FILE_SHARE_NAME' --share-path='$FILE_SHARE_PATH'" From 5a6d12c0753c80ccf65f226fa66d9dc8e9d543a9 Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Mon, 17 Jul 2023 17:23:25 -0700 Subject: [PATCH 14/87] Swedish translation update 2023-07 (#1184) --- .../translations/sv/LC_MESSAGES/messages.po | 350 +++++++++--------- 1 file changed, 182 insertions(+), 168 deletions(-) diff --git a/python/web/src/translations/sv/LC_MESSAGES/messages.po b/python/web/src/translations/sv/LC_MESSAGES/messages.po index 6241a3e2..f13d36e1 100644 --- a/python/web/src/translations/sv/LC_MESSAGES/messages.po +++ b/python/web/src/translations/sv/LC_MESSAGES/messages.po @@ -5,19 +5,18 @@ # msgid "" msgstr "" -"Project-Id-Version: PiSCSI\n" +"Project-Id-Version: PiSCSI\n" "Report-Msgid-Bugs-To: https://github.com/PiSCSI/piscsi/issues\n" -"POT-Creation-Date: 2023-03-19 14:43-0700\n" -"PO-Revision-Date: 2023-03-24 09:06-0700\n" +"POT-Creation-Date: 2023-07-05 10:36-0700\n" +"PO-Revision-Date: 2023-07-05 10:10-0700\n" "Last-Translator: Daniel Markstedt \n" -"Language-Team: N/A\n" "Language: sv\n" +"Language-Team: N/A\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"Generated-By: Babel 2.11.0\n" -"X-Generator: Poedit 3.2.2\n" +"Generated-By: Babel 2.12.1\n" #: src/return_code_mapper.py:14 #, python-format @@ -96,10 +95,11 @@ msgstr "Kunde ej läsa egenskaper från filen %(file_path)s" #: src/return_code_mapper.py:44 #, python-format msgid "" -"Cannot insert an image for %(device_type)s into a %(current_device_type)s device" +"Cannot insert an image for %(device_type)s into a %(current_device_type)s" +" device" msgstr "" -"Det går inte att mata in en skiva av typ %(device_type)s i en enhet av typ " -"%(current_device_type)s" +"Det går inte att mata in en skiva av typ %(device_type)s i en enhet av " +"typ %(current_device_type)s" #: src/return_code_mapper.py:46 #, python-format @@ -122,240 +122,242 @@ msgstr "Kunde ej packa upp arkivet: %(error)s" #: src/socket_cmds_flask.py:42 #, python-format msgid "" -"The PiSCSI Web Interface failed to connect to PiSCSI at %(host)s:%(port)s with " -"error: %(error_msg)s. The PiSCSI process is not running or may have crashed." +"The PiSCSI Web Interface failed to connect to PiSCSI at %(host)s:%(port)s" +" with error: %(error_msg)s. The PiSCSI process is not running or may have" +" crashed." msgstr "" -"PiSCSIs webbgränssnitt kunde inte ansluta till PiSCSI på %(host)s:%(port)s med " -"felmeddelandet '%(error_msg)s'. PiSCSI-processen är antingen avslagen eller har " -"krashat." +"PiSCSIs webbgränssnitt kunde inte ansluta till PiSCSI på " +"%(host)s:%(port)s med felmeddelandet '%(error_msg)s'. PiSCSI-processen är" +" antingen avslagen eller har krashat." #: src/socket_cmds_flask.py:54 msgid "" -"The PiSCSI Web Interface lost connection to PiSCSI. Please go back and try " -"again. If the issue persists, please report a bug." +"The PiSCSI Web Interface lost connection to PiSCSI. Please go back and " +"try again. If the issue persists, please report a bug." msgstr "" -"PiSCSIs webbgränssnitt tappade kontaken med PiSCSI. Gå tillbaks och försök igen. " -"Om samma fel upprepas så rapportera en bugg." +"PiSCSIs webbgränssnitt tappade kontaken med PiSCSI. Gå tillbaks och " +"försök igen. Om samma fel upprepas så rapportera en bugg." #: src/socket_cmds_flask.py:64 msgid "" -"The PiSCSI Web Interface did not get a valid response from PiSCSI. Please go " -"back and try again. If the issue persists, please report a bug." +"The PiSCSI Web Interface did not get a valid response from PiSCSI. Please" +" go back and try again. If the issue persists, please report a bug." msgstr "" -"PiSCSIs webbgränssnitt fick en ogiltig respons från PiSCSI. Gå tillbaks och " -"försök igen. Om samma fel upprepas så rapportera en bugg." +"PiSCSIs webbgränssnitt fick en ogiltig respons från PiSCSI. Gå tillbaks " +"och försök igen. Om samma fel upprepas så rapportera en bugg." -#: src/web.py:252 +#: src/web.py:255 msgid "PiSCSI Control Page" msgstr "PiSCSI kontrollsida" -#: src/web.py:304 +#: src/web.py:307 msgid "PiSCSI Create Drive" msgstr "PiSCSI skapa skiva" -#: src/web.py:320 +#: src/web.py:323 msgid "PiSCSI File Upload" msgstr "PiSCSI uppladdning" -#: src/web.py:346 +#: src/web.py:349 #, python-format msgid "You must log in with valid credentials for a user in the '%(group)s' group" msgstr "Du måste logga in som en användare som tillhör %(group)s-gruppen" -#: src/web.py:399 src/web.py:444 +#: src/web.py:402 src/web.py:447 #, python-format msgid "No properties data for drive %(drive_name)s" msgstr "Saknar egenskapsdata för skivan %(drive_name)s" -#: src/web.py:421 +#: src/web.py:424 #, python-format msgid "Image file with properties created: %(file_name)s" msgstr "Skapade skivbildsfil med egenskaper: %(file_name)s" -#: src/web.py:522 +#: src/web.py:525 msgid "PiSCSI Image Info" msgstr "PiSCSI bildfilsinfo" -#: src/web.py:529 +#: src/web.py:532 #, python-format msgid "An error occurred when getting disk info: %(error)s" msgstr "Ett fel inträffade när vi hämtade skivinfo: %(error)s" -#: src/web.py:543 +#: src/web.py:546 #, python-format msgid "%(app)s is not a recognized PiSCSI app" msgstr "%(app)s är inte en giltig PiSCSI-app" -#: src/web.py:570 +#: src/web.py:573 msgid "PiSCSI Manual" msgstr "PiSCSI manual" -#: src/web.py:577 +#: src/web.py:580 #, python-format msgid "An error occurred when accessing manual page: %(error)s" msgstr "Ett fel inträffade när vi hämtade manualsidan: %(error)s" -#: src/web.py:593 +#: src/web.py:596 msgid "PiSCSI System Logs" msgstr "PiSCSI systemloggar" -#: src/web.py:601 +#: src/web.py:604 #, python-format msgid "An error occurred when fetching logs: %(error)s" msgstr "Ett fel inträffade när vi hämtade loggar: %(error)s" -#: src/web.py:615 +#: src/web.py:618 #, python-format msgid "Log level set to %(value)s" msgstr "Bytte loggnivån till %(value)s" -#: src/web.py:632 src/web.py:695 +#: src/web.py:635 src/web.py:698 msgid "No SCSI ID specified" msgstr "Inget SCSI-id angett" -#: src/web.py:673 +#: src/web.py:676 #, python-format msgid "Attached %(device_type)s to SCSI ID %(id_number)s LUN %(unit_number)s" msgstr "" -"Anslöt %(device_type)s till SCSI-id %(id_number)s enhetsnummer %(unit_number)s" +"Anslöt %(device_type)s till SCSI-id %(id_number)s enhetsnummer " +"%(unit_number)s" -#: src/web.py:697 +#: src/web.py:700 msgid "No image file to insert" msgstr "Ingen skivfil att ansluta" -#: src/web.py:737 +#: src/web.py:740 #, python-format msgid "" "Attached %(file_name)s as %(device_type)s to SCSI ID %(id_number)s LUN " "%(unit_number)s" msgstr "" -"Anslöt %(file_name)s som %(device_type)s till SCSI-id %(id_number)s enhetsnummer " -"%(unit_number)s" +"Anslöt %(file_name)s som %(device_type)s till SCSI-id %(id_number)s " +"enhetsnummer %(unit_number)s" -#: src/web.py:754 +#: src/web.py:757 msgid "Detached all SCSI devices" msgstr "Kopplade ifrån alla SCSI-enheter" -#: src/web.py:770 +#: src/web.py:773 #, python-format msgid "Detached SCSI ID %(id_number)s LUN %(unit_number)s" msgstr "Kopplade ifrån SCSI-id %(id_number)s enhetsnummer %(unit_number)s" -#: src/web.py:792 +#: src/web.py:795 #, python-format msgid "Ejected SCSI ID %(id_number)s LUN %(unit_number)s" msgstr "Matade ut SCSI-id %(id_number)s enhetsnummer %(unit_number)s" -#: src/web.py:811 +#: src/web.py:814 msgid "PiSCSI Device Info" msgstr "PiSCSI enhetsinfo" -#: src/web.py:815 +#: src/web.py:818 msgid "No devices attached" msgstr "Inga enheter anslutna" -#: src/web.py:831 +#: src/web.py:834 #, python-format msgid "Reserved SCSI ID %(id_number)s" msgstr "Reserverat SCSI-id %(id_number)s" -#: src/web.py:849 +#: src/web.py:852 #, python-format msgid "Released the reservation for SCSI ID %(id_number)s" msgstr "Frigjorde SCSI-id %(id_number)s" -#: src/web.py:868 +#: src/web.py:871 #, python-format msgid "System name changed to '%(name)s'." msgstr "Ändrade systemnamnet till '%(name)s'." -#: src/web.py:869 +#: src/web.py:872 msgid "System name reset to default." msgstr "Återställde systemnamnet till grundvärdet." -#: src/web.py:871 +#: src/web.py:874 msgid "Failed to change system name." msgstr "Kunde ej ändra systemnamnet." -#: src/web.py:925 +#: src/web.py:928 #, python-format msgid "%(iso_type)s is not a valid CD-ROM format." msgstr "%(iso_type)s är inte ett giltigt cd-romformat." -#: src/web.py:940 +#: src/web.py:943 #, python-format msgid "The following error occurred when creating the CD-ROM image: %(error)s" msgstr "Ett fel inträffade när vi skapade en cd-bildfil: %(error)s" -#: src/web.py:947 +#: src/web.py:950 #, python-format msgid "CD-ROM image %(file_name)s with type %(iso_type)s was created." msgstr "Cd-bildfilen %(file_name)s av typ %(iso_type)s har skapats." -#: src/web.py:977 src/web.py:1021 +#: src/web.py:980 src/web.py:1024 msgid "Unknown destination" msgstr "Okänd destination" -#: src/web.py:986 +#: src/web.py:989 #, python-format msgid "The following error occurred when downloading: %(error)s" msgstr "Ett fel inträffade under nedladdningen: %(error)s" -#: src/web.py:1065 src/web.py:1078 +#: src/web.py:1068 src/web.py:1081 #, python-format msgid "%(drive_format)s is not a valid hard disk format." msgstr "%(drive_format)s är inte ett giltigt hårddisksformat." -#: src/web.py:1123 +#: src/web.py:1126 #, python-format msgid "Image file with properties created: %(file_name)s%(drive_format)s" msgstr "Skapade skivbildsfil med egenskaper: %(file_name)s%(drive_format)s" -#: src/web.py:1133 +#: src/web.py:1136 #, python-format msgid "Image file created: %(file_name)s%(drive_format)s" msgstr "Skapade skivbildsfil %(file_name)s%(drive_format)s" -#: src/web.py:1194 +#: src/web.py:1197 #, python-format msgid "Image file with properties deleted: %(file_name)s" msgstr "Bildfil med egenskaper raderades: %(file_name)s" -#: src/web.py:1203 +#: src/web.py:1206 #, python-format msgid "Image file deleted: %(file_name)s" msgstr "Filen %(file_name)s raderades" -#: src/web.py:1241 +#: src/web.py:1244 #, python-format msgid "Image file with properties renamed to: %(file_name)s" msgstr "Filen med egenskaper döptes om till %(file_name)s" -#: src/web.py:1250 +#: src/web.py:1253 #, python-format msgid "Image file renamed to: %(file_name)s" msgstr "Filen döptes om till %(file_name)s" -#: src/web.py:1288 +#: src/web.py:1291 #, python-format msgid "Copy of image file with properties saved as: %(file_name)s" msgstr "Kopierade filen med egenskaper och sparade den som %(file_name)s" -#: src/web.py:1297 +#: src/web.py:1300 #, python-format msgid "Copy of image file saved as: %(file_name)s" msgstr "Kopierade filen och sparade den som %(file_name)s" -#: src/web.py:1350 +#: src/web.py:1353 #, python-format msgid "Changed Web Interface language to %(locale)s" msgstr "Bytte webbgränssnittets språk till %(locale)s" -#: src/web.py:1361 +#: src/web.py:1364 msgid "The requested theme does not exist." msgstr "Temat existerar inte." -#: src/web.py:1364 +#: src/web.py:1367 #, python-format msgid "Theme changed to '%(theme)s'." msgstr "Bytte till temat '%(theme)s'." @@ -416,33 +418,40 @@ msgstr "Uttagbar skiva" msgid "Magneto-Optical Disk Image" msgstr "Magnetoptisk skiva" -#: src/web_utils.py:260 +#: src/web_utils.py:263 msgid "You must log in to use this function" msgstr "Du måste logga in för att använda den här funktionen" -#: src/web_utils.py:274 +#: src/web_utils.py:277 #, python-format msgid "Configure the network bridge for %(interface)s first: " msgstr "Ställ in nätverksbryggan för %(interface)s först: " -#: src/web_utils.py:303 -#, python-format -msgid "No permission to use path '%(file_name)s'" -msgstr "Ingen behörighet att använda sökvägen \"%(file_name)s\"" +#: src/web_utils.py:305 +msgid "Path must not be absolute" +msgstr "Sökvägen kan inte vara absolut" -#: src/web_utils.py:324 +#: src/web_utils.py:307 +msgid "Path must not traverse the file system" +msgstr "Sökvägen får inte lov att traversera filsystemet" + +#: src/web_utils.py:309 +msgid "Path must not start in the home directory" +msgstr "Sökvägen kan inte starta i hemkatalogen" + +#: src/web_utils.py:333 msgid "The file already exists!" msgstr "Filen finns redan!" -#: src/web_utils.py:332 +#: src/web_utils.py:341 msgid "Unable to write the file to disk!" msgstr "Kunde ej skriva filen till skivan!" -#: src/web_utils.py:340 +#: src/web_utils.py:349 msgid "Transferred file corrupted!" msgstr "Den överförda filen är skadad!" -#: src/web_utils.py:342 +#: src/web_utils.py:351 msgid "File upload successful!" msgstr "Filen har laddas upp!" @@ -513,7 +522,7 @@ msgid "Macproxy is running at %(ip_addr)s (default port 5000)" msgstr "Macproxy är tillgängligt på %(ip_addr)s (vanligtvis port 5000)" #: src/templates/base.html:120 -msgid "PiSCSI version:" +msgid "PiSCSI software version:" msgstr "PiSCSI-version:" #: src/templates/base.html:123 @@ -591,17 +600,18 @@ msgstr "Observera" #: src/templates/drives.html:5 #, python-format msgid "" -"These device profiles are provided as-is with no guarantee to work equally to " -"the actual physical device they are named after. You may need to provide " -"appropirate device drivers and/or configuration parameters for them to function " -"properly. If you would like to see data modified, or have additional devices to " -"add to the list, please raise an issue ticket at GitHub." +"These device profiles are provided as-is with no guarantee to work " +"equally to the actual physical device they are named after. You may need " +"to provide appropirate device drivers and/or configuration parameters for" +" them to function properly. If you would like to see data modified, or " +"have additional devices to add to the list, please raise an issue ticket " +"at GitHub." msgstr "" -"Det finns ingen garanti att dessa enhetsprofiler fungerar lika väl som de " -"verkliga enheterna de delar namn med. Du bör antagligen skaffa drivrutiner och " -"ange vissa parametrar för att de ska fungera korrekt. Om du ser något som bör " -"ändras, eller om du har andra enhetsprofiler som du skulle vilja lägga till, " -"kontakta oss via GitHub." +"Det finns ingen garanti att dessa enhetsprofiler fungerar lika väl som de" +" verkliga enheterna de delar namn med. Du bör antagligen skaffa " +"drivrutiner och ange vissa parametrar för att de ska fungera korrekt. Om " +"du ser något som bör ändras, eller om du har andra enhetsprofiler som du " +"skulle vilja lägga till, kontakta oss via GitHub." #: src/templates/drives.html:6 msgid "Hard Disk Drives" @@ -642,11 +652,11 @@ msgstr "Cd- och dvd-spelare" #: src/templates/drives.html:43 msgid "" -"This will create a properties file for the given CD-ROM or DVD image. No new " -"image file will be created." +"This will create a properties file for the given CD-ROM or DVD image. No " +"new image file will be created." msgstr "" -"Denna funktion skapar endast en egenskapsfil för en cd- eller dvd-bildfil. Ingen " -"ny cd-bildfil kommer att skapas." +"Denna funktion skapar endast en egenskapsfil för en cd- eller dvd-" +"bildfil. Ingen ny cd-bildfil kommer att skapas." #: src/templates/drives.html:64 msgid "Create for:" @@ -663,19 +673,19 @@ msgstr "%(disk_space)s MiB återstår på Pi-systemets skiva" #: src/templates/index.html:6 msgid "" -" This process may take a while, and will continue in the background if you " -"navigate away from this page." +" This process may take a while, and will continue in the background if " +"you navigate away from this page." msgstr "" -" Denna process kan ta ett tag, och kommer att forsätta i bakgrunden om du " -"navigerar bort från den här sidan." +" Denna process kan ta ett tag, och kommer att forsätta i bakgrunden om du" +" navigerar bort från den här sidan." #: src/templates/index.html:11 msgid "" -" The Web Interface will become unresponsive momentarily. Reload this page after " -"the Pi has started up again." +" The Web Interface will become unresponsive momentarily. Reload this page" +" after the Pi has started up again." msgstr "" -" Webbgränssnittet kommer att sluta reagera nu. Ladda om den här sidan efter " -"systemet har startat igen." +" Webbgränssnittet kommer att sluta reagera nu. Ladda om den här sidan " +"efter systemet har startat igen." #: src/templates/index.html:19 msgid "Current PiSCSI Configuration" @@ -684,19 +694,19 @@ msgstr "Nuvarande PiSCSI-konfiguration" #: src/templates/index.html:22 #, python-format msgid "" -"Save and load device configurations, stored as json files in %(config_dir)s" +"Save and load device configurations, stored as json files in " +"%(config_dir)s" msgstr "" "Spara och läs enhetskonfigurationer. Sparas som json-format i " "%(config_dir)s" #: src/templates/index.html:23 msgid "" -"To have a particular device configuration load when PiSCSI starts, save it as " -"default." +"To have a particular device configuration load when PiSCSI starts, save " +"it as default." msgstr "" -"Om du sparar en enhetskonfiguration som default så blir den läst när " -"PiSCSI startar." +"Om du sparar en enhetskonfiguration som default så blir den läst" +" när PiSCSI startar." #: src/templates/index.html:29 src/templates/index.html:51 #: src/templates/index.html:520 @@ -804,8 +814,8 @@ msgstr "Bildfilshanterare" #: src/templates/index.html:196 #, python-format msgid "" -"Manage image files in the active PiSCSI image directory: %(directory)s " -"with a scan depth of %(scan_depth)s." +"Manage image files in the active PiSCSI image directory: " +"%(directory)s with a scan depth of %(scan_depth)s." msgstr "" "Hantera filer i den aktiva bildfilskatalogen: %(directory)s med " "hierarkiskt djup %(scan_depth)s." @@ -813,12 +823,12 @@ msgstr "" #: src/templates/index.html:197 #, python-format msgid "" -"Select a valid SCSI ID and LUN to " -"attach to. Unless you know what you're doing, always use LUN 0." +"Select a valid SCSI ID and LUN " +"to attach to. Unless you know what you're doing, always use LUN 0." msgstr "" -"Välj ett giltigt SCSI-id samt LUN " -"(enhetsnummer) att ansluta till. Om du inte har särskild orsak, använd alltid " -"enhetsnummer 0." +"Välj ett giltigt SCSI-id samt LUN (enhetsnummer) att ansluta till. Om du inte har" +" särskild orsak, använd alltid enhetsnummer 0." #: src/templates/index.html:200 msgid "Recognized image file types:" @@ -867,18 +877,16 @@ msgid "Unknown" msgstr "Okänd" #: src/templates/index.html:344 -#, python-format -msgid "Enter new file name for: %(file_name)s" -msgstr "Ange ett nytt filnamn åt %(file_name)s" +msgid "Enter a new file name:" +msgstr "Ange ett nytt filnamn:" #: src/templates/index.html:347 src/templates/index.html:756 msgid "Rename" msgstr "Döp om" #: src/templates/index.html:349 -#, python-format -msgid "Save copy of %(file_name)s as:" -msgstr "Spara kopia av %(file_name)s som:" +msgid "Enter a file name for the copy:" +msgstr "Ange ett nytt filnamn åt kopian:" #: src/templates/index.html:352 msgid "Copy" @@ -899,8 +907,8 @@ msgstr "Info" #: src/templates/index.html:374 #, python-format -msgid "%(disk_space)s MiB disk space remaining on the system" -msgstr "Fritt utrymme på skivan: %(disk_space)s MiB" +msgid "%(disk_space)s MiB disk space remaining for images" +msgstr "%(disk_space)s MiB är tillgängligt för skivbilder" #: src/templates/index.html:382 msgid "Transfer Files to the PiSCSI" @@ -918,11 +926,11 @@ msgstr "Fildelning" #: src/templates/index.html:389 msgid "" -"Install a file server and create the shared files directory in order to share " -"files between the Pi and your vintage computers." +"Install a file server and create the shared files directory in order to " +"share files between the Pi and your vintage computers." msgstr "" -"Installera en filserver och skapa en katalog för fildelning för att dela filer " -"mellan Pi och dina gamla datorer." +"Installera en filserver och skapa en katalog för fildelning för att dela " +"filer mellan Pi och dina gamla datorer." #: src/templates/index.html:395 src/templates/index.html:446 msgid "Download file from URL:" @@ -950,11 +958,11 @@ msgstr "HFS är för Mac OS, Joliet för Windows, samt Rock Ridge för POSIX." #: src/templates/index.html:440 msgid "" -"If the downloaded file is a zip archive, we will attempt to unzip it and store " -"the resulting files." +"If the downloaded file is a zip archive, we will attempt to unzip it and " +"store the resulting files." msgstr "" -"Om den nedladdade filen är en zipfil så försöker vi packa up den och spara de " -"uppackade filerna på cd-bildfilen." +"Om den nedladdade filen är en zipfil så försöker vi packa up den och " +"spara de uppackade filerna på cd-bildfilen." #: src/templates/index.html:448 src/templates/index.html:480 #: src/templates/index.html:522 @@ -980,15 +988,16 @@ msgstr "Skapa tom skivbilsdfil" #: src/templates/index.html:514 #, python-format msgid "" -"Please refer to wiki documentation to " -"learn more about the supported image file types." +"Please refer to wiki " +"documentation to learn more about the supported image file types." msgstr "" -"Se wikin för vidare information " -"angående filtyper som stödjs." +"Se wikin för vidare information" +" angående filtyper som stödjs." #: src/templates/index.html:515 msgid "" -"It is not recommended to use the Lido hard disk driver with the Macintosh Plus." +"It is not recommended to use the Lido hard disk driver with the Macintosh" +" Plus." msgstr "Det är inte rekommenderat att använda Lido-drivrutiner med Macintosh Plus." #: src/templates/index.html:530 @@ -1018,19 +1027,19 @@ msgstr "Anslut kringutrustning" #: src/templates/index.html:580 msgid "" -"The piscsi_bridge network bridge is active and ready to be used by an " -"emulated network adapter!" +"The piscsi_bridge network bridge is active and ready to be used " +"by an emulated network adapter!" msgstr "" -"Nätverksbryggan piscsi_bridge är påslagen och kan användas av det " -"emulerade gränssnittet!" +"Nätverksbryggan piscsi_bridge är påslagen och kan användas av " +"det emulerade gränssnittet!" #: src/templates/index.html:582 msgid "" -"Please configure the piscsi_bridge network bridge before attaching an " -"emulated network adapter!" +"Please configure the piscsi_bridge network bridge before " +"attaching an emulated network adapter!" msgstr "" -"Se till att ställa in och sätta på piscsi_bridge innan du ansluter det " -"emulerade nätverksgränssnittet!" +"Se till att ställa in och sätta på piscsi_bridge innan du " +"ansluter det emulerade nätverksgränssnittet!" #: src/templates/index.html:584 #, python-format @@ -1038,17 +1047,17 @@ msgid "" "To browse the modern web, install a vintage web proxy such as Macproxy." msgstr "" -"För att surfa på den moderna webben, installera en webb-proxyserver så som Macproxy." +"För att surfa på den moderna webben, installera en webb-proxyserver så " +"som Macproxy." #: src/templates/index.html:586 #, python-format msgid "" -"Read more about supported device types " -"on the wiki." +"Read more about supported device " +"types on the wiki." msgstr "" -"Läs mer om enheter som stödjs (engelska) på wikin." +"Läs mer om enheter som stödjs " +"(engelska) på wikin." #: src/templates/index.html:593 msgid "Key" @@ -1096,18 +1105,19 @@ msgstr "Systemdrift" #: src/templates/index.html:730 msgid "" -"The System Name is the \"pretty\" hostname if set, with a fallback to the " -"regular hostname." +"The System Name is the \"pretty\" hostname if set, with a fallback to the" +" regular hostname." msgstr "" -"Vi använder \"pretty hostname\" som systemnamn, med vanligt hostname som reserv." +"Vi använder \"pretty hostname\" som systemnamn, med vanligt hostname som " +"reserv." #: src/templates/index.html:731 msgid "" -"IMPORTANT: Always shut down the system before turning off the power. Failing to " -"do so may lead to data loss." +"IMPORTANT: Always shut down the system before turning off the power. " +"Failing to do so may lead to data loss." msgstr "" -"VIKTIGT: Stäng alltid av systemet innan du stänger av strömmen. Om du inte gör " -"det kan det leda till dataförlust." +"VIKTIGT: Stäng alltid av systemet innan du stänger av strömmen. Om du " +"inte gör det kan det leda till dataförlust." #: src/templates/index.html:737 msgid "Language:" @@ -1166,19 +1176,19 @@ msgstr "Ladda upp filer från din dator" #: src/templates/upload.html:6 #, python-format msgid "" -"The largest file size accepted in this form is %(max_file_size)s MiB. Use other " -"file transfer means for larger files." +"The largest file size accepted in this form is %(max_file_size)s MiB. Use" +" other file transfer means for larger files." msgstr "" -"Den största filstorlek som kan hanteras i detta formulär är %(max_file_size)s " -"MiB. Använd andra men för större storlekar." +"Den största filstorlek som kan hanteras i detta formulär är " +"%(max_file_size)s MiB. Använd andra men för större storlekar." #: src/templates/upload.html:7 msgid "" "You have to manually clean up partially uploaded files, as a result of " "cancelling the upload or closing this page." msgstr "" -"Du måste själv rensa upp delvis uppladdade filer som ett resultat av att du " -"avbryter uppladdningen eller stänger den här sidan." +"Du måste själv rensa upp delvis uppladdade filer som ett resultat av att " +"du avbryter uppladdningen eller stänger den här sidan." #: src/templates/upload.html:12 src/templates/upload.html:36 msgid "PiSCSI Config" @@ -1198,14 +1208,17 @@ msgstr "Din webbläsare stöder ej filuppladdning via dra och släpp." #: src/templates/upload.html:56 msgid "" -"Please use the fallback form below to upload your files like in the olden days." +"Please use the fallback form below to upload your files like in the olden" +" days." msgstr "" -"Använd reservformuläret nedan för att ladda upp dina filer på gammaldags vis." +"Använd reservformuläret nedan för att ladda upp dina filer på gammaldags " +"vis." #: src/templates/upload.html:57 msgid "File is too big: {{filesize}}MiB. Max filesize: {{maxFilesize}}MiB." msgstr "" -"Filen är för stor: {{filesize}}MiB. Största möjliga storlek: {{maxFilesize}}MiB." +"Filen är för stor: {{filesize}}MiB. Största möjliga storlek: " +"{{maxFilesize}}MiB." #: src/templates/upload.html:58 msgid "You can't upload files of this type." @@ -1250,3 +1263,4 @@ msgstr "KiB" #: src/templates/upload.html:70 msgid "B" msgstr "B" + From 51f1e7eae79cd75fc08b5430e719545c05eb1a17 Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Wed, 26 Jul 2023 18:19:22 -0700 Subject: [PATCH 15/87] Check for predictable network interface names in bridge check function, and always return a status message with the outcome of the check. Issue #1191 --- python/web/src/web.py | 4 +++- python/web/src/web_utils.py | 18 ++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/python/web/src/web.py b/python/web/src/web.py index 40eb6c14..cc0a1398 100644 --- a/python/web/src/web.py +++ b/python/web/src/web.py @@ -651,10 +651,12 @@ def attach_device(): if param: params.update({item.replace(PARAM_PREFIX, ""): param}) + return_message = "Attached %(device_type)s to SCSI ID %(id_number)s LUN %(unit_number)s" if "interface" in params.keys(): bridge_status = is_bridge_configured(params["interface"]) if not bridge_status["status"]: return response(error=True, message=bridge_status["msg"]) + return_message = return_message + " - " + bridge_status["msg"] kwargs = { "unit": int(unit), @@ -672,7 +674,7 @@ def attach_device(): if process["status"]: return response( message=_( - "Attached %(device_type)s to SCSI ID %(id_number)s LUN %(unit_number)s", + return_message, device_type=get_device_name(device_type), id_number=scsi_id, unit_number=unit, diff --git a/python/web/src/web_utils.py b/python/web/src/web_utils.py index 862a3caf..e8acd00a 100644 --- a/python/web/src/web_utils.py +++ b/python/web/src/web_utils.py @@ -274,24 +274,34 @@ def is_bridge_configured(interface): PATH_IPTV4 = "/etc/iptables/rules.v4" PATH_DHCPCD = "/etc/dhcpcd.conf" PATH_BRIDGE = "/etc/network/interfaces.d/piscsi_bridge" - return_msg = _("Configure the network bridge for %(interface)s first: ", interface=interface) to_configure = [] sys_cmd = SysCmds() - if interface.startswith("wlan"): + if interface.startswith("wlan") or interface.startswith("wlx"): + return_msg = _("Wireless network bridge enabled for %(interface)s", interface=interface) if not sys_cmd.introspect_file(PATH_SYSCTL, r"^net\.ipv4\.ip_forward=1$"): to_configure.append("IPv4 forwarding") if not Path(PATH_IPTV4).is_file(): to_configure.append("NAT") - else: + elif interface.startswith("eth") or interface.startswith("enx"): + return_msg = _("Wired network bridge enabled for %(interface)s", interface=interface) if not sys_cmd.introspect_file(PATH_DHCPCD, r"^denyinterfaces " + interface + r"$"): to_configure.append(PATH_DHCPCD) if not Path(PATH_BRIDGE).is_file(): to_configure.append(PATH_BRIDGE) + else: + return_msg = _( + "Unable to detect if %(interface)s is Ethernet or WiFi. " + "Make sure that the correct network bridge is configured.", + interface=interface, + ) if to_configure: + return_msg = _( + "Configure the network bridge for %(interface)s first: ", interface=interface + ) return {"status": False, "msg": return_msg + ", ".join(to_configure)} - return {"status": True, "msg": ""} + return {"status": True, "msg": return_msg} def is_safe_path(file_name): From d3317e97027af1fa05e117a6eb810c38f25217c4 Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Fri, 28 Jul 2023 22:53:12 -0700 Subject: [PATCH 16/87] Bump stylelint to 15.10.2 and remove deprecated module (#1189) - Bump stylelint and stylelint-config-standard libs to the latest versions (security fixes) - Remove deprecated stylelint-config-prettier module - Scan the classic style sheet with stylelint and prettier in the github workflow - Address stylelint and prettier issues in the classic stylesheet - Bump prettier to 3.0.0 --- .github/workflows/web.yml | 8 +- python/web/.stylelintrc.json | 7 +- python/web/package-lock.json | 2143 +++-------------- python/web/package.json | 7 +- .../web/src/static/themes/classic/style.css | 16 +- 5 files changed, 409 insertions(+), 1772 deletions(-) diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml index 69da8317..4b31adad 100644 --- a/.github/workflows/web.yml +++ b/.github/workflows/web.yml @@ -123,8 +123,12 @@ jobs: id: npm - name: Stylelint - run: npx stylelint src/static/themes/modern/style.css + run: | + npx stylelint src/static/themes/modern/style.css + npx stylelint src/static/themes/classic/style.css - name: Prettier - run: npx prettier --check src/static/themes/modern/style.css + run: | + npx prettier --check src/static/themes/modern/style.css + npx prettier --check src/static/themes/classic/style.css if: success() || failure() && steps.npm.outcome == 'success' diff --git a/python/web/.stylelintrc.json b/python/web/.stylelintrc.json index d7b63129..8bd7861c 100644 --- a/python/web/.stylelintrc.json +++ b/python/web/.stylelintrc.json @@ -1,6 +1,7 @@ { - "extends": ["stylelint-config-standard", "stylelint-config-prettier"], + "extends": ["stylelint-config-standard"], "rules": { - "no-descending-specificity": null + "no-descending-specificity": null, + "media-feature-range-notation": "prefix" } -} \ No newline at end of file +} diff --git a/python/web/package-lock.json b/python/web/package-lock.json index d2bae6b7..06133d7f 100644 --- a/python/web/package-lock.json +++ b/python/web/package-lock.json @@ -1,44 +1,43 @@ { "name": "web", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { "devDependencies": { - "prettier": "2.7.1", - "stylelint": "^14.14.1", - "stylelint-config-prettier": "^9.0.3", - "stylelint-config-standard": "^29.0.0" + "prettier": "3.0.0", + "stylelint": "^15.10.2", + "stylelint-config-standard": "^34.0.0" } }, "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-validator-identifier": "^7.22.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, @@ -46,21 +45,84 @@ "node": ">=6.9.0" } }, - "node_modules/@csstools/selector-specificity": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz", - "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==", + "node_modules/@csstools/css-parser-algorithms": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.3.0.tgz", + "integrity": "sha512-dTKSIHHWc0zPvcS5cqGP+/TPFUJB0ekJ9dGKvMAFoNuBFhDPBt9OMGNZiIA5vTiNdGHHBeScYPXIGBMnVOahsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^2.1.1" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.1.1.tgz", + "integrity": "sha512-GbrTj2Z8MCTUv+52GE0RbFGM527xuXZ0Xa5g0Z+YN573uveS4G0qi6WNOMyz3yrFM/jaILTTwJ0+umx81EzqfA==", "dev": true, "engines": { - "node": "^12 || ^14 || >=16" + "node": "^14 || ^16 || >=18" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/csstools" + } + }, + "node_modules/@csstools/media-query-list-parser": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.2.tgz", + "integrity": "sha512-M8cFGGwl866o6++vIY7j1AKuq9v57cf+dGepScwCcbut9ypJNr4Cj+LLTWligYUZ0uyhEoJDKt5lvyBfh2L3ZQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "postcss": "^8.2", - "postcss-selector-parser": "^6.0.10" + "@csstools/css-parser-algorithms": "^2.3.0", + "@csstools/css-tokenizer": "^2.1.1" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.0.tgz", + "integrity": "sha512-hBI9tfBtuPIi885ZsZ32IMEU/5nlZH/KOVYJCOh7gyMxaVLGmLedYqFN6Ui1LXkI8JlC8IsuC0rF0btcRZKd5g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^6.0.13" } }, "node_modules/@nodelib/fs.scandir": { @@ -110,12 +172,6 @@ "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "dev": true }, - "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true - }, "node_modules/ajv": { "version": "8.11.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", @@ -153,6 +209,12 @@ "node": ">=4" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -224,26 +286,30 @@ } }, "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-7.0.2.tgz", + "integrity": "sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==", "dev": true, "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" + "camelcase": "^6.3.0", + "map-obj": "^4.1.0", + "quick-lru": "^5.1.1", + "type-fest": "^1.2.1" }, "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -291,30 +357,45 @@ "dev": true }, "node_modules/cosmiconfig": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", - "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", + "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==", "dev": true, "dependencies": { - "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" + "path-type": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" } }, "node_modules/css-functions-list": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.1.0.tgz", - "integrity": "sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.0.tgz", + "integrity": "sha512-d/jBMPyYybkkLVypgtGv12R+pIFw4/f/IHtCTxWpZc8ofTYOPigIgmA6vu5rMHartZC+WuXhBUHfnyNUIQSYrg==", "dev": true, "engines": { "node": ">=12.22" } }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -345,12 +426,15 @@ } }, "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-5.0.1.tgz", + "integrity": "sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/decamelize-keys": { @@ -369,6 +453,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/decamelize-keys/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/decamelize-keys/node_modules/map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", @@ -421,9 +514,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz", + "integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -479,16 +572,19 @@ } }, "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "dependencies": { - "locate-path": "^5.0.0", + "locate-path": "^6.0.0", "path-exists": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/flat-cache": { @@ -649,9 +745,9 @@ } }, "node_modules/html-tags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz", - "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", "dev": true, "engines": { "node": ">=8" @@ -661,9 +757,9 @@ } }, "node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true, "engines": { "node": ">= 4" @@ -713,12 +809,15 @@ } }, "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", "dev": true, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/inflight": { @@ -750,9 +849,9 @@ "dev": true }, "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", "dev": true, "dependencies": { "has": "^1.0.3" @@ -830,6 +929,18 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -852,9 +963,9 @@ } }, "node_modules/known-css-properties": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.26.0.tgz", - "integrity": "sha512-5FZRzrZzNTBruuurWpvZnvP9pum+fe0HcK8z/ooo+U+Hmp4vtbyp1/QDsqmufirXy4egGzbaH/y2uCZf+6W5Kg==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.27.0.tgz", + "integrity": "sha512-uMCj6+hZYDoffuvAJjFAPz56E9uoowFHmTkqRtRq5WyC5Q6Cu/fTZKNQpX/RbzChBYLLl3lo8CjFZBAZXq9qFg==", "dev": true }, "node_modules/lines-and-columns": { @@ -864,15 +975,18 @@ "dev": true }, "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "dependencies": { - "p-locate": "^4.1.0" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lodash.truncate": { @@ -915,27 +1029,33 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true + }, "node_modules/meow": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", - "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", + "version": "10.1.5", + "resolved": "https://registry.npmjs.org/meow/-/meow-10.1.5.tgz", + "integrity": "sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==", "dev": true, "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize": "^1.2.0", + "@types/minimist": "^1.2.2", + "camelcase-keys": "^7.0.0", + "decamelize": "^5.0.0", "decamelize-keys": "^1.1.0", "hard-rejection": "^2.1.0", "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" + "normalize-package-data": "^3.0.2", + "read-pkg-up": "^8.0.0", + "redent": "^4.0.0", + "trim-newlines": "^4.0.2", + "type-fest": "^1.2.2", + "yargs-parser": "^20.2.9" }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -1005,10 +1125,16 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -1050,39 +1176,33 @@ } }, "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "dependencies": { - "p-try": "^2.0.0" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "dependencies": { - "p-limit": "^2.2.0" + "p-limit": "^3.0.2" }, "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/parent-module": { @@ -1133,12 +1253,6 @@ "node": ">=0.10.0" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -1167,9 +1281,9 @@ } }, "node_modules/postcss": { - "version": "8.4.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", - "integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==", + "version": "8.4.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.26.tgz", + "integrity": "sha512-jrXHFF8iTloAenySjM/ob3gSj7pCu0Ji49hnjqzsgSRa50hkWCKD0HQ+gMNJkW38jBI68MpAAg7ZWwHwX8NMMw==", "dev": true, "funding": [ { @@ -1179,10 +1293,14 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "nanoid": "^3.3.4", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -1190,12 +1308,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss-media-query-parser": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", - "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", - "dev": true - }, "node_modules/postcss-resolve-nested-selector": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", @@ -1219,9 +1331,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", - "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -1238,15 +1350,15 @@ "dev": true }, "node_modules/prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz", + "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==", "dev": true, "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" @@ -1282,102 +1394,66 @@ ] }, "node_modules/quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", "dev": true, "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg/node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "node_modules/read-pkg": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-6.0.0.tgz", + "integrity": "sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==", "dev": true, "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/read-pkg/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true, + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^3.0.2", + "parse-json": "^5.2.0", + "type-fest": "^1.0.1" + }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-8.0.0.tgz", + "integrity": "sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==", + "dev": true, + "dependencies": { + "find-up": "^5.0.0", + "read-pkg": "^6.0.0", + "type-fest": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-4.0.0.tgz", + "integrity": "sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==", "dev": true, "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" + "indent-string": "^5.0.0", + "strip-indent": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/require-from-string": { @@ -1389,23 +1465,6 @@ "node": ">=0.10.0" } }, - "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -1464,9 +1523,9 @@ } }, "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -1479,10 +1538,16 @@ } }, "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz", + "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/slash": { "version": "3.0.0", @@ -1553,9 +1618,9 @@ } }, "node_modules/spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "dev": true, "dependencies": { "spdx-expression-parse": "^3.0.0", @@ -1579,9 +1644,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", - "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", + "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", "dev": true }, "node_modules/string-width": { @@ -1611,15 +1676,18 @@ } }, "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.0.0.tgz", + "integrity": "sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==", "dev": true, "dependencies": { - "min-indent": "^1.0.0" + "min-indent": "^1.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/style-search": { @@ -1629,96 +1697,88 @@ "dev": true }, "node_modules/stylelint": { - "version": "14.14.1", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.14.1.tgz", - "integrity": "sha512-Jnftu+lSD8cSpcV/+Z2nfgfgFpTIS1FcujezXPngtoIQ6wtwutL22MsNE0dJuMiM1h1790g2qIjAyUZCMrX4sw==", + "version": "15.10.2", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.10.2.tgz", + "integrity": "sha512-UxqSb3hB74g4DTO45QhUHkJMjKKU//lNUAOWyvPBVPZbCknJ5HjOWWZo+UDuhHa9FLeVdHBZXxu43eXkjyIPWg==", "dev": true, "dependencies": { - "@csstools/selector-specificity": "^2.0.2", + "@csstools/css-parser-algorithms": "^2.3.0", + "@csstools/css-tokenizer": "^2.1.1", + "@csstools/media-query-list-parser": "^2.1.2", + "@csstools/selector-specificity": "^3.0.0", "balanced-match": "^2.0.0", "colord": "^2.9.3", - "cosmiconfig": "^7.0.1", - "css-functions-list": "^3.1.0", + "cosmiconfig": "^8.2.0", + "css-functions-list": "^3.2.0", + "css-tree": "^2.3.1", "debug": "^4.3.4", - "fast-glob": "^3.2.12", + "fast-glob": "^3.3.0", "fastest-levenshtein": "^1.0.16", "file-entry-cache": "^6.0.1", "global-modules": "^2.0.0", "globby": "^11.1.0", "globjoin": "^0.1.4", - "html-tags": "^3.2.0", - "ignore": "^5.2.0", + "html-tags": "^3.3.1", + "ignore": "^5.2.4", "import-lazy": "^4.0.0", "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", - "known-css-properties": "^0.26.0", + "known-css-properties": "^0.27.0", "mathml-tag-names": "^2.1.3", - "meow": "^9.0.0", + "meow": "^10.1.5", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "picocolors": "^1.0.0", - "postcss": "^8.4.18", - "postcss-media-query-parser": "^0.2.3", + "postcss": "^8.4.25", "postcss-resolve-nested-selector": "^0.1.1", "postcss-safe-parser": "^6.0.0", - "postcss-selector-parser": "^6.0.10", + "postcss-selector-parser": "^6.0.13", "postcss-value-parser": "^4.2.0", "resolve-from": "^5.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "style-search": "^0.1.0", - "supports-hyperlinks": "^2.3.0", + "supports-hyperlinks": "^3.0.0", "svg-tags": "^1.0.0", "table": "^6.8.1", - "v8-compile-cache": "^2.3.0", - "write-file-atomic": "^4.0.2" + "write-file-atomic": "^5.0.1" }, "bin": { - "stylelint": "bin/stylelint.js" + "stylelint": "bin/stylelint.mjs" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": "^14.13.1 || >=16.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/stylelint" } }, - "node_modules/stylelint-config-prettier": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/stylelint-config-prettier/-/stylelint-config-prettier-9.0.3.tgz", - "integrity": "sha512-5n9gUDp/n5tTMCq1GLqSpA30w2sqWITSSEiAWQlpxkKGAUbjcemQ0nbkRvRUa0B1LgD3+hCvdL7B1eTxy1QHJg==", - "dev": true, - "bin": { - "stylelint-config-prettier": "bin/check.js", - "stylelint-config-prettier-check": "bin/check.js" - }, - "engines": { - "node": ">= 12" - }, - "peerDependencies": { - "stylelint": ">=11.0.0" - } - }, "node_modules/stylelint-config-recommended": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-9.0.0.tgz", - "integrity": "sha512-9YQSrJq4NvvRuTbzDsWX3rrFOzOlYBmZP+o513BJN/yfEmGSr0AxdvrWs0P/ilSpVV/wisamAHu5XSk8Rcf4CQ==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-13.0.0.tgz", + "integrity": "sha512-EH+yRj6h3GAe/fRiyaoO2F9l9Tgg50AOFhaszyfov9v6ayXJ1IkSHwTxd7lB48FmOeSGDPLjatjO11fJpmarkQ==", "dev": true, + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, "peerDependencies": { - "stylelint": "^14.10.0" + "stylelint": "^15.10.0" } }, "node_modules/stylelint-config-standard": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-29.0.0.tgz", - "integrity": "sha512-uy8tZLbfq6ZrXy4JKu3W+7lYLgRQBxYTUUB88vPgQ+ZzAxdrvcaSUW9hOMNLYBnwH+9Kkj19M2DHdZ4gKwI7tg==", + "version": "34.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-34.0.0.tgz", + "integrity": "sha512-u0VSZnVyW9VSryBG2LSO+OQTjN7zF9XJaAJRX/4EwkmU0R2jYwmBSN10acqZisDitS0CLiEiGjX7+Hrq8TAhfQ==", "dev": true, "dependencies": { - "stylelint-config-recommended": "^9.0.0" + "stylelint-config-recommended": "^13.0.0" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" }, "peerDependencies": { - "stylelint": "^14.14.0" + "stylelint": "^15.10.0" } }, "node_modules/supports-color": { @@ -1734,16 +1794,16 @@ } }, "node_modules/supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz", + "integrity": "sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==", "dev": true, "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" }, "engines": { - "node": ">=8" + "node": ">=14.18" } }, "node_modules/supports-hyperlinks/node_modules/has-flag": { @@ -1767,18 +1827,6 @@ "node": ">=8" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/svg-tags": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", @@ -1814,18 +1862,21 @@ } }, "node_modules/trim-newlines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-4.1.1.tgz", + "integrity": "sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==", "dev": true, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", "dev": true, "engines": { "node": ">=10" @@ -1849,12 +1900,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -1884,16 +1929,16 @@ "dev": true }, "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", "dev": true, "dependencies": { "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" + "signal-exit": "^4.0.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/yallist": { @@ -1902,15 +1947,6 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/yargs-parser": { "version": "20.2.9", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", @@ -1919,1425 +1955,18 @@ "engines": { "node": ">=10" } - } - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "requires": { - "@babel/highlight": "^7.18.6" - } }, - "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", - "dev": true - }, - "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@csstools/selector-specificity": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz", - "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==", - "dev": true, - "requires": {} - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@types/minimist": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", - "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", - "dev": true - }, - "@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", - "dev": true - }, - "@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true - }, - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "dev": true - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, - "balanced-match": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", - "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - }, - "dependencies": { - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - } - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "cosmiconfig": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", - "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", - "dev": true, - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - } - }, - "css-functions-list": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.1.0.tgz", - "integrity": "sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w==", - "dev": true - }, - "cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true - }, - "decamelize-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", - "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", - "dev": true, - "requires": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "dependencies": { - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", - "dev": true - } - } - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - } - }, - "fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true - }, - "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "dev": true, - "requires": { - "global-prefix": "^3.0.0" - } - }, - "global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "dev": true, - "requires": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - } - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, - "globjoin": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", - "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", - "dev": true - }, - "hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "html-tags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz", - "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==", - "dev": true - }, - "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - } - } - }, - "import-lazy": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", - "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", - "dev": true - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", - "dev": true - }, - "is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "known-css-properties": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.26.0.tgz", - "integrity": "sha512-5FZRzrZzNTBruuurWpvZnvP9pum+fe0HcK8z/ooo+U+Hmp4vtbyp1/QDsqmufirXy4egGzbaH/y2uCZf+6W5Kg==", - "dev": true - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", - "dev": true - }, - "mathml-tag-names": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", - "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", - "dev": true - }, - "meow": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", - "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", - "dev": true, - "requires": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize": "^1.2.0", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - } - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "dev": true - }, - "normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dev": true, - "requires": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "postcss": { - "version": "8.4.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", - "integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==", - "dev": true, - "requires": { - "nanoid": "^3.3.4", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - } - }, - "postcss-media-query-parser": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", - "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", - "dev": true - }, - "postcss-resolve-nested-selector": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", - "integrity": "sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==", - "dev": true - }, - "postcss-safe-parser": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", - "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", - "dev": true, - "requires": {} - }, - "postcss-selector-parser": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", - "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", - "dev": true, - "requires": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - } - }, - "postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true - }, - "prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", - "dev": true - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "dev": true - }, - "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true - } - } - }, - "read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "dependencies": { - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - } - } - }, - "redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "requires": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - } - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true - }, - "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dev": true, - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } - } - }, - "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", - "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "requires": { - "min-indent": "^1.0.0" - } - }, - "style-search": { + "node_modules/yocto-queue": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", - "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==", - "dev": true - }, - "stylelint": { - "version": "14.14.1", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.14.1.tgz", - "integrity": "sha512-Jnftu+lSD8cSpcV/+Z2nfgfgFpTIS1FcujezXPngtoIQ6wtwutL22MsNE0dJuMiM1h1790g2qIjAyUZCMrX4sw==", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, - "requires": { - "@csstools/selector-specificity": "^2.0.2", - "balanced-match": "^2.0.0", - "colord": "^2.9.3", - "cosmiconfig": "^7.0.1", - "css-functions-list": "^3.1.0", - "debug": "^4.3.4", - "fast-glob": "^3.2.12", - "fastest-levenshtein": "^1.0.16", - "file-entry-cache": "^6.0.1", - "global-modules": "^2.0.0", - "globby": "^11.1.0", - "globjoin": "^0.1.4", - "html-tags": "^3.2.0", - "ignore": "^5.2.0", - "import-lazy": "^4.0.0", - "imurmurhash": "^0.1.4", - "is-plain-object": "^5.0.0", - "known-css-properties": "^0.26.0", - "mathml-tag-names": "^2.1.3", - "meow": "^9.0.0", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.18", - "postcss-media-query-parser": "^0.2.3", - "postcss-resolve-nested-selector": "^0.1.1", - "postcss-safe-parser": "^6.0.0", - "postcss-selector-parser": "^6.0.10", - "postcss-value-parser": "^4.2.0", - "resolve-from": "^5.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "style-search": "^0.1.0", - "supports-hyperlinks": "^2.3.0", - "svg-tags": "^1.0.0", - "table": "^6.8.1", - "v8-compile-cache": "^2.3.0", - "write-file-atomic": "^4.0.2" - } - }, - "stylelint-config-prettier": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/stylelint-config-prettier/-/stylelint-config-prettier-9.0.3.tgz", - "integrity": "sha512-5n9gUDp/n5tTMCq1GLqSpA30w2sqWITSSEiAWQlpxkKGAUbjcemQ0nbkRvRUa0B1LgD3+hCvdL7B1eTxy1QHJg==", - "dev": true, - "requires": {} - }, - "stylelint-config-recommended": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-9.0.0.tgz", - "integrity": "sha512-9YQSrJq4NvvRuTbzDsWX3rrFOzOlYBmZP+o513BJN/yfEmGSr0AxdvrWs0P/ilSpVV/wisamAHu5XSk8Rcf4CQ==", - "dev": true, - "requires": {} - }, - "stylelint-config-standard": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-29.0.0.tgz", - "integrity": "sha512-uy8tZLbfq6ZrXy4JKu3W+7lYLgRQBxYTUUB88vPgQ+ZzAxdrvcaSUW9hOMNLYBnwH+9Kkj19M2DHdZ4gKwI7tg==", - "dev": true, - "requires": { - "stylelint-config-recommended": "^9.0.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "dev": true, - "requires": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" + "engines": { + "node": ">=10" }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "svg-tags": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", - "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", - "dev": true - }, - "table": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", - "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", - "dev": true, - "requires": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "trim-newlines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", - "dev": true - }, - "type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", - "dev": true - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true - }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true } } } diff --git a/python/web/package.json b/python/web/package.json index 56ef0a57..5ff05b21 100644 --- a/python/web/package.json +++ b/python/web/package.json @@ -1,8 +1,7 @@ { "devDependencies": { - "prettier": "2.7.1", - "stylelint": "^14.14.1", - "stylelint-config-prettier": "^9.0.3", - "stylelint-config-standard": "^29.0.0" + "prettier": "3.0.0", + "stylelint": "^15.10.2", + "stylelint-config-standard": "^34.0.0" } } diff --git a/python/web/src/static/themes/classic/style.css b/python/web/src/static/themes/classic/style.css index 23b4ab88..b8ed990d 100644 --- a/python/web/src/static/themes/classic/style.css +++ b/python/web/src/static/themes/classic/style.css @@ -13,7 +13,9 @@ form { display: inline; } -table, tr, td { +table, +tr, +td { border: 1px solid black; border-collapse: collapse; margin: none; @@ -48,7 +50,7 @@ div.footer { } div.footer div.theme-change-hint { - margin-bottom: 15px; + margin-bottom: 15px; } div.logged-in { @@ -100,7 +102,7 @@ td.inactive { background-color: tan; } -ul.inline_list { +ul.inline-list { list-style: none; } @@ -113,7 +115,8 @@ summary.filename { text-decoration: underline; } -.dropzone, .dropzone * { +.dropzone, +.dropzone * { box-sizing: border-box; } @@ -134,7 +137,7 @@ summary.filename { position: relative; display: inline-block; width: 180px; - margin: .5em; + margin: 0.5em; } .dropzone .dz-preview .dz-progress { @@ -159,7 +162,8 @@ summary.filename { display: block; } -.dropzone .dz-preview .dz-error-mark, .dropzone .dz-preview .dz-success-mark { +.dropzone .dz-preview .dz-error-mark, +.dropzone .dz-preview .dz-success-mark { position: absolute; display: none; top: 30px; From 8089bb93f37a3cab161dc78d9732a2859269ad4a Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Mon, 31 Jul 2023 12:12:53 -0700 Subject: [PATCH 17/87] Launch python client services only after network-online target (#1197) --- python/ctrlboard/service-infra/piscsi-ctrlboard.service | 2 +- python/oled/service-infra/piscsi-oled.service | 2 +- python/web/service-infra/piscsi-web.service | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/python/ctrlboard/service-infra/piscsi-ctrlboard.service b/python/ctrlboard/service-infra/piscsi-ctrlboard.service index 3eb9de72..0c336d40 100644 --- a/python/ctrlboard/service-infra/piscsi-ctrlboard.service +++ b/python/ctrlboard/service-infra/piscsi-ctrlboard.service @@ -1,6 +1,6 @@ [Unit] Description=PiSCSI Control Board service -After=network.target piscsi.service +After=network-online.target piscsi.service [Service] Type=simple diff --git a/python/oled/service-infra/piscsi-oled.service b/python/oled/service-infra/piscsi-oled.service index a339e6a2..9be68433 100644 --- a/python/oled/service-infra/piscsi-oled.service +++ b/python/oled/service-infra/piscsi-oled.service @@ -1,6 +1,6 @@ [Unit] Description=PiSCSI-OLED Monitor service -After=network.target piscsi.service +After=network-online.target piscsi.service [Service] Type=simple diff --git a/python/web/service-infra/piscsi-web.service b/python/web/service-infra/piscsi-web.service index cf7c8b85..3f4d7ed4 100644 --- a/python/web/service-infra/piscsi-web.service +++ b/python/web/service-infra/piscsi-web.service @@ -1,6 +1,6 @@ [Unit] Description=PiSCSI-Web service -After=network.target +After=network-online.target piscsi.service [Service] Type=simple From 3b6822d7c89a6b2f66cb0fed5d3d1627557d5810 Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Mon, 31 Jul 2023 12:13:23 -0700 Subject: [PATCH 18/87] easyinstall: Dynamically detect an available network i/f, and abort if none is found (#1193) * Dynamically detect an available network i/f, and abort if none is found * Network i/f fallback for headless mode * Split piscsi installation and system service configuration --- easyinstall.sh | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/easyinstall.sh b/easyinstall.sh index 53c10ce2..4a980fef 100755 --- a/easyinstall.sh +++ b/easyinstall.sh @@ -189,8 +189,10 @@ function installPiscsi() { # install sudo make install CONNECT_TYPE="$CONNECT_TYPE" Date: Fri, 1 Sep 2023 02:23:12 -0400 Subject: [PATCH 19/87] Add web UI notification for under voltage/throttling events (#1203) --- README.md | 2 +- python/common/requirements.txt | 1 + python/common/src/piscsi/return_codes.py | 8 ++++ python/common/src/piscsi/sys_cmds.py | 36 ++++++++++++++++ python/web/mock/bin/vcgencmd | 23 ++++++++++ python/web/requirements.txt | 3 +- python/web/src/return_code_mapper.py | 16 +++++++ python/web/src/settings.py | 16 +++++++ .../web/src/static/themes/classic/style.css | 33 ++++++++++++++ python/web/src/static/themes/modern/style.css | 43 +++++++++++++++++++ python/web/src/templates/base.html | 12 ++++++ python/web/src/web.py | 7 +++ python/web/tests/api/test_settings.py | 9 ++++ 13 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 python/web/mock/bin/vcgencmd diff --git a/README.md b/README.md index f58de439..3747d6f4 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Please check out the full story with much more detail on the [wiki](https://gith # How do I contribute? PiSCSI is using the Gitflow Workflow. A quick overview: -- The *master* branch should always reflect the contents of the last stable release +- The *main* 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 .. 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. diff --git a/python/common/requirements.txt b/python/common/requirements.txt index 45ccd776..90a99b04 100644 --- a/python/common/requirements.txt +++ b/python/common/requirements.txt @@ -1,2 +1,3 @@ protobuf==3.19.5 requests==2.31.0 +vcgencmd==0.1.1 diff --git a/python/common/src/piscsi/return_codes.py b/python/common/src/piscsi/return_codes.py index cfd8265c..25cd2392 100644 --- a/python/common/src/piscsi/return_codes.py +++ b/python/common/src/piscsi/return_codes.py @@ -27,3 +27,11 @@ class ReturnCodes: EXTRACTIMAGE_NO_FILES_SPECIFIED = 91 EXTRACTIMAGE_NO_FILES_EXTRACTED = 92 EXTRACTIMAGE_COMMAND_ERROR = 93 + UNDER_VOLTAGE_DETECTED = 100 + ARM_FREQUENCY_CAPPED = 101 + CURRENTLY_THROTTLED = 102 + SOFT_TEMPERATURE_LIMIT_ACTIVE = 103 + UNDER_VOLTAGE_HAS_OCCURRED = 116 + ARM_FREQUENCY_CAPPING_HAS_OCCURRED = 117 + THROTTLING_HAS_OCCURRED = 118 + SOFT_TEMPERATURE_LIMIT_HAS_OCCURRED = 119 diff --git a/python/common/src/piscsi/sys_cmds.py b/python/common/src/piscsi/sys_cmds.py index c21cc8f4..cb43360a 100644 --- a/python/common/src/piscsi/sys_cmds.py +++ b/python/common/src/piscsi/sys_cmds.py @@ -9,7 +9,9 @@ from re import findall, match from socket import socket, gethostname, AF_INET, SOCK_DGRAM from pathlib import Path from platform import uname +from vcgencmd import Vcgencmd +from piscsi.return_codes import ReturnCodes from piscsi.common_settings import SHELL_ERROR @@ -263,3 +265,37 @@ class SysCmds: return process.returncode, process.stdout.decode("utf-8") return process.returncode, process.stderr.decode("utf-8") + + @staticmethod + def get_throttled(enabled_modes, test_modes): + """ + Takes (list) enabled_modes & (list) test_modes parameters & returns a + tuple of (str) category & (str) message. + + enabled_modes is a list of modes that will be enabled for display if + they're triggered. test_modes works similarly to enabled_mode but will + ALWAYS display the modes listed for troubleshooting styling. + """ + vcgcmd = Vcgencmd() + t_states = vcgcmd.get_throttled()['breakdown'] + matched_states = [] + + state_msgs = { + "0": ("error", ReturnCodes.UNDER_VOLTAGE_DETECTED), + "1": ("warning", ReturnCodes.ARM_FREQUENCY_CAPPED), + "2": ("error", ReturnCodes.CURRENTLY_THROTTLED), + "3": ("warning", ReturnCodes.SOFT_TEMPERATURE_LIMIT_ACTIVE), + "16": ("warning", ReturnCodes.UNDER_VOLTAGE_HAS_OCCURRED), + "17": ("warning", ReturnCodes.ARM_FREQUENCY_CAPPING_HAS_OCCURRED), + "18": ("warning", ReturnCodes.THROTTLING_HAS_OCCURRED), + "19": ("warning", ReturnCodes.SOFT_TEMPERATURE_LIMIT_HAS_OCCURRED), + } + + for k in t_states: + if t_states[k] and k in enabled_modes: + matched_states.append(state_msgs[k]) + + for t in test_modes: + matched_states.append(state_msgs[t]) + + return matched_states diff --git a/python/web/mock/bin/vcgencmd b/python/web/mock/bin/vcgencmd new file mode 100644 index 00000000..ce21e8e2 --- /dev/null +++ b/python/web/mock/bin/vcgencmd @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +# Info: https://www.raspberrypi.com/documentation/computers/os.html#vcgencmd +# +# Bit Hex value Meaning +# ----- ----------- ------------------------ +# 0 0x1 Under-voltage detected +# 1 0x2 Arm frequency capped +# 2 0x4 Currently throttled +# 3 0x8 Soft temperature limit active +# 16 0x10000 Under-voltage has occurred +# 17 0x20000 Arm frequency capping has occurred +# 18 0x40000 Throttling has occurred +# 19 0x80000 Soft temperature limit has occurred + +if [[ "$1" == "get_throttled" ]] +then + # Return 'Under-voltage detected' & 'Under-voltage has occurred' + echo "throttled=0x10001" +fi + +echo "Mock does not recognize: $0 $@" +exit 1 \ No newline at end of file diff --git a/python/web/requirements.txt b/python/web/requirements.txt index d5e01521..60758b1c 100644 --- a/python/web/requirements.txt +++ b/python/web/requirements.txt @@ -5,4 +5,5 @@ protobuf==3.20.2 requests==2.31.0 simplepam==0.1.5 flask_babel==2.0.0 -ua-parser==0.16.1 \ No newline at end of file +ua-parser==0.16.1 +vcgencmd==0.1.1 \ No newline at end of file diff --git a/python/web/src/return_code_mapper.py b/python/web/src/return_code_mapper.py index f6c6e273..9695a1dd 100644 --- a/python/web/src/return_code_mapper.py +++ b/python/web/src/return_code_mapper.py @@ -50,6 +50,22 @@ class ReturnCodeMapper: _("No files were extracted (existing files are skipped)"), ReturnCodes.EXTRACTIMAGE_COMMAND_ERROR: _("Unable to extract archive: %(error)s"), + ReturnCodes.UNDER_VOLTAGE_DETECTED: + _("Under voltage detected - Make sure to use a proper power source (2.5+ amps)."), + ReturnCodes.ARM_FREQUENCY_CAPPED: + _("ARM frequency capped - Ensure proper airflow/cooling."), + ReturnCodes.CURRENTLY_THROTTLED: + _("Currently throttled - Make sure to use a proper power source (2.5+ amps)."), + ReturnCodes.SOFT_TEMPERATURE_LIMIT_ACTIVE: + _("Soft-temperature limit active - Ensure proper airflow/cooling."), + ReturnCodes.UNDER_VOLTAGE_HAS_OCCURRED: + _("Under voltage has occurred since last reboot. Make sure to use a proper power source (2.5+ amps)."), + ReturnCodes.ARM_FREQUENCY_CAPPING_HAS_OCCURRED: + _("ARM frequency capping has occurred since last reboot. Ensure proper airflow/cooling."), + ReturnCodes.THROTTLING_HAS_OCCURRED: + _("Throttling has occurred since the last reboot. Make sure to use a proper power source (2.5+ amps)."), + ReturnCodes.SOFT_TEMPERATURE_LIMIT_HAS_OCCURRED: + _("Soft temperature limit has occurred since last reboot. Ensure proper airflow/cooling."), } # fmt: on diff --git a/python/web/src/settings.py b/python/web/src/settings.py index 3a2c4d73..7bcc99c3 100644 --- a/python/web/src/settings.py +++ b/python/web/src/settings.py @@ -31,3 +31,19 @@ TEMPLATE_THEME_DEFAULT = "modern" # Fallback theme for older browsers TEMPLATE_THEME_LEGACY = "classic" + +# Enable throttle notifications +# +# Available modes: +# "0": "Under-voltage detected" +# "1": "Arm frequency capped" +# "2": "Currently throttled" +# "3": "Soft temperature limit active" +# "16": "Under-voltage has occurred" +# "17": "Arm frequency capping has occurred" +# "18": "Throttling has occurred" +# "19": "Soft temperature limit has occurred" +THROTTLE_NOTIFY_MODES = ["0", "16"] +# Include a list of modes to be shown ALL THE TIME to be used for styling +# and formatting. +THROTTLE_TEST_MODES = [] diff --git a/python/web/src/static/themes/classic/style.css b/python/web/src/static/themes/classic/style.css index b8ed990d..c5da7eed 100644 --- a/python/web/src/static/themes/classic/style.css +++ b/python/web/src/static/themes/classic/style.css @@ -172,3 +172,36 @@ summary.filename { left: 50%; margin-left: -27px; } + +div.throttle_notice > div { + display: grid; + align-items: center; + background-color: #efefef; + background-repeat: no-repeat; + background-position: 1rem center; + background-size: 1rem; + font-size: x-small; + font-weight: bold; + justify-content: center; +} + +div.throttle_notice > div.error { + background-color: #dc3545; + align-items: center; +} + +div.throttle_notice > div.warning { + background-color: #ffc107; + align-items: center; +} + +div.throttle_notice > div a { + color: black; + text-decoration: none; +} + +div.throttle_notice > div a:hover { + text-decoration: underline; +} + + diff --git a/python/web/src/static/themes/modern/style.css b/python/web/src/static/themes/modern/style.css index 361f5bed..f6c55871 100644 --- a/python/web/src/static/themes/modern/style.css +++ b/python/web/src/static/themes/modern/style.css @@ -470,6 +470,49 @@ div.footer div.theme-change-hint a { color: yellow; } +/* + ------------------------------------------------------------------------------ + Throttle messages + ------------------------------------------------------------------------------ + */ + +div.throttle_notice > div { + display: grid; + align-items: center; + background-color: #efefef; + background-repeat: no-repeat; + background-position: 1rem center; + background-size: 1rem; + font-size: x-small; + font-weight: bold; +} + +div.throttle_notice > div.error { + background-color: var(--danger); + background-image: url("icons/error.svg"); + color: #fff; + align-items: center; +} + +div.throttle_notice > div.warning { + background-color: var(--warning); + background-image: url("icons/warning.svg"); + align-items: center; +} + +div.throttle_notice > div > span.message { + padding-left: 3rem; +} + +div.throttle_notice > div a { + color: black; + text-decoration: none; +} + +div.throttle_notice > div a:hover { + text-decoration: underline; +} + /* ------------------------------------------------------------------------------ Flash messages diff --git a/python/web/src/templates/base.html b/python/web/src/templates/base.html index 25331690..5bca87b2 100644 --- a/python/web/src/templates/base.html +++ b/python/web/src/templates/base.html @@ -73,6 +73,18 @@ +
+ {% if (env["throttle_status"]|length > 0) %} + {% for category, response in env["throttle_status"] %} + + {% endfor %} + {% endif %} +
+
{% if get_flashed_messages(): %} {% for category, message in get_flashed_messages(with_categories=true) %} diff --git a/python/web/src/web.py b/python/web/src/web.py index cc0a1398..732ef0fe 100644 --- a/python/web/src/web.py +++ b/python/web/src/web.py @@ -69,6 +69,8 @@ from settings import ( TEMPLATE_THEMES, TEMPLATE_THEME_DEFAULT, TEMPLATE_THEME_LEGACY, + THROTTLE_NOTIFY_MODES, + THROTTLE_TEST_MODES, ) @@ -88,6 +90,9 @@ def get_env_info(): else: username = None + throttled_statuses = sys_cmd.get_throttled( + THROTTLE_NOTIFY_MODES, THROTTLE_TEST_MODES) + return { "running_env": sys_cmd.running_env(), "username": username, @@ -106,6 +111,8 @@ def get_env_info(): "cd_suffixes": tuple(server_info["sccd"]), "rm_suffixes": tuple(server_info["scrm"]), "mo_suffixes": tuple(server_info["scmo"]), + "throttle_status": + [(s[0], ReturnCodeMapper.add_msg({"return_code":s[1]})) for s in throttled_statuses], } diff --git a/python/web/tests/api/test_settings.py b/python/web/tests/api/test_settings.py index 3875fd9e..1c51205a 100644 --- a/python/web/tests/api/test_settings.py +++ b/python/web/tests/api/test_settings.py @@ -286,3 +286,12 @@ def test_rename_system(env, http_client): response_data = response.json() assert response_data["data"]["system_name"] == old_name + + +def test_throttle_notification(http_client): + response = http_client.get("/") + response_data = response.json() + + assert response.status_code == 200 + assert response_data["status"] == STATUS_SUCCESS + assert "Under voltage detected" in response_data["data"] From 74eef6f9cc318f3d3a970253fa1c9b413d451b75 Mon Sep 17 00:00:00 2001 From: Tony Kuker <34318535+akuker@users.noreply.github.com> Date: Sat, 2 Sep 2023 18:36:45 -0500 Subject: [PATCH 20/87] Enable actions on pull request (#1209) * Update revision for release * Create sonarcloud.yml * Update sonarcloud.yml * Update sonarcloud.yml * Update sonarcloud.yml * Update sonarcloud.yml * Delete sonarcloud.yml * Update revision number for release * Remove SonarCloud cache setup as it is now offered by default (#1135) No need to configure the cache anymore, SonarCloud now has an automatic analysis caching. See https://docs.sonarcloud.io/advanced-setup/languages/c-c-objective-c/#analysis-cache. * Revert "Remove SonarCloud cache setup as it is now offered by default (#1135)" (#1144) This reverts commit 3ad668cfd1bc84c80a6ea0edb024bee08264bd37. * Update web.yml * Update cpp.yml * Update web.yml * Update build_code.yml * Update build_code.yml * Update cpp.yml * Update web.yml --------- Co-authored-by: Tony Kuker Co-authored-by: Massimo Paladin --- .github/workflows/build_code.yml | 13 +++++++++++++ .github/workflows/cpp.yml | 12 ++++++++++++ .github/workflows/web.yml | 14 ++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/.github/workflows/build_code.yml b/.github/workflows/build_code.yml index 0970e9f5..e0c38852 100644 --- a/.github/workflows/build_code.yml +++ b/.github/workflows/build_code.yml @@ -7,6 +7,19 @@ on: - 'cpp/**' - '.github/workflows/build_code.yml' - '.github/workflows/arm_cross_compile.yml' + pull_request: + paths: + - 'cpp/**' + - '.github/workflows/build_code.yml' + - '.github/workflows/arm_cross_compile.yml' + types: + - assigned + - opened + - synchronize + - reopened + branches: + - 'develop' + - 'main' jobs: fullspec: diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml index 33a84537..450c1695 100644 --- a/.github/workflows/cpp.yml +++ b/.github/workflows/cpp.yml @@ -6,6 +6,18 @@ on: paths: - 'cpp/**' - '.github/workflows/cpp.yml' + pull_request: + paths: + - 'cpp/**' + - '.github/workflows/cpp.yml' + types: + - assigned + - opened + - synchronize + - reopened + branches: + - 'develop' + - 'main' env: APT_PACKAGES: libspdlog-dev libpcap-dev libevdev2 libev-dev protobuf-compiler libgtest-dev libgmock-dev diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml index 4b31adad..d22c4906 100644 --- a/.github/workflows/web.yml +++ b/.github/workflows/web.yml @@ -8,6 +8,20 @@ on: - 'python/common/**' - '.github/workflows/web.yml' - 'easyinstall.sh' + pull_request: + paths: + - 'python/web/**' + - 'python/common/**' + - '.github/workflows/web.yml' + - 'easyinstall.sh' + types: + - assigned + - opened + - synchronize + - reopened + branches: + - 'develop' + - 'main' jobs: backend_checks: From b514440957eb149ae4f91d7bad0c70107cb8ee81 Mon Sep 17 00:00:00 2001 From: Benjamin Krein Date: Mon, 4 Sep 2023 11:08:42 -0400 Subject: [PATCH 21/87] Fix test failures in low-voltage PR (#1207) * fix lint checks in web python * fix lint checks in common python * rework to pass tests in docker * fix css style lint * fix css style syntax --- python/common/src/piscsi/sys_cmds.py | 48 +++++++++++-------- python/web/requirements-dev.txt | 1 + python/web/src/return_code_mapper.py | 23 +++++---- python/web/src/settings.py | 2 +- .../web/src/static/themes/classic/style.css | 12 ++--- python/web/src/static/themes/modern/style.css | 14 +++--- python/web/src/templates/base.html | 5 +- python/web/src/web.py | 8 ++-- python/web/tests/api/test_settings.py | 9 ---- 9 files changed, 63 insertions(+), 59 deletions(-) diff --git a/python/common/src/piscsi/sys_cmds.py b/python/common/src/piscsi/sys_cmds.py index cb43360a..37c85f4a 100644 --- a/python/common/src/piscsi/sys_cmds.py +++ b/python/common/src/piscsi/sys_cmds.py @@ -3,13 +3,18 @@ Module with methods that interact with the Pi system """ import subprocess import logging +import sys from subprocess import run, CalledProcessError from shutil import disk_usage from re import findall, match from socket import socket, gethostname, AF_INET, SOCK_DGRAM from pathlib import Path from platform import uname -from vcgencmd import Vcgencmd + +try: + from vcgencmd import Vcgencmd +except ImportError: + pass from piscsi.return_codes import ReturnCodes from piscsi.common_settings import SHELL_ERROR @@ -276,26 +281,29 @@ class SysCmds: they're triggered. test_modes works similarly to enabled_mode but will ALWAYS display the modes listed for troubleshooting styling. """ - vcgcmd = Vcgencmd() - t_states = vcgcmd.get_throttled()['breakdown'] - matched_states = [] + if "vcgencmd" in sys.modules: + vcgcmd = Vcgencmd() + t_states = vcgcmd.get_throttled()["breakdown"] + matched_states = [] - state_msgs = { - "0": ("error", ReturnCodes.UNDER_VOLTAGE_DETECTED), - "1": ("warning", ReturnCodes.ARM_FREQUENCY_CAPPED), - "2": ("error", ReturnCodes.CURRENTLY_THROTTLED), - "3": ("warning", ReturnCodes.SOFT_TEMPERATURE_LIMIT_ACTIVE), - "16": ("warning", ReturnCodes.UNDER_VOLTAGE_HAS_OCCURRED), - "17": ("warning", ReturnCodes.ARM_FREQUENCY_CAPPING_HAS_OCCURRED), - "18": ("warning", ReturnCodes.THROTTLING_HAS_OCCURRED), - "19": ("warning", ReturnCodes.SOFT_TEMPERATURE_LIMIT_HAS_OCCURRED), - } + state_msgs = { + "0": ("error", ReturnCodes.UNDER_VOLTAGE_DETECTED), + "1": ("warning", ReturnCodes.ARM_FREQUENCY_CAPPED), + "2": ("error", ReturnCodes.CURRENTLY_THROTTLED), + "3": ("warning", ReturnCodes.SOFT_TEMPERATURE_LIMIT_ACTIVE), + "16": ("warning", ReturnCodes.UNDER_VOLTAGE_HAS_OCCURRED), + "17": ("warning", ReturnCodes.ARM_FREQUENCY_CAPPING_HAS_OCCURRED), + "18": ("warning", ReturnCodes.THROTTLING_HAS_OCCURRED), + "19": ("warning", ReturnCodes.SOFT_TEMPERATURE_LIMIT_HAS_OCCURRED), + } - for k in t_states: - if t_states[k] and k in enabled_modes: - matched_states.append(state_msgs[k]) + for k in t_states: + if t_states[k] and k in enabled_modes: + matched_states.append(state_msgs[k]) - for t in test_modes: - matched_states.append(state_msgs[t]) + for t in test_modes: + matched_states.append(state_msgs[t]) - return matched_states + return matched_states + else: + return [] diff --git a/python/web/requirements-dev.txt b/python/web/requirements-dev.txt index fd2bb2a1..f65d5f8c 100644 --- a/python/web/requirements-dev.txt +++ b/python/web/requirements-dev.txt @@ -4,3 +4,4 @@ black==22.8.0 flake8==5.0.4 watchdog==2.1.9 requests==2.31.0 +vcgencmd==0.1.1 diff --git a/python/web/src/return_code_mapper.py b/python/web/src/return_code_mapper.py index 9695a1dd..95025d9b 100644 --- a/python/web/src/return_code_mapper.py +++ b/python/web/src/return_code_mapper.py @@ -51,21 +51,28 @@ class ReturnCodeMapper: ReturnCodes.EXTRACTIMAGE_COMMAND_ERROR: _("Unable to extract archive: %(error)s"), ReturnCodes.UNDER_VOLTAGE_DETECTED: - _("Under voltage detected - Make sure to use a proper power source (2.5+ amps)."), + _("Potential instability - Under voltage detected - Make sure to use a sufficient " + "power source (2.5+ amps)."), ReturnCodes.ARM_FREQUENCY_CAPPED: - _("ARM frequency capped - Ensure proper airflow/cooling."), + _("Potential instability - ARM frequency capped - Ensure sufficient airflow/cooling."), ReturnCodes.CURRENTLY_THROTTLED: - _("Currently throttled - Make sure to use a proper power source (2.5+ amps)."), + _("Potential instability - Currently throttled - Make sure to use a sufficient power " + "source (2.5+ amps)."), ReturnCodes.SOFT_TEMPERATURE_LIMIT_ACTIVE: - _("Soft-temperature limit active - Ensure proper airflow/cooling."), + _("Potential instability - Soft-temperature limit active - Ensure sufficient " + "airflow/cooling."), ReturnCodes.UNDER_VOLTAGE_HAS_OCCURRED: - _("Under voltage has occurred since last reboot. Make sure to use a proper power source (2.5+ amps)."), + _("Potential instability - Under voltage has occurred since last reboot. Make sure " + "to use a sufficient power source (2.5+ amps)."), ReturnCodes.ARM_FREQUENCY_CAPPING_HAS_OCCURRED: - _("ARM frequency capping has occurred since last reboot. Ensure proper airflow/cooling."), + _("Potential instability - ARM frequency capping has occurred since last reboot. " + "Ensure sufficient airflow/cooling."), ReturnCodes.THROTTLING_HAS_OCCURRED: - _("Throttling has occurred since the last reboot. Make sure to use a proper power source (2.5+ amps)."), + _("Potential instability - Throttling has occurred since the last reboot. Make sure " + "to use a sufficient power source (2.5+ amps)."), ReturnCodes.SOFT_TEMPERATURE_LIMIT_HAS_OCCURRED: - _("Soft temperature limit has occurred since last reboot. Ensure proper airflow/cooling."), + _("Potential instability - Soft temperature limit has occurred since last reboot. " + "Ensure sufficient airflow/cooling."), } # fmt: on diff --git a/python/web/src/settings.py b/python/web/src/settings.py index 7bcc99c3..5994c719 100644 --- a/python/web/src/settings.py +++ b/python/web/src/settings.py @@ -33,7 +33,7 @@ TEMPLATE_THEME_DEFAULT = "modern" TEMPLATE_THEME_LEGACY = "classic" # Enable throttle notifications -# +# # Available modes: # "0": "Under-voltage detected" # "1": "Arm frequency capped" diff --git a/python/web/src/static/themes/classic/style.css b/python/web/src/static/themes/classic/style.css index c5da7eed..3c8b754a 100644 --- a/python/web/src/static/themes/classic/style.css +++ b/python/web/src/static/themes/classic/style.css @@ -173,7 +173,7 @@ summary.filename { margin-left: -27px; } -div.throttle_notice > div { +div.throttle-notice > div { display: grid; align-items: center; background-color: #efefef; @@ -185,23 +185,21 @@ div.throttle_notice > div { justify-content: center; } -div.throttle_notice > div.error { +div.throttle-notice > div.error { background-color: #dc3545; align-items: center; } -div.throttle_notice > div.warning { +div.throttle-notice > div.warning { background-color: #ffc107; align-items: center; } -div.throttle_notice > div a { +div.throttle-notice > div a { color: black; text-decoration: none; } -div.throttle_notice > div a:hover { +div.throttle-notice > div a:hover { text-decoration: underline; } - - diff --git a/python/web/src/static/themes/modern/style.css b/python/web/src/static/themes/modern/style.css index f6c55871..a21a91ed 100644 --- a/python/web/src/static/themes/modern/style.css +++ b/python/web/src/static/themes/modern/style.css @@ -476,7 +476,7 @@ div.footer div.theme-change-hint a { ------------------------------------------------------------------------------ */ -div.throttle_notice > div { +div.throttle-notice > div { display: grid; align-items: center; background-color: #efefef; @@ -487,29 +487,29 @@ div.throttle_notice > div { font-weight: bold; } -div.throttle_notice > div.error { +div.throttle-notice > div.error { background-color: var(--danger); background-image: url("icons/error.svg"); color: #fff; align-items: center; } -div.throttle_notice > div.warning { +div.throttle-notice > div.warning { background-color: var(--warning); background-image: url("icons/warning.svg"); align-items: center; } -div.throttle_notice > div > span.message { - padding-left: 3rem; +div.throttle-notice > div > span.message { + padding-left: 3rem; } -div.throttle_notice > div a { +div.throttle-notice > div a { color: black; text-decoration: none; } -div.throttle_notice > div a:hover { +div.throttle-notice > div a:hover { text-decoration: underline; } diff --git a/python/web/src/templates/base.html b/python/web/src/templates/base.html index 5bca87b2..1bcb16e4 100644 --- a/python/web/src/templates/base.html +++ b/python/web/src/templates/base.html @@ -73,13 +73,12 @@
-
+
{% if (env["throttle_status"]|length > 0) %} {% for category, response in env["throttle_status"] %}
Potential - instability due to: {{ response['msg'] }} + href="https://www.raspberrypi.com/documentation/computers/configuration.html#undervoltage-warning">{{ response['msg'] }}
{% endfor %} {% endif %} diff --git a/python/web/src/web.py b/python/web/src/web.py index 732ef0fe..a550a95c 100644 --- a/python/web/src/web.py +++ b/python/web/src/web.py @@ -90,8 +90,7 @@ def get_env_info(): else: username = None - throttled_statuses = sys_cmd.get_throttled( - THROTTLE_NOTIFY_MODES, THROTTLE_TEST_MODES) + throttled_statuses = sys_cmd.get_throttled(THROTTLE_NOTIFY_MODES, THROTTLE_TEST_MODES) return { "running_env": sys_cmd.running_env(), @@ -111,8 +110,9 @@ def get_env_info(): "cd_suffixes": tuple(server_info["sccd"]), "rm_suffixes": tuple(server_info["scrm"]), "mo_suffixes": tuple(server_info["scmo"]), - "throttle_status": - [(s[0], ReturnCodeMapper.add_msg({"return_code":s[1]})) for s in throttled_statuses], + "throttle_status": [ + (s[0], ReturnCodeMapper.add_msg({"return_code": s[1]})) for s in throttled_statuses + ], } diff --git a/python/web/tests/api/test_settings.py b/python/web/tests/api/test_settings.py index 1c51205a..3875fd9e 100644 --- a/python/web/tests/api/test_settings.py +++ b/python/web/tests/api/test_settings.py @@ -286,12 +286,3 @@ def test_rename_system(env, http_client): response_data = response.json() assert response_data["data"]["system_name"] == old_name - - -def test_throttle_notification(http_client): - response = http_client.get("/") - response_data = response.json() - - assert response.status_code == 200 - assert response_data["status"] == STATUS_SUCCESS - assert "Under voltage detected" in response_data["data"] From 65c1b194c511188842123091c55918add5c499be Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Sun, 24 Sep 2023 09:30:54 -0700 Subject: [PATCH 22/87] Manage Samba and Netatalk with Webmin (#1217) * Display status of Samba/Netatalk/vsftp in the footer, with links to Webmin module * Add option to install Webmin and the netatalk2 module --- easyinstall.sh | 55 +++++++++++++++++++++++++++--- python/web/src/templates/base.html | 38 +++++++++++++++++---- python/web/src/web.py | 3 ++ 3 files changed, 84 insertions(+), 12 deletions(-) diff --git a/easyinstall.sh b/easyinstall.sh index 4a980fef..2f7f2cd4 100755 --- a/easyinstall.sh +++ b/easyinstall.sh @@ -876,6 +876,8 @@ function installMacproxy { # Installs vsftpd (FTP server) function installFtp() { + echo + echo "Installing packages..." sudo apt-get update && sudo apt-get install vsftpd --assume-yes --no-install-recommends
- {% if env["netatalk_configured"] == 1 %} - {{ _("The AppleShare server is running. No active connections.") }} + {% if env["netatalk_configured"] %} + {{ _("Mac AFP file sharing is enabled.") }} + {% if env["webmin_configured"] %} + + {{ ("Server administration") }} + + {% endif %} + {% else %} + {{ _("Mac AFP file sharing is disabled.") }} {% endif %} - {% if env["netatalk_configured"] == 2 %} - {{ _("%(value)d active AFP connection", value=(env["netatalk_configured"] - 1)) }} - {% elif env["netatalk_configured"] > 2 %} - {{ _("%(value)d active AFP connections", value=(env["netatalk_configured"] - 1)) }} +
+
+ {% if env["samba_configured"] %} + {{ _("Windows SMB file sharing is enabled.") }} + {% if env["webmin_configured"] %} + + {{ ("Server administration") }} + + {% endif %} + {% else %} + {{ _("Windows SMB file sharing is disabled.") }} + {% endif %} +
+
+ {% if env["ftp_configured"] %} + {{ _("FTP file sharing is enabled.") }} + {% else %} + {{ _("FTP file sharing is disabled.") }} {% endif %}
{% if env["macproxy_configured"] %} - {{ _("Macproxy is running at %(ip_addr)s (default port 5000)", ip_addr=env['ip_addr']) }} + {{ _("Macproxy is running at %(ip_addr)s (default port 5000)", ip_addr=env['ip_addr']) }} + {% else %} + {{ _("Macproxy is disabled.") }} {% endif %}
+
{{ _("PiSCSI software version:") }} {{ env["version"] }}
diff --git a/python/web/src/web.py b/python/web/src/web.py index a550a95c..6829b3d4 100644 --- a/python/web/src/web.py +++ b/python/web/src/web.py @@ -106,7 +106,10 @@ def get_env_info(): "image_dir": server_info["image_dir"], "image_root_dir": Path(server_info["image_dir"]).name, "netatalk_configured": sys_cmd.running_proc("afpd"), + "samba_configured": sys_cmd.running_proc("smbd"), + "ftp_configured": sys_cmd.running_proc("vsftpd"), "macproxy_configured": sys_cmd.running_proc("macproxy"), + "webmin_configured": sys_cmd.running_proc("miniserv.pl"), "cd_suffixes": tuple(server_info["sccd"]), "rm_suffixes": tuple(server_info["scrm"]), "mo_suffixes": tuple(server_info["scmo"]), From 84c20c4f295c32739580d38ca3eb6fd9e32e295f Mon Sep 17 00:00:00 2001 From: Tony Kuker <34318535+akuker@users.noreply.github.com> Date: Sun, 1 Oct 2023 16:41:53 -0500 Subject: [PATCH 23/87] Update Sonarcloud version (#1225) --- .github/workflows/cpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml index 450c1695..700428b3 100644 --- a/.github/workflows/cpp.yml +++ b/.github/workflows/cpp.yml @@ -52,7 +52,7 @@ jobs: env: SOURCES: cpp BUILD_WRAPPER_OUT_DIR: "$HOME/.build_wrapper_out" # Directory where build-wrapper output will be placed - SONAR_SCANNER_VERSION: 4.7.0.2747 + SONAR_SCANNER_VERSION: 5.0.1.3006 SONAR_SERVER_URL: "https://sonarcloud.io" SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} SONAR_PROJECT_KEY: "akuker_PISCSI" From bd9b776c47fef741587b3c9dba0aa020a07a071f Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Sun, 1 Oct 2023 23:50:30 +0200 Subject: [PATCH 24/87] Fix block size evaluation (#1212) (#1213) * Fix block size evaluation (#1212) --- cpp/piscsi/piscsi_core.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/cpp/piscsi/piscsi_core.cpp b/cpp/piscsi/piscsi_core.cpp index ff6ecc18..c5391ce0 100644 --- a/cpp/piscsi/piscsi_core.cpp +++ b/cpp/piscsi/piscsi_core.cpp @@ -157,7 +157,6 @@ void Piscsi::TerminationHandler(int) Piscsi::optargs_type Piscsi::ParseArguments(const vector& args, int& port) const { optargs_type optargs; - int block_size = 0; string name; opterr = 1; @@ -168,6 +167,7 @@ Piscsi::optargs_type Piscsi::ParseArguments(const vector& args, int& por // the 'bus' object is created and configured case 'i': case 'I': + case 'b': case 'd': case 'D': case 'R': @@ -182,13 +182,6 @@ Piscsi::optargs_type Piscsi::ParseArguments(const vector& args, int& por continue; } - case 'b': { - if (!GetAsUnsignedInt(optarg, block_size)) { - throw parser_exception("Invalid block size " + string(optarg)); - } - continue; - } - case 'L': current_log_level = optarg; continue; @@ -220,7 +213,7 @@ Piscsi::optargs_type Piscsi::ParseArguments(const vector& args, int& por } if (optopt) { - throw parser_exception("Praser error"); + throw parser_exception("Parser error"); } } @@ -253,6 +246,12 @@ void Piscsi::CreateInitialDevices(const optargs_type& optargs) const id_and_lun = value; continue; + case 'b': + if (!GetAsUnsignedInt(value, block_size)) { + throw parser_exception("Invalid block size " + value); + } + continue; + case 'z': locale = value.c_str(); continue; From 10f59afbae321a81892b1317392fe843f795603e Mon Sep 17 00:00:00 2001 From: Tony Kuker <34318535+akuker@users.noreply.github.com> Date: Mon, 2 Oct 2023 02:06:51 -0500 Subject: [PATCH 25/87] Update Sonar Project Key (#1230) --- .github/workflows/cpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml index 700428b3..91e2ae5d 100644 --- a/.github/workflows/cpp.yml +++ b/.github/workflows/cpp.yml @@ -55,7 +55,7 @@ jobs: SONAR_SCANNER_VERSION: 5.0.1.3006 SONAR_SERVER_URL: "https://sonarcloud.io" SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - SONAR_PROJECT_KEY: "akuker_PISCSI" + SONAR_PROJECT_KEY: "akuker-PISCSI" SONAR_ORGANIZATION: "piscsi" steps: - uses: actions/checkout@v3 From 2ced0d3515ba2ce967ba134ef0ada5476cac699e Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Sat, 7 Oct 2023 10:33:57 +0000 Subject: [PATCH 26/87] Default compiler threads based on available RAM, but no more or less than available cores --- easyinstall.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/easyinstall.sh b/easyinstall.sh index 2f7f2cd4..28da5f8a 100755 --- a/easyinstall.sh +++ b/easyinstall.sh @@ -49,7 +49,13 @@ echo -e $logo CONNECT_TYPE="FULLSPEC" # clang v11 is the latest distributed by Buster COMPILER="clang++-11" -CORES=1 +MEM=$(grep MemTotal /proc/meminfo | awk '{print $2}') +CORES=`expr $MEM / 450000` +if [ $CORES -gt $(nproc) ]; then + CORES=$(nproc) +elif [ $CORES -lt 1 ]; then + CORES=1 +fi USER=$(whoami) BASE=$(dirname "$(readlink -f "${0}")") CPP_PATH="$BASE/cpp" From ed2ff0ed734be08282189c7db017bf93a249df6c Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Tue, 10 Oct 2023 09:55:43 +0900 Subject: [PATCH 27/87] Swedish translations (#1221) --- .../translations/sv/LC_MESSAGES/messages.po | 246 ++++++++++++------ 1 file changed, 168 insertions(+), 78 deletions(-) diff --git a/python/web/src/translations/sv/LC_MESSAGES/messages.po b/python/web/src/translations/sv/LC_MESSAGES/messages.po index f13d36e1..e145529d 100644 --- a/python/web/src/translations/sv/LC_MESSAGES/messages.po +++ b/python/web/src/translations/sv/LC_MESSAGES/messages.po @@ -1,14 +1,14 @@ # Swedish translations for PiSCSI. # Copyright (C) 2021 akuker # This file is distributed under the same license as the PiSCSI project. -# Daniel Markstedt , 2021, 2022. +# Daniel Markstedt , 2021, 2022, 2023. # msgid "" msgstr "" "Project-Id-Version: PiSCSI\n" "Report-Msgid-Bugs-To: https://github.com/PiSCSI/piscsi/issues\n" -"POT-Creation-Date: 2023-07-05 10:36-0700\n" -"PO-Revision-Date: 2023-07-05 10:10-0700\n" +"POT-Creation-Date: 2023-09-25 13:59-0700\n" +"PO-Revision-Date: 2023-09-25 12:31-0700\n" "Last-Translator: Daniel Markstedt \n" "Language: sv\n" "Language-Team: N/A\n" @@ -119,6 +119,70 @@ msgstr "Inga filer packages upp (hoppade över existerande filer)" msgid "Unable to extract archive: %(error)s" msgstr "Kunde ej packa upp arkivet: %(error)s" +#: src/return_code_mapper.py:54 +msgid "" +"Potential instability - Under voltage detected - Make sure to use a " +"sufficient power source (2.5+ amps)." +msgstr "" +"Risk för instabilitet - otillräcklig utspänning - se till att använda " +"strömförsörjning med minst 2.5A." + +#: src/return_code_mapper.py:57 +msgid "" +"Potential instability - ARM frequency capped - Ensure sufficient " +"airflow/cooling." +msgstr "" +"Risk för instabilitet - ARM-klockfrekvensen begränsad - se till " +"attluftflödet är gott nog." + +#: src/return_code_mapper.py:59 +msgid "" +"Potential instability - Currently throttled - Make sure to use a " +"sufficient power source (2.5+ amps)." +msgstr "" +"Risk för instabilitiet - throttling är aktiv - set till att " +"användaströmförsörjning med minst 2.5A." + +#: src/return_code_mapper.py:62 +msgid "" +"Potential instability - Soft-temperature limit active - Ensure sufficient" +" airflow/cooling." +msgstr "" +"Risk för instabilitet - överhettningsskydd aktivt - se till attluftflödet" +" är gott nog." + +#: src/return_code_mapper.py:65 +msgid "" +"Potential instability - Under voltage has occurred since last reboot. " +"Make sure to use a sufficient power source (2.5+ amps)." +msgstr "" +"Risk för instabilitet - ARM-klockfrekvensen begränsad någon gång sedan " +"den sista omstarten - se till att luftflödet är gott nog." + +#: src/return_code_mapper.py:68 +msgid "" +"Potential instability - ARM frequency capping has occurred since last " +"reboot. Ensure sufficient airflow/cooling." +msgstr "" +"Risk för instabilitet - ARM-klockfrekvensen var begränsad någon gång " +"sedan den sista omstarten - se till att luftflödet är gott nog." + +#: src/return_code_mapper.py:71 +msgid "" +"Potential instability - Throttling has occurred since the last reboot. " +"Make sure to use a sufficient power source (2.5+ amps)." +msgstr "" +"Risk för instabilitiet - throttling var aktiv någon gång sedan den sista " +"omstarten - se till att använda strömförsörjning med minst 2.5A." + +#: src/return_code_mapper.py:74 +msgid "" +"Potential instability - Soft temperature limit has occurred since last " +"reboot. Ensure sufficient airflow/cooling." +msgstr "" +"Risk för instabilitet - överhettningsskydd aktivt någon gång sedan den " +"sista omstarten - se till att luftflödet är gott nog." + #: src/socket_cmds_flask.py:42 #, python-format msgid "" @@ -146,86 +210,79 @@ msgstr "" "PiSCSIs webbgränssnitt fick en ogiltig respons från PiSCSI. Gå tillbaks " "och försök igen. Om samma fel upprepas så rapportera en bugg." -#: src/web.py:255 +#: src/web.py:265 msgid "PiSCSI Control Page" msgstr "PiSCSI kontrollsida" -#: src/web.py:307 +#: src/web.py:317 msgid "PiSCSI Create Drive" msgstr "PiSCSI skapa skiva" -#: src/web.py:323 +#: src/web.py:333 msgid "PiSCSI File Upload" msgstr "PiSCSI uppladdning" -#: src/web.py:349 +#: src/web.py:359 #, python-format msgid "You must log in with valid credentials for a user in the '%(group)s' group" msgstr "Du måste logga in som en användare som tillhör %(group)s-gruppen" -#: src/web.py:402 src/web.py:447 +#: src/web.py:412 src/web.py:457 #, python-format msgid "No properties data for drive %(drive_name)s" msgstr "Saknar egenskapsdata för skivan %(drive_name)s" -#: src/web.py:424 +#: src/web.py:434 #, python-format msgid "Image file with properties created: %(file_name)s" msgstr "Skapade skivbildsfil med egenskaper: %(file_name)s" -#: src/web.py:525 +#: src/web.py:535 msgid "PiSCSI Image Info" msgstr "PiSCSI bildfilsinfo" -#: src/web.py:532 +#: src/web.py:542 #, python-format msgid "An error occurred when getting disk info: %(error)s" msgstr "Ett fel inträffade när vi hämtade skivinfo: %(error)s" -#: src/web.py:546 +#: src/web.py:556 #, python-format msgid "%(app)s is not a recognized PiSCSI app" msgstr "%(app)s är inte en giltig PiSCSI-app" -#: src/web.py:573 +#: src/web.py:583 msgid "PiSCSI Manual" msgstr "PiSCSI manual" -#: src/web.py:580 +#: src/web.py:590 #, python-format msgid "An error occurred when accessing manual page: %(error)s" msgstr "Ett fel inträffade när vi hämtade manualsidan: %(error)s" -#: src/web.py:596 +#: src/web.py:606 msgid "PiSCSI System Logs" msgstr "PiSCSI systemloggar" -#: src/web.py:604 +#: src/web.py:614 #, python-format msgid "An error occurred when fetching logs: %(error)s" msgstr "Ett fel inträffade när vi hämtade loggar: %(error)s" -#: src/web.py:618 +#: src/web.py:628 #, python-format msgid "Log level set to %(value)s" msgstr "Bytte loggnivån till %(value)s" -#: src/web.py:635 src/web.py:698 +#: src/web.py:645 src/web.py:710 msgid "No SCSI ID specified" msgstr "Inget SCSI-id angett" -#: src/web.py:676 -#, python-format -msgid "Attached %(device_type)s to SCSI ID %(id_number)s LUN %(unit_number)s" -msgstr "" -"Anslöt %(device_type)s till SCSI-id %(id_number)s enhetsnummer " -"%(unit_number)s" - -#: src/web.py:700 +#: src/web.py:712 msgid "No image file to insert" msgstr "Ingen skivfil att ansluta" -#: src/web.py:740 +#: src/web.py:752 #, python-format msgid "" "Attached %(file_name)s as %(device_type)s to SCSI ID %(id_number)s LUN " @@ -234,130 +291,130 @@ msgstr "" "Anslöt %(file_name)s som %(device_type)s till SCSI-id %(id_number)s " "enhetsnummer %(unit_number)s" -#: src/web.py:757 +#: src/web.py:769 msgid "Detached all SCSI devices" msgstr "Kopplade ifrån alla SCSI-enheter" -#: src/web.py:773 +#: src/web.py:785 #, python-format msgid "Detached SCSI ID %(id_number)s LUN %(unit_number)s" msgstr "Kopplade ifrån SCSI-id %(id_number)s enhetsnummer %(unit_number)s" -#: src/web.py:795 +#: src/web.py:807 #, python-format msgid "Ejected SCSI ID %(id_number)s LUN %(unit_number)s" msgstr "Matade ut SCSI-id %(id_number)s enhetsnummer %(unit_number)s" -#: src/web.py:814 +#: src/web.py:826 msgid "PiSCSI Device Info" msgstr "PiSCSI enhetsinfo" -#: src/web.py:818 +#: src/web.py:830 msgid "No devices attached" msgstr "Inga enheter anslutna" -#: src/web.py:834 +#: src/web.py:846 #, python-format msgid "Reserved SCSI ID %(id_number)s" msgstr "Reserverat SCSI-id %(id_number)s" -#: src/web.py:852 +#: src/web.py:864 #, python-format msgid "Released the reservation for SCSI ID %(id_number)s" msgstr "Frigjorde SCSI-id %(id_number)s" -#: src/web.py:871 +#: src/web.py:883 #, python-format msgid "System name changed to '%(name)s'." msgstr "Ändrade systemnamnet till '%(name)s'." -#: src/web.py:872 +#: src/web.py:884 msgid "System name reset to default." msgstr "Återställde systemnamnet till grundvärdet." -#: src/web.py:874 +#: src/web.py:886 msgid "Failed to change system name." msgstr "Kunde ej ändra systemnamnet." -#: src/web.py:928 +#: src/web.py:940 #, python-format msgid "%(iso_type)s is not a valid CD-ROM format." msgstr "%(iso_type)s är inte ett giltigt cd-romformat." -#: src/web.py:943 +#: src/web.py:955 #, python-format msgid "The following error occurred when creating the CD-ROM image: %(error)s" msgstr "Ett fel inträffade när vi skapade en cd-bildfil: %(error)s" -#: src/web.py:950 +#: src/web.py:962 #, python-format msgid "CD-ROM image %(file_name)s with type %(iso_type)s was created." msgstr "Cd-bildfilen %(file_name)s av typ %(iso_type)s har skapats." -#: src/web.py:980 src/web.py:1024 +#: src/web.py:992 src/web.py:1036 msgid "Unknown destination" msgstr "Okänd destination" -#: src/web.py:989 +#: src/web.py:1001 #, python-format msgid "The following error occurred when downloading: %(error)s" msgstr "Ett fel inträffade under nedladdningen: %(error)s" -#: src/web.py:1068 src/web.py:1081 +#: src/web.py:1080 src/web.py:1093 #, python-format msgid "%(drive_format)s is not a valid hard disk format." msgstr "%(drive_format)s är inte ett giltigt hårddisksformat." -#: src/web.py:1126 +#: src/web.py:1138 #, python-format msgid "Image file with properties created: %(file_name)s%(drive_format)s" msgstr "Skapade skivbildsfil med egenskaper: %(file_name)s%(drive_format)s" -#: src/web.py:1136 +#: src/web.py:1148 #, python-format msgid "Image file created: %(file_name)s%(drive_format)s" msgstr "Skapade skivbildsfil %(file_name)s%(drive_format)s" -#: src/web.py:1197 +#: src/web.py:1209 #, python-format msgid "Image file with properties deleted: %(file_name)s" msgstr "Bildfil med egenskaper raderades: %(file_name)s" -#: src/web.py:1206 +#: src/web.py:1218 #, python-format msgid "Image file deleted: %(file_name)s" msgstr "Filen %(file_name)s raderades" -#: src/web.py:1244 +#: src/web.py:1256 #, python-format msgid "Image file with properties renamed to: %(file_name)s" msgstr "Filen med egenskaper döptes om till %(file_name)s" -#: src/web.py:1253 +#: src/web.py:1265 #, python-format msgid "Image file renamed to: %(file_name)s" msgstr "Filen döptes om till %(file_name)s" -#: src/web.py:1291 +#: src/web.py:1303 #, python-format msgid "Copy of image file with properties saved as: %(file_name)s" msgstr "Kopierade filen med egenskaper och sparade den som %(file_name)s" -#: src/web.py:1300 +#: src/web.py:1312 #, python-format msgid "Copy of image file saved as: %(file_name)s" msgstr "Kopierade filen och sparade den som %(file_name)s" -#: src/web.py:1353 +#: src/web.py:1365 #, python-format msgid "Changed Web Interface language to %(locale)s" msgstr "Bytte webbgränssnittets språk till %(locale)s" -#: src/web.py:1364 +#: src/web.py:1376 msgid "The requested theme does not exist." msgstr "Temat existerar inte." -#: src/web.py:1367 +#: src/web.py:1379 #, python-format msgid "Theme changed to '%(theme)s'." msgstr "Bytte till temat '%(theme)s'." @@ -422,36 +479,55 @@ msgstr "Magnetoptisk skiva" msgid "You must log in to use this function" msgstr "Du måste logga in för att använda den här funktionen" -#: src/web_utils.py:277 +#: src/web_utils.py:280 +#, python-format +msgid "Wireless network bridge enabled for %(interface)s" +msgstr "Trådlös nätverksbrygga aktiv för %(interface)s" + +#: src/web_utils.py:286 +#, python-format +msgid "Wired network bridge enabled for %(interface)s" +msgstr "Nätverksbrygga aktiv för %(interface)s" + +#: src/web_utils.py:292 +#, python-format +msgid "" +"Unable to detect if %(interface)s is Ethernet or WiFi. Make sure that the" +" correct network bridge is configured." +msgstr "" +"Kunde ej avgöra om %(interface)s är Ethernet eller Wi-Fi. Se till att den" +" korrekta nätverksbryggan är inställd." + +#: src/web_utils.py:299 #, python-format msgid "Configure the network bridge for %(interface)s first: " msgstr "Ställ in nätverksbryggan för %(interface)s först: " -#: src/web_utils.py:305 +#: src/web_utils.py:315 msgid "Path must not be absolute" msgstr "Sökvägen kan inte vara absolut" -#: src/web_utils.py:307 +#: src/web_utils.py:317 msgid "Path must not traverse the file system" msgstr "Sökvägen får inte lov att traversera filsystemet" -#: src/web_utils.py:309 +#: src/web_utils.py:319 msgid "Path must not start in the home directory" msgstr "Sökvägen kan inte starta i hemkatalogen" -#: src/web_utils.py:333 +#: src/web_utils.py:343 msgid "The file already exists!" msgstr "Filen finns redan!" -#: src/web_utils.py:341 +#: src/web_utils.py:351 msgid "Unable to write the file to disk!" msgstr "Kunde ej skriva filen till skivan!" -#: src/web_utils.py:349 +#: src/web_utils.py:359 msgid "Transferred file corrupted!" msgstr "Den överförda filen är skadad!" -#: src/web_utils.py:351 +#: src/web_utils.py:361 msgid "File upload successful!" msgstr "Filen har laddas upp!" @@ -497,39 +573,53 @@ msgstr "kontrollsida" msgid "System Name:" msgstr "Systemnamn:" -#: src/templates/base.html:98 src/templates/base.html:100 +#: src/templates/base.html:109 src/templates/base.html:111 #, python-format msgid "Switch to the %(theme)s theme" msgstr "Byt till temat %(theme)s" -#: src/templates/base.html:106 -msgid "The AppleShare server is running. No active connections." -msgstr "AppleShare-servern är aktiv. Inga klienter är anslutna." +#: src/templates/base.html:117 +msgid "Mac AFP file sharing is enabled." +msgstr "Mac AFP-fildelning är påslagen" -#: src/templates/base.html:109 -#, python-format -msgid "%(value)d active AFP connection" -msgstr "%(value)d aktiv AFP-klient" +#: src/templates/base.html:124 +msgid "Mac AFP file sharing is disabled." +msgstr "Mac AFP-fildelning är avslagen" -#: src/templates/base.html:111 -#, python-format -msgid "%(value)d active AFP connections" -msgstr "%(value)d aktiva AFP-klienter" +#: src/templates/base.html:129 +msgid "Windows SMB file sharing is enabled." +msgstr "Windows SMB-fildelning är påslagen" -#: src/templates/base.html:116 +#: src/templates/base.html:136 +msgid "Windows SMB file sharing is disabled." +msgstr "Windows SMB-fildelning är avslagen" + +#: src/templates/base.html:141 +msgid "FTP file sharing is enabled." +msgstr "FTP-fildelning är påslagen" + +#: src/templates/base.html:143 +msgid "FTP file sharing is disabled." +msgstr "FTP-fildelning är avslagen" + +#: src/templates/base.html:148 #, python-format msgid "Macproxy is running at %(ip_addr)s (default port 5000)" msgstr "Macproxy är tillgängligt på %(ip_addr)s (vanligtvis port 5000)" -#: src/templates/base.html:120 +#: src/templates/base.html:150 +msgid "Macproxy is disabled." +msgstr "Macproxy är avslagen" + +#: src/templates/base.html:155 msgid "PiSCSI software version:" msgstr "PiSCSI-version:" -#: src/templates/base.html:123 +#: src/templates/base.html:158 msgid "Hardware and OS:" msgstr "Hårdvara och operativsystem:" -#: src/templates/base.html:126 +#: src/templates/base.html:161 msgid "Network Address:" msgstr "Nätverksadress:" From 68e0c29d83b24f8dd18fc24a586d781c0a7d9d52 Mon Sep 17 00:00:00 2001 From: Tony Kuker <34318535+akuker@users.noreply.github.com> Date: Mon, 9 Oct 2023 20:10:46 -0500 Subject: [PATCH 28/87] Disable the Timer Test #1227 (#1228) The Timer test isn't reliable on all variants of the Raspberry Pi. This will temporarily comment it out. When the RPi5 support is being added, this test should be made optional and only triggered when a CLI option is present. --- cpp/scsiloop/scsiloop_core.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/scsiloop/scsiloop_core.cpp b/cpp/scsiloop/scsiloop_core.cpp index 09b867a3..b6181d64 100644 --- a/cpp/scsiloop/scsiloop_core.cpp +++ b/cpp/scsiloop/scsiloop_core.cpp @@ -159,8 +159,8 @@ int ScsiLoop::run(const vector &args) // This must be executed before the timer test, since this initializes the timer ScsiLoop_GPIO gpio_test; - int result = ScsiLoop_Timer::RunTimerTest(error_list); - result += gpio_test.RunLoopbackTest(error_list); + // int result = ScsiLoop_Timer::RunTimerTest(error_list); + int result = gpio_test.RunLoopbackTest(error_list); if (result == 0) { // Only test the dat inputs/outputs if the loopback test passed. From c1f6f3ffeabc457c1ae75ff2f355cce56adb2a6c Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Tue, 3 Oct 2023 04:49:09 +0900 Subject: [PATCH 29/87] Explicit lock of werkzeug lib version --- python/web/requirements.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/python/web/requirements.txt b/python/web/requirements.txt index 60758b1c..a11d5f40 100644 --- a/python/web/requirements.txt +++ b/python/web/requirements.txt @@ -1,9 +1,10 @@ bjoern==3.2.2 -Flask==2.3.2 +Flask==2.3.3 Jinja2==3.1.2 protobuf==3.20.2 requests==2.31.0 simplepam==0.1.5 flask_babel==2.0.0 ua-parser==0.16.1 -vcgencmd==0.1.1 \ No newline at end of file +vcgencmd==0.1.1 +werkzeug==2.3.7 From 41bdcd4aedf78cc544c762a8e1f2c4499a46906b Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Sun, 15 Oct 2023 08:38:15 +0200 Subject: [PATCH 30/87] Issues 1179 and 1182 (#1232) * Update logging * Remove duplicate code * Update unit tests * Clean up includes * Merge ProtobufSerializer into protobuf_util namespace * Precompile regex * Add const * Add Split() convenience method, update log level/ID parsing * Move log.h to legacy folder * Elimininate gotos * Fixes for gcc 13 * Update compiler flags * Update default folder handling * Use references instead of pointers * Move code for better encapsulation * Move code * Remove unused method argument * Move device logger * Remove redundant to_string * Rename for consistency * Update handling of protobuf pointers * Simplify protobuf usage * Memory handling update * Add hasher --- cpp/Makefile | 40 +- cpp/controllers/abstract_controller.cpp | 105 ++-- cpp/controllers/abstract_controller.h | 61 ++- cpp/controllers/controller_manager.cpp | 76 ++- cpp/controllers/controller_manager.h | 43 +- cpp/controllers/phase_handler.h | 32 +- cpp/controllers/scsi_controller.cpp | 251 ++++----- cpp/controllers/scsi_controller.h | 26 +- cpp/devices/cd_track.h | 1 + cpp/devices/cfilesystem.cpp | 20 +- cpp/devices/cfilesystem.h | 9 + cpp/devices/ctapdriver.cpp | 483 +++++++---------- cpp/devices/ctapdriver.h | 43 +- cpp/devices/device.cpp | 18 +- cpp/devices/device.h | 23 +- cpp/devices/device_factory.cpp | 82 +-- cpp/devices/device_factory.h | 20 +- cpp/devices/device_logger.cpp | 37 +- cpp/devices/device_logger.h | 8 +- cpp/devices/disk.cpp | 98 ++-- cpp/devices/disk.h | 15 +- cpp/devices/disk_cache.cpp | 6 +- cpp/devices/disk_cache.h | 5 +- cpp/devices/disk_track.cpp | 16 +- cpp/devices/disk_track.h | 6 +- cpp/devices/host_services.cpp | 25 +- cpp/devices/host_services.h | 9 +- cpp/devices/interfaces/byte_writer.h | 27 - cpp/devices/mode_page_device.cpp | 26 +- cpp/devices/mode_page_device.h | 14 +- cpp/devices/primary_device.cpp | 89 ++-- cpp/devices/primary_device.h | 38 +- cpp/devices/scsi_command_util.cpp | 84 +-- cpp/devices/scsi_command_util.h | 48 +- cpp/devices/scsi_daynaport.cpp | 105 ++-- cpp/devices/scsi_daynaport.h | 18 +- cpp/devices/scsi_host_bridge.cpp | 28 +- cpp/devices/scsi_host_bridge.h | 21 +- cpp/devices/scsi_printer.cpp | 48 +- cpp/devices/scsi_printer.h | 10 +- cpp/devices/scsicd.cpp | 22 +- cpp/devices/scsicd.h | 11 +- cpp/devices/scsihd.cpp | 15 +- cpp/devices/scsihd.h | 7 +- cpp/devices/scsihd_nec.cpp | 21 +- cpp/devices/scsihd_nec.h | 2 +- cpp/devices/scsimo.cpp | 32 +- cpp/devices/scsimo.h | 4 +- cpp/devices/storage_device.cpp | 63 ++- cpp/devices/storage_device.h | 30 +- cpp/hal/data_sample.h | 1 + cpp/hal/gpiobus.cpp | 18 +- cpp/hal/gpiobus.h | 2 +- cpp/hal/gpiobus_bananam2p.cpp | 18 +- cpp/hal/gpiobus_bananam2p.h | 1 - cpp/hal/gpiobus_factory.cpp | 10 +- cpp/hal/gpiobus_raspberry.cpp | 10 +- cpp/hal/gpiobus_raspberry.h | 1 - cpp/hal/gpiobus_virtual.cpp | 4 +- cpp/hal/gpiobus_virtual.h | 3 +- cpp/{shared => hal}/log.h | 4 +- cpp/hal/sbc_version.cpp | 2 +- cpp/hal/sbc_version.h | 1 + cpp/hal/sunxi_utils.cpp | 8 +- cpp/hal/systimer.cpp | 5 +- cpp/hal/systimer_allwinner.cpp | 8 +- cpp/hal/systimer_raspberry.cpp | 7 +- cpp/monitor/sm_core.cpp | 69 ++- cpp/monitor/sm_core.h | 5 +- cpp/monitor/sm_html_report.cpp | 4 +- cpp/monitor/sm_json_report.cpp | 6 +- cpp/monitor/sm_vcd_report.cpp | 4 +- cpp/piscsi.cpp | 4 +- cpp/piscsi/command_context.cpp | 63 ++- cpp/piscsi/command_context.h | 44 +- cpp/piscsi/localizer.cpp | 65 ++- cpp/piscsi/localizer.h | 12 +- cpp/piscsi/piscsi_core.cpp | 645 +++++++++++----------- cpp/piscsi/piscsi_core.h | 71 +-- cpp/piscsi/piscsi_executor.cpp | 412 ++++++-------- cpp/piscsi/piscsi_executor.h | 45 +- cpp/piscsi/piscsi_image.cpp | 274 +++++----- cpp/piscsi/piscsi_image.h | 23 +- cpp/piscsi/piscsi_response.cpp | 482 +++++++---------- cpp/piscsi/piscsi_response.h | 49 +- cpp/piscsi/piscsi_service.cpp | 154 +++--- cpp/piscsi/piscsi_service.h | 31 +- cpp/piscsi_interface.proto | 4 +- cpp/scsictl/scsictl_commands.cpp | 69 ++- cpp/scsictl/scsictl_commands.h | 27 +- cpp/scsictl/scsictl_core.cpp | 55 +- cpp/scsictl/scsictl_display.cpp | 278 ++++------ cpp/scsictl/scsictl_display.h | 15 +- cpp/scsictl/scsictl_parser.cpp | 8 +- cpp/scsictl/scsictl_parser.h | 6 +- cpp/scsidump.cpp | 4 +- cpp/scsidump/scsidump_core.cpp | 81 +-- cpp/scsidump/scsidump_core.h | 9 +- cpp/scsiloop/scsiloop_core.cpp | 2 +- cpp/scsiloop/scsiloop_cout.cpp | 8 +- cpp/scsiloop/scsiloop_cout.h | 4 +- cpp/scsiloop/scsiloop_gpio.cpp | 4 +- cpp/scsiloop/scsiloop_gpio.h | 4 +- cpp/scsiloop/scsiloop_timer.cpp | 4 +- cpp/shared/network_util.cpp | 75 +++ cpp/shared/network_util.h | 24 + cpp/shared/piscsi_exceptions.h | 2 +- cpp/shared/piscsi_util.cpp | 83 ++- cpp/shared/piscsi_util.h | 40 +- cpp/shared/piscsi_version.cpp | 5 +- cpp/shared/protobuf_serializer.cpp | 80 --- cpp/shared/protobuf_serializer.h | 29 - cpp/shared/protobuf_util.cpp | 172 +++--- cpp/shared/protobuf_util.h | 37 +- cpp/shared/scsi.h | 104 ++-- cpp/test/abstract_controller_test.cpp | 150 +----- cpp/test/command_context_test.cpp | 137 ++++- cpp/test/controller_manager_test.cpp | 69 ++- cpp/test/ctapdriver_test.cpp | 17 +- cpp/test/device_factory_test.cpp | 37 +- cpp/test/device_test.cpp | 31 +- cpp/test/disk_test.cpp | 682 ++++++++++-------------- cpp/test/gpiobus_raspberry_test.cpp | 8 +- cpp/test/host_services_test.cpp | 104 ++-- cpp/test/linux_os_stubs.cpp | 10 +- cpp/test/mocks.h | 61 ++- cpp/test/mode_page_device_test.cpp | 66 +-- cpp/test/network_util_test.cpp | 33 ++ cpp/test/phase_handler_test.cpp | 49 +- cpp/test/piscsi_exceptions_test.cpp | 14 +- cpp/test/piscsi_executor_test.cpp | 474 ++++++++-------- cpp/test/piscsi_image_test.cpp | 109 ++-- cpp/test/piscsi_response_test.cpp | 143 ++--- cpp/test/piscsi_service_test.cpp | 100 +++- cpp/test/piscsi_util_test.cpp | 80 ++- cpp/test/primary_device_test.cpp | 274 ++++------ cpp/test/protobuf_serializer_test.cpp | 98 ---- cpp/test/protobuf_util_test.cpp | 106 +++- cpp/test/scsi_command_util_test.cpp | 59 +- cpp/test/scsi_controller_test.cpp | 85 +-- cpp/test/scsi_daynaport_test.cpp | 194 +++---- cpp/test/scsi_host_bridge_test.cpp | 4 +- cpp/test/scsi_printer_test.cpp | 91 ++-- cpp/test/scsicd_test.cpp | 29 +- cpp/test/scsictl_commands_test.cpp | 5 +- cpp/test/scsictl_display_test.cpp | 9 +- cpp/test/scsihd_nec_test.cpp | 21 +- cpp/test/scsihd_test.cpp | 25 +- cpp/test/scsimo_test.cpp | 16 +- cpp/test/storage_device_test.cpp | 62 ++- cpp/test/test_setup.cpp | 37 +- cpp/test/test_shared.cpp | 45 +- cpp/test/test_shared.h | 18 +- doc/piscsi.1 | 2 +- doc/piscsi_man_page.txt | 132 +++-- doc/scsictl.1 | 2 +- doc/scsictl_man_page.txt | 93 ++-- docker/README.md | 2 +- docker/docker-compose.yml | 4 +- docker/web/Dockerfile | 2 +- easyinstall.sh | 9 +- 161 files changed, 4767 insertions(+), 5150 deletions(-) delete mode 100644 cpp/devices/interfaces/byte_writer.h rename cpp/{shared => hal}/log.h (86%) create mode 100644 cpp/shared/network_util.cpp create mode 100644 cpp/shared/network_util.h delete mode 100644 cpp/shared/protobuf_serializer.cpp delete mode 100644 cpp/shared/protobuf_serializer.h create mode 100644 cpp/test/network_util_test.cpp delete mode 100644 cpp/test/protobuf_serializer_test.cpp diff --git a/cpp/Makefile b/cpp/Makefile index 1a5f95b0..9f6f2e06 100644 --- a/cpp/Makefile +++ b/cpp/Makefile @@ -7,8 +7,6 @@ CROSS_COMPILE = CXX = $(CROSS_COMPILE)g++ -AR = $(CROSS_COMPILE)ar -RANLIB = $(CROSS_COMPILE)ranlib ## DEBUG=1 : A Debug build includes the debugger symbols ## and disables compiler optimization. Typically, @@ -16,30 +14,21 @@ RANLIB = $(CROSS_COMPILE)ranlib DEBUG ?= 0 ifeq ($(DEBUG), 1) # Debug compiler flags - CXXFLAGS += -O0 -g -Wall -Wextra -DDEBUG - BUILD_TYPE = Debug + CXXFLAGS += -Og -g -Wall -Wextra -DDEBUG else # Release compiler flags CXXFLAGS += -O3 -Wall -Werror -Wextra -DNDEBUG - BUILD_TYPE = Release endif ifeq ("$(shell uname -s)","Linux") # -Wno-psabi might not work on non-Linux platforms CXXFLAGS += -Wno-psabi endif -# Depending on the GCC version the compilation flags differ -GCCVERSION10 := $(shell expr `$(CXX) -dumpversion` \>= 10) - -CXXFLAGS += -std=c++17 -iquote . -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE -MD -MP +CXXFLAGS += -std=c++20 -iquote . -D_FILE_OFFSET_BITS=64 -DFMT_HEADER_ONLY -DSPDLOG_FMT_EXTERNAL -MD -MP ## EXTRA_FLAGS : Can be used to pass special purpose flags CXXFLAGS += $(EXTRA_FLAGS) -ifeq "$(GCCVERSION10)" "1" - CXXFLAGS += -DFMT_HEADER_ONLY -endif - ## CONNECT_TYPE=FULLSPEC : Specify the type of PiSCSI board type ## that you are using. The typical options are @@ -86,12 +75,12 @@ SRC_PROTOC = piscsi_interface.proto SRC_GENERATED = $(GENERATED_DIR)/piscsi_interface.pb.cpp SRC_PROTOBUF = \ - shared/protobuf_util.cpp \ - shared/protobuf_serializer.cpp + shared/protobuf_util.cpp SRC_SHARED = \ shared/piscsi_version.cpp \ - shared/piscsi_util.cpp + shared/piscsi_util.cpp \ + shared/network_util.cpp SRC_PISCSI_CORE = $(shell find ./piscsi -name '*.cpp') SRC_PISCSI_CORE += $(shell find ./controllers -name '*.cpp') @@ -167,7 +156,7 @@ $(SRC_GENERATED) : $(SRC_PROTOC) protoc --cpp_out=$(GENERATED_DIR) $(SRC_PROTOC) mv $(GENERATED_DIR)/piscsi_interface.pb.cc $@ -$(OBJ_GENERATED) : $(SRC_GENERATED) +$(OBJ_GENERATED) : $(SRC_GENERATED) | $(OBJDIR) $(CXX) $(CXXFLAGS) -c $< -o $@ ## Build Targets: @@ -180,10 +169,11 @@ $(OBJ_GENERATED) : $(SRC_GENERATED) ## Note that you have to run 'make clean' before switching ## between coverage and non-coverage builds. .DEFAULT_GOAL := all -.PHONY: all ALL docs test coverage lcov -all: $(BIN_ALL) docs +.PHONY: all docs test coverage lcov -test: $(BINDIR)/$(PISCSI_TEST) +all: $(SRC_GENERATED) $(BIN_ALL) docs + +test: $(SRC_GENERATED) $(BINDIR)/$(PISCSI_TEST) $(BINDIR)/$(PISCSI_TEST) coverage: CXXFLAGS += --coverage @@ -198,10 +188,10 @@ docs: $(DOC_DIR)/piscsi_man_page.txt $(DOC_DIR)/scsictl_man_page.txt $(DOC_DIR)/ $(SRC_PISCSI_CORE) $(SRC_SCSICTL_CORE) : $(OBJ_GENERATED) -$(BINDIR)/$(PISCSI): $(SRC_GENERATED) $(OBJ_PISCSI_CORE) $(OBJ_PISCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR) - $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_PISCSI_CORE) $(OBJ_PISCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lpcap -lprotobuf -lstdc++fs +$(BINDIR)/$(PISCSI): $(OBJ_GENERATED) $(OBJ_PISCSI_CORE) $(OBJ_PISCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR) + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_PISCSI_CORE) $(OBJ_PISCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lpcap -lprotobuf -$(BINDIR)/$(SCSICTL): $(SRC_GENERATED) $(OBJ_SCSICTL_CORE) $(OBJ_SCSICTL) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR) +$(BINDIR)/$(SCSICTL): $(OBJ_GENERATED) $(OBJ_SCSICTL_CORE) $(OBJ_SCSICTL) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR) $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_SCSICTL_CORE) $(OBJ_SCSICTL) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lprotobuf $(BINDIR)/$(SCSIDUMP): $(OBJ_SCSIDUMP) $(OBJ_SHARED) | $(BINDIR) @@ -213,8 +203,8 @@ $(BINDIR)/$(SCSIMON): $(OBJ_SCSIMON) $(OBJ_SHARED) | $(BINDIR) $(BINDIR)/$(SCSILOOP): $(OBJ_SHARED) $(OBJ_SCSILOOP) | $(BINDIR) $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_SHARED) $(OBJ_SCSILOOP) -$(BINDIR)/$(PISCSI_TEST): $(SRC_GENERATED) $(OBJ_PISCSI_CORE) $(OBJ_SCSICTL_CORE) $(OBJ_PISCSI_TEST) $(OBJ_SCSICTL_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR) - $(CXX) $(CXXFLAGS) $(LDFLAGS) $(TEST_WRAPS) -o $@ $(OBJ_PISCSI_CORE) $(OBJ_SCSICTL_CORE) $(OBJ_PISCSI_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lpcap -lprotobuf -lstdc++fs -lgmock -lgtest +$(BINDIR)/$(PISCSI_TEST): $(OBJ_GENERATED) $(OBJ_PISCSI_CORE) $(OBJ_SCSICTL_CORE) $(OBJ_PISCSI_TEST) $(OBJ_SCSICTL_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR) + $(CXX) $(CXXFLAGS) $(LDFLAGS) $(TEST_WRAPS) -o $@ $(OBJ_PISCSI_CORE) $(OBJ_SCSICTL_CORE) $(OBJ_PISCSI_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lpcap -lprotobuf -lgmock -lgtest # Phony rules for building individual utilities .PHONY: $(PISCSI) $(SCSICTL) $(SCSIDUMP) $(SCSIMON) $(PISCSI_TEST) $(SCSILOOP) diff --git a/cpp/controllers/abstract_controller.cpp b/cpp/controllers/abstract_controller.cpp index 17e91984..36242a6c 100644 --- a/cpp/controllers/abstract_controller.cpp +++ b/cpp/controllers/abstract_controller.cpp @@ -3,16 +3,22 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "shared/piscsi_exceptions.h" #include "devices/primary_device.h" #include "abstract_controller.h" +#include using namespace scsi_defs; +AbstractController::AbstractController(BUS& bus, int target_id, int max_luns) : bus(bus), target_id(target_id), max_luns(max_luns) +{ + device_logger.SetIdAndLun(target_id, -1); +} + void AbstractController::AllocateCmd(size_t size) { if (size > ctrl.cmd.size()) { @@ -40,9 +46,8 @@ unordered_set> AbstractController::GetDevices() const { unordered_set> devices; - for (const auto& [id, lun] : luns) { - devices.insert(lun); - } + // "luns | views:values" is not supported by the bullseye compiler + ranges::transform(luns, inserter(devices, devices.begin()), [] (const auto& l) { return l.second; } ); return devices; } @@ -56,100 +61,66 @@ void AbstractController::Reset() { SetPhase(phase_t::busfree); - ctrl.status = status::GOOD; - ctrl.message = 0x00; - ctrl.blocks = 0; - ctrl.next = 0; - ctrl.offset = 0; - ctrl.length = 0; + ctrl = {}; + + SetByteTransfer(false); // Reset all LUNs - for (const auto& [lun, device] : luns) { + for (const auto& [_, device] : luns) { device->Reset(); } + + GetBus().Reset(); } -void AbstractController::ProcessPhase() +void AbstractController::ProcessOnController(int id_data) { - switch (GetPhase()) { - case phase_t::busfree: - BusFree(); - break; + device_logger.SetIdAndLun(GetTargetId(), -1); - case phase_t::selection: - Selection(); - break; + const int initiator_id = ExtractInitiatorId(id_data); + if (initiator_id != UNKNOWN_INITIATOR_ID) { + LogTrace("++++ Starting processing for initiator ID " + to_string(initiator_id)); + } + else { + LogTrace("++++ Starting processing for unknown initiator ID"); + } - case phase_t::dataout: - DataOut(); - break; - - case phase_t::datain: - DataIn(); - break; - - case phase_t::command: - Command(); - break; - - case phase_t::status: - Status(); - break; - - case phase_t::msgout: - MsgOut(); - break; - - case phase_t::msgin: - MsgIn(); - break; - - default: - throw scsi_exception(sense_key::ABORTED_COMMAND); - break; + while (Process(initiator_id)) { + // Handle bus phases until the bus is free for the next command } } bool AbstractController::AddDevice(shared_ptr device) { - if (device->GetLun() < 0 || device->GetLun() >= GetMaxLuns() || HasDeviceForLun(device->GetLun())) { + const int lun = device->GetLun(); + + if (lun < 0 || lun >= GetMaxLuns() || HasDeviceForLun(lun) || device->GetController()) { return false; } - luns[device->GetLun()] = device; - device->SetController(shared_from_this()); + luns[lun] = device; + device->SetController(this); return true; } -bool AbstractController::RemoveDevice(shared_ptr device) +bool AbstractController::RemoveDevice(PrimaryDevice& device) { - device->SetController(nullptr); + device.CleanUp(); - return luns.erase(device->GetLun()) == 1; + return luns.erase(device.GetLun()) == 1; } bool AbstractController::HasDeviceForLun(int lun) const { - return luns.find(lun) != luns.end(); + return luns.contains(lun); } int AbstractController::ExtractInitiatorId(int id_data) const { - int initiator_id = UNKNOWN_INITIATOR_ID; - - if (int tmp = id_data - (1 << target_id); tmp) { - initiator_id = 0; - for (int j = 0; j < 8; j++) { - tmp >>= 1; - if (tmp) { - initiator_id++; - } - else { - break; - } - } + if (const int id_data_without_target = id_data - (1 << target_id); id_data_without_target) { + return static_cast(log2(id_data_without_target & -id_data_without_target)); } - return initiator_id; + return UNKNOWN_INITIATOR_ID; } diff --git a/cpp/controllers/abstract_controller.h b/cpp/controllers/abstract_controller.h index ae718bbf..820b98f3 100644 --- a/cpp/controllers/abstract_controller.h +++ b/cpp/controllers/abstract_controller.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // Base class for device controllers // @@ -14,17 +14,19 @@ #include "shared/scsi.h" #include "hal/bus.h" #include "phase_handler.h" -#include "controller_manager.h" +#include "devices/device_logger.h" #include #include +#include #include #include +#include using namespace std; class PrimaryDevice; -class AbstractController : public PhaseHandler, public enable_shared_from_this +class AbstractController : public PhaseHandler { public: @@ -37,19 +39,19 @@ public: RESTART_PI }; - AbstractController(shared_ptr controller_manager, int target_id, int max_luns) - : controller_manager(controller_manager), target_id(target_id), max_luns(max_luns) {} + AbstractController(BUS&, int, int); ~AbstractController() override = default; - virtual void Error(scsi_defs::sense_key, scsi_defs::asc = scsi_defs::asc::NO_ADDITIONAL_SENSE_INFORMATION, - scsi_defs::status = scsi_defs::status::CHECK_CONDITION) = 0; + virtual void Error(scsi_defs::sense_key, scsi_defs::asc = scsi_defs::asc::no_additional_sense_information, + scsi_defs::status = scsi_defs::status::check_condition) = 0; virtual void Reset(); virtual int GetInitiatorId() const = 0; // Get requested LUN based on IDENTIFY message, with LUN from the CDB as fallback virtual int GetEffectiveLun() const = 0; - virtual void ScheduleShutdown(piscsi_shutdown_mode) = 0; + void ScheduleShutdown(piscsi_shutdown_mode mode) { shutdown_mode = mode; } + piscsi_shutdown_mode GetShutdownMode() const { return shutdown_mode; } int GetTargetId() const { return target_id; } int GetMaxLuns() const { return max_luns; } @@ -58,52 +60,59 @@ public: unordered_set> GetDevices() const; shared_ptr GetDeviceForLun(int) const; bool AddDevice(shared_ptr); - bool RemoveDevice(shared_ptr); + bool RemoveDevice(PrimaryDevice&); bool HasDeviceForLun(int) const; - int ExtractInitiatorId(int) const; + void ProcessOnController(int); // TODO These should probably be extracted into a new TransferHandler class void AllocateBuffer(size_t); - vector& GetBuffer() { return ctrl.buffer; } - scsi_defs::status GetStatus() const { return ctrl.status; } + auto& GetBuffer() { return ctrl.buffer; } + auto GetStatus() const { return ctrl.status; } void SetStatus(scsi_defs::status s) { ctrl.status = s; } - uint32_t GetLength() const { return ctrl.length; } + auto GetLength() const { return ctrl.length; } void SetLength(uint32_t l) { ctrl.length = l; } - uint32_t GetBlocks() const { return ctrl.blocks; } + bool HasBlocks() const { return ctrl.blocks; } void SetBlocks(uint32_t b) { ctrl.blocks = b; } void DecrementBlocks() { --ctrl.blocks; } - uint64_t GetNext() const { return ctrl.next; } + auto GetNext() const { return ctrl.next; } void SetNext(uint64_t n) { ctrl.next = n; } void IncrementNext() { ++ctrl.next; } int GetMessage() const { return ctrl.message; } void SetMessage(int m) { ctrl.message = m; } - vector& GetCmd() { return ctrl.cmd; } - int GetCmd(int index) const { return ctrl.cmd[index]; } + auto GetCmd() const { return ctrl.cmd; } + int GetCmdByte(int index) const { return ctrl.cmd[index]; } bool IsByteTransfer() const { return is_byte_transfer; } void SetByteTransfer(bool); - uint32_t GetBytesToTransfer() const { return bytes_to_transfer; } + auto GetBytesToTransfer() const { return bytes_to_transfer; } void SetBytesToTransfer(uint32_t b) { bytes_to_transfer = b; } protected: - shared_ptr GetControllerManager() const { return controller_manager.lock(); } - inline BUS& GetBus() const { return controller_manager.lock()->GetBus(); } + BUS& GetBus() const { return bus; } - scsi_defs::scsi_command GetOpcode() const { return static_cast(ctrl.cmd[0]); } + auto GetOpcode() const { return static_cast(ctrl.cmd[0]); } int GetLun() const { return (ctrl.cmd[1] >> 5) & 0x07; } - void ProcessPhase(); - void AllocateCmd(size_t); + void SetCmdByte(int index, int value) { ctrl.cmd[index] = value; } + // TODO These should probably be extracted into a new TransferHandler class bool HasValidLength() const { return ctrl.length != 0; } int GetOffset() const { return ctrl.offset; } void ResetOffset() { ctrl.offset = 0; } void UpdateOffsetAndLength() { ctrl.offset += ctrl.length; ctrl.length = 0; } + void LogTrace(const string& s) const { device_logger.Trace(s); } + void LogDebug(const string& s) const { device_logger.Debug(s); } + void LogInfo(const string& s) const { device_logger.Info(s); } + void LogWarn(const string& s) const { device_logger.Warn(s); } + void LogError(const string& s) const { device_logger.Error(s); } + private: + int ExtractInitiatorId(int) const; + using ctrl_t = struct _ctrl_t { // Command data, dynamically resized if required vector cmd = vector(16); @@ -121,7 +130,9 @@ private: ctrl_t ctrl = {}; - weak_ptr controller_manager; + BUS& bus; + + DeviceLogger device_logger; // Logical units of this controller mapped to their LUN numbers unordered_map> luns; @@ -132,4 +143,6 @@ private: bool is_byte_transfer = false; uint32_t bytes_to_transfer = 0; + + piscsi_shutdown_mode shutdown_mode = piscsi_shutdown_mode::NONE; }; diff --git a/cpp/controllers/controller_manager.cpp b/cpp/controllers/controller_manager.cpp index 9f877cdb..d7d2a540 100644 --- a/cpp/controllers/controller_manager.cpp +++ b/cpp/controllers/controller_manager.cpp @@ -3,29 +3,34 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "devices/device_factory.h" #include "devices/primary_device.h" #include "scsi_controller.h" #include "controller_manager.h" using namespace std; -bool ControllerManager::AttachToScsiController(int id, shared_ptr device) +shared_ptr ControllerManager::CreateScsiController(BUS& bus, int id) const { - auto controller = FindController(id); - if (controller != nullptr) { + return make_shared(bus, id); +} + +bool ControllerManager::AttachToController(BUS& bus, int id, shared_ptr device) +{ + if (auto controller = FindController(id); controller != nullptr) { + if (controller->HasDeviceForLun(device->GetLun())) { + return false; + } + return controller->AddDevice(device); } - // If there is no LUN yet the first LUN must be LUN 0 - if (device->GetLun() == 0) { - controller = make_shared(shared_from_this(), id); - - if (controller->AddDevice(device)) { + // If this is LUN 0 create a new controller + if (!device->GetLun()) { + if (auto controller = CreateScsiController(bus, id); controller->AddDevice(device)) { controllers[id] = controller; return true; @@ -35,20 +40,37 @@ bool ControllerManager::AttachToScsiController(int id, shared_ptr return false; } -bool ControllerManager::DeleteController(shared_ptr controller) +bool ControllerManager::DeleteController(const AbstractController& controller) { - return controllers.erase(controller->GetTargetId()) == 1; -} - -shared_ptr ControllerManager::IdentifyController(int data) const -{ - for (const auto& [id, controller] : controllers) { - if (data & (1 << controller->GetTargetId())) { - return controller; - } + for (const auto& device : controller.GetDevices()) { + device->CleanUp(); } - return nullptr; + return controllers.erase(controller.GetTargetId()) == 1; +} + +void ControllerManager::DeleteAllControllers() +{ + unordered_set> values; + ranges::transform(controllers, inserter(values, values.begin()), [] (const auto& controller) { return controller.second; } ); + + for (const auto& controller : values) { + DeleteController(*controller); + } + + assert(controllers.empty()); +} + +AbstractController::piscsi_shutdown_mode ControllerManager::ProcessOnController(int id_data) const +{ + if (const auto& it = ranges::find_if(controllers, [&] (const auto& c) { return (id_data & (1 << c.first)); } ); + it != controllers.end()) { + (*it).second->ProcessOnController(id_data); + + return (*it).second->GetShutdownMode(); + } + + return AbstractController::piscsi_shutdown_mode::NONE; } shared_ptr ControllerManager::FindController(int target_id) const @@ -57,11 +79,15 @@ shared_ptr ControllerManager::FindController(int target_id) return it == controllers.end() ? nullptr : it->second; } +bool ControllerManager::HasController(int target_id) const { + return controllers.contains(target_id); +} + unordered_set> ControllerManager::GetAllDevices() const { unordered_set> devices; - for (const auto& [id, controller] : controllers) { + for (const auto& [_, controller] : controllers) { const auto& d = controller->GetDevices(); devices.insert(d.begin(), d.end()); } @@ -69,12 +95,12 @@ unordered_set> ControllerManager::GetAllDevices() cons return devices; } -void ControllerManager::DeleteAllControllers() +bool ControllerManager::HasDeviceForIdAndLun(int id, int lun) const { - controllers.clear(); + return GetDeviceForIdAndLun(id, lun) != nullptr; } -shared_ptr ControllerManager::GetDeviceByIdAndLun(int id, int lun) const +shared_ptr ControllerManager::GetDeviceForIdAndLun(int id, int lun) const { if (const auto& controller = FindController(id); controller != nullptr) { return controller->GetDeviceForLun(lun); diff --git a/cpp/controllers/controller_manager.h b/cpp/controllers/controller_manager.h index dd145e07..43f6177c 100644 --- a/cpp/controllers/controller_manager.h +++ b/cpp/controllers/controller_manager.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // Keeps track of and manages the controllers // @@ -11,36 +11,41 @@ #pragma once +#include "hal/bus.h" +#include "controllers/abstract_controller.h" #include #include #include -#include "hal/bus.h" using namespace std; -class AbstractController; +class ScsiController; class PrimaryDevice; -class ControllerManager : public enable_shared_from_this +class ControllerManager { - BUS& bus; - - unordered_map> controllers; - public: - explicit ControllerManager(BUS& bus) : bus(bus) {} + ControllerManager() = default; ~ControllerManager() = default; - // Maximum number of controller devices - static const int DEVICE_MAX = 8; - - inline BUS& GetBus() const { return bus; } - bool AttachToScsiController(int, shared_ptr); - bool DeleteController(shared_ptr); - shared_ptr IdentifyController(int) const; - shared_ptr FindController(int) const; - unordered_set> GetAllDevices() const; + bool AttachToController(BUS&, int, shared_ptr); + bool DeleteController(const AbstractController&); void DeleteAllControllers(); - shared_ptr GetDeviceByIdAndLun(int, int) const; + AbstractController::piscsi_shutdown_mode ProcessOnController(int) const; + shared_ptr FindController(int) const; + bool HasController(int) const; + unordered_set> GetAllDevices() const; + bool HasDeviceForIdAndLun(int, int) const; + shared_ptr GetDeviceForIdAndLun(int, int) const; + + static int GetScsiIdMax() { return 8; } + static int GetScsiLunMax() { return 32; } + +private: + + shared_ptr CreateScsiController(BUS&, int) const; + + // Controllers mapped to their device IDs + unordered_map> controllers; }; diff --git a/cpp/controllers/phase_handler.h b/cpp/controllers/phase_handler.h index a50adf39..a7c52035 100644 --- a/cpp/controllers/phase_handler.h +++ b/cpp/controllers/phase_handler.h @@ -3,15 +3,18 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once #include "shared/scsi.h" +#include "shared/piscsi_exceptions.h" +#include +#include - using namespace scsi_defs; +using namespace scsi_defs; class PhaseHandler { @@ -31,7 +34,7 @@ public: virtual void MsgIn() = 0; virtual void MsgOut() = 0; - virtual phase_t Process(int) = 0; + virtual bool Process(int) = 0; protected: @@ -45,4 +48,27 @@ protected: bool IsDataOut() const { return phase == phase_t::dataout; } bool IsMsgIn() const { return phase == phase_t::msgin; } bool IsMsgOut() const { return phase == phase_t::msgout; } + + void ProcessPhase() const + { + try { + phase_executors.at(phase)(); + } + catch(const out_of_range&) { + throw scsi_exception(sense_key::aborted_command); + } + } + +private: + + const unordered_map> phase_executors = { + { phase_t::busfree, [this] () { BusFree(); } }, + { phase_t::selection, [this] () { Selection(); } }, + { phase_t::dataout, [this] () { DataOut(); } }, + { phase_t::datain, [this] () { DataIn(); } }, + { phase_t::command, [this] () { Command(); } }, + { phase_t::status, [this] () { Status(); } }, + { phase_t::msgout, [this] () { MsgOut(); } }, + { phase_t::msgin, [this] () { MsgIn(); } }, + }; }; diff --git a/cpp/controllers/scsi_controller.cpp b/cpp/controllers/scsi_controller.cpp index 20aaa136..e6acca41 100644 --- a/cpp/controllers/scsi_controller.cpp +++ b/cpp/controllers/scsi_controller.cpp @@ -6,7 +6,7 @@ // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) // Copyright (C) 2014-2020 GIMONS // Copyright (C) akuker -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // Licensed under the BSD 3-Clause License. // See LICENSE file in the project root folder. @@ -16,7 +16,9 @@ #include "shared/piscsi_exceptions.h" #include "hal/gpiobus.h" #include "hal/systimer.h" -#include "devices/interfaces/byte_writer.h" +#include "controllers/controller_manager.h" +#include "devices/scsi_host_bridge.h" +#include "devices/scsi_daynaport.h" #include "devices/mode_page_device.h" #include "devices/disk.h" #include "scsi_controller.h" @@ -28,11 +30,8 @@ using namespace scsi_defs; -ScsiController::ScsiController(shared_ptr controller_manager, int target_id) - : AbstractController(controller_manager, target_id, LUN_MAX) +ScsiController::ScsiController(BUS& bus, int target_id) : AbstractController(bus, target_id, ControllerManager::GetScsiLunMax()) { - logger.SetIdAndLun(target_id, -1); - // The initial buffer size will default to either the default buffer size OR // the size of an Ethernet message, whichever is larger. AllocateBuffer(std::max(DEFAULT_BUFFER_SIZE, ETH_FRAME_LEN + 16 + ETH_FCS_LEN)); @@ -44,26 +43,21 @@ void ScsiController::Reset() execstart = 0; identified_lun = -1; + initiator_id = UNKNOWN_INITIATOR_ID; - scsi.atnmsg = false; - scsi.msc = 0; - scsi.msb = {}; - - SetByteTransfer(false); + scsi = {}; } -phase_t ScsiController::Process(int id) +bool ScsiController::Process(int id) { GetBus().Acquire(); if (GetBus().GetRST()) { - logger.Warn("RESET signal received!"); + LogWarn("RESET signal received!"); Reset(); - GetBus().Reset(); - - return GetPhase(); + return false; } initiator_id = id; @@ -72,23 +66,20 @@ phase_t ScsiController::Process(int id) ProcessPhase(); } catch(const scsi_exception&) { - // Any exception should have been handled during the phase processing - logger.Error("Unhandled SCSI error, resetting controller and bus and entering bus free phase"); + LogError("Unhandled SCSI error, resetting controller and bus and entering bus free phase"); Reset(); - GetBus().Reset(); BusFree(); } - return GetPhase(); + return !IsBusFree(); } void ScsiController::BusFree() { if (!IsBusFree()) { - logger.Trace("Bus free phase"); - + LogTrace("Bus Free phase"); SetPhase(phase_t::busfree); GetBus().SetREQ(false); @@ -98,7 +89,7 @@ void ScsiController::BusFree() GetBus().SetBSY(false); // Initialize status and message - SetStatus(status::GOOD); + SetStatus(status::good); SetMessage(0x00); // Initialize ATN message reception status @@ -108,39 +99,6 @@ void ScsiController::BusFree() SetByteTransfer(false); - if (shutdown_mode != piscsi_shutdown_mode::NONE) { - // Prepare the shutdown by flushing all caches - for (const auto& device : GetControllerManager()->GetAllDevices()) { - device->FlushCache(); - } - } - - // When the bus is free PiSCSI or the Pi may be shut down. - // This code has to be executed in the bus free phase and thus has to be located in the controller. - switch(shutdown_mode) { - case piscsi_shutdown_mode::STOP_PISCSI: - logger.Info("PiSCSI shutdown requested"); - exit(EXIT_SUCCESS); - break; - - case piscsi_shutdown_mode::STOP_PI: - logger.Info("Raspberry Pi shutdown requested"); - if (system("init 0") == -1) { - logger.Error("Raspberry Pi shutdown failed: " + string(strerror(errno))); - } - break; - - case piscsi_shutdown_mode::RESTART_PI: - logger.Info("Raspberry Pi restart requested"); - if (system("init 6") == -1) { - logger.Error("Raspberry Pi restart failed: " + string(strerror(errno))); - } - break; - - default: - break; - } - return; } @@ -153,18 +111,7 @@ void ScsiController::BusFree() void ScsiController::Selection() { if (!IsSelection()) { - // A different device controller was selected - if (int id = 1 << GetTargetId(); (static_cast(GetBus().GetDAT()) & id) == 0) { - return; - } - - // Abort if there is no LUN for this controller - if (!GetLunCount()) { - return; - } - - logger.Trace("Selection phase"); - + LogTrace("Selection phase"); SetPhase(phase_t::selection); // Raise BSY and respond @@ -174,6 +121,8 @@ void ScsiController::Selection() // Selection completed if (!GetBus().GetSEL() && GetBus().GetBSY()) { + LogTrace("Selection completed"); + // Message out phase if ATN=1, otherwise command phase if (GetBus().GetATN()) { MsgOut(); @@ -186,8 +135,7 @@ void ScsiController::Selection() void ScsiController::Command() { if (!IsCommand()) { - logger.Trace("Command phase"); - + LogTrace("Command phase"); SetPhase(phase_t::command); GetBus().SetMSG(false); @@ -198,9 +146,9 @@ void ScsiController::Command() if (actual_count == 0) { stringstream s; s << "Received unknown command: $" << setfill('0') << setw(2) << hex << GetBuffer()[0]; - logger.Trace(s.str()); + LogTrace(s.str()); - Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE); + Error(sense_key::illegal_request, asc::invalid_command_operation_code); return; } @@ -210,16 +158,16 @@ void ScsiController::Command() if (actual_count != command_byte_count) { stringstream s; s << "Command byte count mismatch for command $" << setfill('0') << setw(2) << hex << GetBuffer()[0]; - logger.Error(s.str() + ": expected " + to_string(command_byte_count) + " bytes, received" + LogError(s.str() + ": expected " + to_string(command_byte_count) + " bytes, received" + to_string(actual_count) + " byte(s)"); - Error(sense_key::ABORTED_COMMAND); + Error(sense_key::aborted_command); return; } // Command data transfer AllocateCmd(command_byte_count); for (int i = 0; i < command_byte_count; i++) { - GetCmd()[i] = GetBuffer()[i]; + SetCmdByte(i, GetBuffer()[i]); } SetLength(0); @@ -234,9 +182,9 @@ void ScsiController::Execute() s << "Controller is executing " << command_mapping.find(GetOpcode())->second.second << ", CDB $" << setfill('0') << hex; for (int i = 0; i < BUS::GetCommandByteCount(static_cast(GetOpcode())); i++) { - s << setw(2) << GetCmd(i); + s << setw(2) << GetCmdByte(i); } - logger.Debug(s.str()); + LogDebug(s.str()); // Initialization for data transfer ResetOffset(); @@ -245,15 +193,15 @@ void ScsiController::Execute() // Discard pending sense data from the previous command if the current command is not REQUEST SENSE if (GetOpcode() != scsi_command::eCmdRequestSense) { - SetStatus(status::GOOD); + SetStatus(status::good); } int lun = GetEffectiveLun(); if (!HasDeviceForLun(lun)) { if (GetOpcode() != scsi_command::eCmdInquiry && GetOpcode() != scsi_command::eCmdRequestSense) { - logger.Trace("Invalid LUN " + to_string(lun)); + LogTrace("Invalid LUN " + to_string(lun)); - Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_LUN); + Error(sense_key::illegal_request, asc::invalid_lun); return; } @@ -265,7 +213,7 @@ void ScsiController::Execute() // SCSI-2 4.4.3 Incorrect logical unit handling if (GetOpcode() == scsi_command::eCmdInquiry && !HasDeviceForLun(lun)) { - logger.Trace("Reporting LUN" + to_string(GetEffectiveLun()) + " as not supported"); + LogTrace("Reporting LUN" + to_string(GetEffectiveLun()) + " as not supported"); GetBuffer().data()[0] = 0x7f; @@ -279,7 +227,7 @@ void ScsiController::Execute() device->SetStatusCode(0); } - if (device->CheckReservation(initiator_id, GetOpcode(), GetCmd(4) & 0x01)) { + if (device->CheckReservation(initiator_id, GetOpcode(), GetCmdByte(4) & 0x01)) { try { device->Dispatch(GetOpcode()); } @@ -288,7 +236,7 @@ void ScsiController::Execute() } } else { - Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT); + Error(sense_key::aborted_command, asc::no_additional_sense_information, status::reservation_conflict); } } @@ -304,9 +252,8 @@ void ScsiController::Status() } stringstream s; - s << "Status Phase, status is $" << setfill('0') << setw(2) << hex << static_cast(GetStatus()); - logger.Trace(s.str()); - + s << "Status phase, status is $" << setfill('0') << setw(2) << hex << static_cast(GetStatus()); + LogTrace(s.str()); SetPhase(phase_t::status); // Signal line operated by the target @@ -329,8 +276,7 @@ void ScsiController::Status() void ScsiController::MsgIn() { if (!IsMsgIn()) { - logger.Trace("Message In phase"); - + LogTrace("Message In phase"); SetPhase(phase_t::msgin); GetBus().SetMSG(true); @@ -347,8 +293,6 @@ void ScsiController::MsgIn() void ScsiController::MsgOut() { if (!IsMsgOut()) { - logger.Trace("Message Out phase"); - // process the IDENTIFY message if (IsSelection()) { scsi.atnmsg = true; @@ -356,6 +300,7 @@ void ScsiController::MsgOut() scsi.msb = {}; } + LogTrace("Message Out phase"); SetPhase(phase_t::msgout); GetBus().SetMSG(true); @@ -387,8 +332,7 @@ void ScsiController::DataIn() return; } - logger.Trace("Entering Data In phase"); - + LogTrace("Data In phase"); SetPhase(phase_t::datain); GetBus().SetMSG(false); @@ -417,11 +361,9 @@ void ScsiController::DataOut() return; } - logger.Trace("Data Out phase"); - + LogTrace("Data Out phase"); SetPhase(phase_t::dataout); - // Signal line operated by the target GetBus().SetMSG(false); GetBus().SetCD(false); GetBus().SetIO(false); @@ -441,7 +383,6 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status) // Reset check if (GetBus().GetRST()) { Reset(); - GetBus().Reset(); return; } @@ -453,9 +394,9 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status) } int lun = GetEffectiveLun(); - if (!HasDeviceForLun(lun) || asc == asc::INVALID_LUN) { + if (!HasDeviceForLun(lun) || asc == asc::invalid_lun) { if (!HasDeviceForLun(0)) { - logger.Error("No LUN 0"); + LogError("No LUN 0"); SetStatus(status); SetMessage(0x00); @@ -468,11 +409,11 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status) lun = 0; } - if (sense_key != sense_key::NO_SENSE || asc != asc::NO_ADDITIONAL_SENSE_INFORMATION) { + if (sense_key != sense_key::no_sense || asc != asc::no_additional_sense_information) { stringstream s; s << setfill('0') << setw(2) << hex << "Error status: Sense Key $" << static_cast(sense_key) << ", ASC $" << static_cast(asc); - logger.Debug(s.str()); + LogDebug(s.str()); // Set Sense Key and ASC for a subsequent REQUEST SENSE GetDeviceForLun(lun)->SetStatusCode((static_cast(sense_key) << 16) | (static_cast(asc) << 8)); @@ -481,7 +422,7 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status) SetStatus(status); SetMessage(0x00); - logger.Trace("Error (to status phase)"); + LogTrace("Error (to status phase)"); Status(); } @@ -492,7 +433,7 @@ void ScsiController::Send() assert(GetBus().GetIO()); if (HasValidLength()) { - logger.Trace("Sending data, offset: " + to_string(GetOffset()) + ", length: " + to_string(GetLength())); + LogTrace("Sending data, offset: " + to_string(GetOffset()) + ", length: " + to_string(GetLength())); // The delay should be taken from the respective LUN, but as there are no Daynaport drivers for // LUNs other than 0 this work-around works. @@ -500,7 +441,7 @@ void ScsiController::Send() HasDeviceForLun(0) ? GetDeviceForLun(0)->GetSendDelay() : 0); len != static_cast(GetLength())) { // If you cannot send all, move to status phase - Error(sense_key::ABORTED_COMMAND); + Error(sense_key::aborted_command); return; } @@ -510,33 +451,30 @@ void ScsiController::Send() } DecrementBlocks(); - bool result = true; // Processing after data collection (read/data-in only) - if (IsDataIn() && GetBlocks() != 0) { + if (IsDataIn() && HasBlocks()) { // set next buffer (set offset, length) - result = XferIn(GetBuffer()); - logger.Trace("Processing after data collection. Blocks: " + to_string(GetBlocks())); + if (!XferIn(GetBuffer())) { + // If result FALSE, move to status phase + Error(sense_key::aborted_command); + return; + } + + LogTrace("Processing after data collection"); } - // If result FALSE, move to status phase - if (!result) { - Error(sense_key::ABORTED_COMMAND); - return; - } - - // Continue sending if block !=0 - if (GetBlocks() != 0){ - logger.Trace("Continuing to send. Blocks: " + to_string(GetBlocks())); + // Continue sending if blocks != 0 + if (HasBlocks()) { + LogTrace("Continuing to send"); assert(HasValidLength()); assert(GetOffset() == 0); return; } // Move to next phase - logger.Trace("Moving to next phase: " + string(BUS::GetPhaseStrRaw(GetPhase()))); + LogTrace("All data transferred, moving to next phase: " + string(BUS::GetPhaseStrRaw(GetPhase()))); switch (GetPhase()) { - // Message in phase case phase_t::msgin: // Completed sending response to extended message of IDENTIFY message if (scsi.atnmsg) { @@ -551,13 +489,11 @@ void ScsiController::Send() } break; - // Data-in Phase case phase_t::datain: // status phase Status(); break; - // status phase case phase_t::status: // Message in phase SetLength(1); @@ -578,13 +514,13 @@ void ScsiController::Receive() assert(!GetBus().GetIO()); if (HasValidLength()) { - logger.Trace("Receiving data, transfer length: " + to_string(GetLength()) + " byte(s)"); + LogTrace("Receiving data, transfer length: " + to_string(GetLength()) + " byte(s)"); // If not able to receive all, move to status phase if (uint32_t len = GetBus().ReceiveHandShake(GetBuffer().data() + GetOffset(), GetLength()); len != GetLength()) { - logger.Error("Not able to receive " + to_string(GetLength()) + " byte(s) of data, only received " + LogError("Not able to receive " + to_string(GetLength()) + " byte(s) of data, only received " + to_string(len)); - Error(sense_key::ABORTED_COMMAND); + Error(sense_key::aborted_command); return; } } @@ -603,10 +539,10 @@ void ScsiController::Receive() bool result = true; // Processing after receiving data (by phase) - logger.Trace("Phase: " + string(BUS::GetPhaseStrRaw(GetPhase()))); + LogTrace("Phase: " + string(BUS::GetPhaseStrRaw(GetPhase()))); switch (GetPhase()) { case phase_t::dataout: - if (GetBlocks() == 0) { + if (!HasBlocks()) { // End with this buffer result = XferOut(false); } else { @@ -633,12 +569,12 @@ void ScsiController::Receive() // If result FALSE, move to status phase if (!result) { - Error(sense_key::ABORTED_COMMAND); + Error(sense_key::aborted_command); return; } - // Continue to receive if block != 0 - if (GetBlocks() != 0) { + // Continue to receive if blocks != 0 + if (HasBlocks()) { assert(HasValidLength()); assert(GetOffset() == 0); return; @@ -692,7 +628,7 @@ void ScsiController::ReceiveBytes() bool result = true; // Processing after receiving data (by phase) - logger.Trace("Phase: " + string(BUS::GetPhaseStrRaw(GetPhase()))); + LogTrace("Phase: " + string(BUS::GetPhaseStrRaw(GetPhase()))); switch (GetPhase()) { case phase_t::dataout: result = XferOut(false); @@ -716,7 +652,7 @@ void ScsiController::ReceiveBytes() // If result FALSE, move to status phase if (!result) { - Error(sense_key::ABORTED_COMMAND); + Error(sense_key::aborted_command); return; } @@ -752,7 +688,7 @@ bool ScsiController::XferOut(bool cont) SetByteTransfer(false); auto device = GetDeviceForLun(GetEffectiveLun()); - return device != nullptr ? device->WriteByteSequence(GetBuffer(), count) : false; + return device != nullptr ? device->WriteByteSequence(span(GetBuffer().data(), count)) : false; } void ScsiController::DataOutNonBlockOriented() @@ -760,7 +696,6 @@ void ScsiController::DataOutNonBlockOriented() assert(IsDataOut()); switch (GetOpcode()) { - // TODO Check why these cases are needed case scsi_command::eCmdWrite6: case scsi_command::eCmdWrite10: case scsi_command::eCmdWrite16: @@ -777,7 +712,7 @@ void ScsiController::DataOutNonBlockOriented() device->ModeSelect(GetOpcode(), GetCmd(), GetBuffer(), GetOffset()); } else { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE); + throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code); } } break; @@ -790,7 +725,7 @@ void ScsiController::DataOutNonBlockOriented() stringstream s; s << "Unexpected Data Out phase for command $" << setfill('0') << setw(2) << hex << static_cast(GetOpcode()); - logger.Warn(s.str()); + LogWarn(s.str()); break; } } @@ -807,9 +742,9 @@ bool ScsiController::XferIn(vector& buf) stringstream s; s << "Command: $" << setfill('0') << setw(2) << hex << static_cast(GetOpcode()); - logger.Trace(s.str()); + LogTrace(s.str()); - int lun = GetEffectiveLun(); + const int lun = GetEffectiveLun(); if (!HasDeviceForLun(lun)) { return false; } @@ -821,7 +756,7 @@ bool ScsiController::XferIn(vector& buf) case scsi_command::eCmdRead16: // Read from disk try { - SetLength(dynamic_pointer_cast(GetDeviceForLun(lun))->Read(GetCmd(), buf, GetNext())); + SetLength(dynamic_pointer_cast(GetDeviceForLun(lun))->Read(buf, GetNext())); } catch(const scsi_exception&) { // If there is an error, go to the status phase @@ -878,13 +813,23 @@ bool ScsiController::XferOutBlockOriented(bool cont) case scsi_command::eCmdWrite6: case scsi_command::eCmdWrite10: case scsi_command::eCmdWrite16: - // TODO Verify has to verify, not to write + // TODO Verify has to verify, not to write, see https://github.com/PiSCSI/piscsi/issues/807 case scsi_command::eCmdVerify10: case scsi_command::eCmdVerify16: { - // Special case for SCBR and SCDP - if (auto byte_writer = dynamic_pointer_cast(device); byte_writer) { - if (!byte_writer->WriteBytes(GetCmd(), GetBuffer(), GetLength())) { + // TODO Get rid of this special case for SCBR + if (auto bridge = dynamic_pointer_cast(device); bridge) { + if (!bridge->ReadWrite(GetCmd(), GetBuffer())) { + return false; + } + + ResetOffset(); + break; + } + + // TODO Get rid of this special case for SCDP + if (auto daynaport = dynamic_pointer_cast(device); daynaport) { + if (!daynaport->Write(GetCmd(), GetBuffer())) { return false; } @@ -898,7 +843,7 @@ bool ScsiController::XferOutBlockOriented(bool cont) } try { - disk->Write(GetCmd(), GetBuffer(), GetNext() - 1); + disk->Write(GetBuffer(), GetNext() - 1); } catch(const scsi_exception& e) { Error(e.get_sense_key(), e.get_asc()); @@ -918,14 +863,14 @@ bool ScsiController::XferOutBlockOriented(bool cont) } case scsi_command::eCmdSetMcastAddr: - logger.Trace("Done with DaynaPort Set Multicast Address"); + LogTrace("Done with DaynaPort Set Multicast Address"); break; default: stringstream s; s << "Received an unexpected command ($" << setfill('0') << setw(2) << hex << static_cast(GetOpcode()) << ")"; - logger.Warn(s.str()); + LogWarn(s.str()); break; } @@ -934,15 +879,15 @@ bool ScsiController::XferOutBlockOriented(bool cont) void ScsiController::ProcessCommand() { - uint32_t len = GPIOBUS::GetCommandByteCount(GetBuffer()[0]); + const uint32_t len = GPIOBUS::GetCommandByteCount(GetBuffer()[0]); stringstream s; s << "CDB=$" << setfill('0') << setw(2) << hex; for (uint32_t i = 0; i < len; i++) { - GetCmd()[i] = GetBuffer()[i]; - s << GetCmd(i); + SetCmdByte(i, GetBuffer()[i]); + s << GetCmdByte(i); } - logger.Trace(s.str()); + LogTrace(s.str()); Execute(); } @@ -954,13 +899,13 @@ void ScsiController::ParseMessage() const uint8_t message_type = scsi.msb[i]; if (message_type == 0x06) { - logger.Trace("Received ABORT message"); + LogTrace("Received ABORT message"); BusFree(); return; } if (message_type == 0x0C) { - logger.Trace("Received BUS DEVICE RESET message"); + LogTrace("Received BUS DEVICE RESET message"); scsi.syncoffset = 0; if (auto device = GetDeviceForLun(identified_lun); device != nullptr) { device->DiscardReservation(); @@ -971,11 +916,11 @@ void ScsiController::ParseMessage() if (message_type >= 0x80) { identified_lun = static_cast(message_type) & 0x1F; - logger.Trace("Received IDENTIFY message for LUN " + to_string(identified_lun)); + LogTrace("Received IDENTIFY message for LUN " + to_string(identified_lun)); } if (message_type == 0x01) { - logger.Trace("Received EXTENDED MESSAGE"); + LogTrace("Received EXTENDED MESSAGE"); // Check only when synchronous transfer is possible if (!scsi.syncenable || scsi.msb[i + 2] != 0x01) { @@ -1046,4 +991,4 @@ void ScsiController::Sleep() SysTimer::SleepUsec(MIN_EXEC_TIME - time); } execstart = 0; -} \ No newline at end of file +} diff --git a/cpp/controllers/scsi_controller.h b/cpp/controllers/scsi_controller.h index fd5093bc..68a4be25 100644 --- a/cpp/controllers/scsi_controller.h +++ b/cpp/controllers/scsi_controller.h @@ -15,8 +15,6 @@ #pragma once #include "shared/scsi.h" -#include "controller_manager.h" -#include "devices/device_logger.h" #include "abstract_controller.h" #include @@ -52,20 +50,17 @@ class ScsiController : public AbstractController public: - // Maximum number of logical units - static inline const int LUN_MAX = 32; - - explicit ScsiController(shared_ptr, int); + ScsiController(BUS&, int); ~ScsiController() override = default; void Reset() override; - phase_t Process(int) override; + bool Process(int) override; int GetEffectiveLun() const override; - void Error(scsi_defs::sense_key sense_key, scsi_defs::asc asc = scsi_defs::asc::NO_ADDITIONAL_SENSE_INFORMATION, - scsi_defs::status status = scsi_defs::status::CHECK_CONDITION) override; + void Error(scsi_defs::sense_key sense_key, scsi_defs::asc asc = scsi_defs::asc::no_additional_sense_information, + scsi_defs::status status = scsi_defs::status::check_condition) override; int GetInitiatorId() const override { return initiator_id; } @@ -79,16 +74,8 @@ public: void DataIn() override; void DataOut() override; - // TODO Make non-virtual private as soon as SysTimer calls do not segfault anymore on a regular PC, - // e.g. by using ifdef __arm__. Currently the unit tests require this method to be public. - virtual void Execute(); - - void ScheduleShutdown(piscsi_shutdown_mode mode) override { shutdown_mode = mode; } - private: - DeviceLogger logger; - // Execution start time uint32_t execstart = 0; @@ -109,6 +96,9 @@ private: void DataOutNonBlockOriented(); void Receive(); + // TODO Make non-virtual as soon as SysTimer calls do not segfault anymore on a regular PC, e.g. by using ifdef __arm__. + virtual void Execute(); + void ProcessCommand(); void ParseMessage(); void ProcessMessage(); @@ -116,7 +106,5 @@ private: void Sleep(); scsi_t scsi = {}; - - AbstractController::piscsi_shutdown_mode shutdown_mode = AbstractController::piscsi_shutdown_mode::NONE; }; diff --git a/cpp/devices/cd_track.h b/cpp/devices/cd_track.h index cd712484..02302323 100644 --- a/cpp/devices/cd_track.h +++ b/cpp/devices/cd_track.h @@ -14,6 +14,7 @@ #pragma once +#include #include using namespace std; diff --git a/cpp/devices/cfilesystem.cpp b/cpp/devices/cfilesystem.cpp index 7531c70a..381ed933 100644 --- a/cpp/devices/cfilesystem.cpp +++ b/cpp/devices/cfilesystem.cpp @@ -17,8 +17,8 @@ // //--------------------------------------------------------------------------- -#include "shared/log.h" #include "cfilesystem.h" +#include #include #include #include @@ -3507,18 +3507,18 @@ int CFileSys::GetDPB(uint32_t nUnit, Human68k::dpb_t* pDpb) const // Acquire sector data if (!m_cEntry.GetCapacityCache(nUnit, &cap)) { // Carry out an extra media check here because it may be skipped when doing a manual eject - if (!m_cEntry.isEnable(nUnit)) - goto none; - // Media check - if (m_cEntry.isMediaOffline(nUnit)) - goto none; - - // Get drive status - m_cEntry.GetCapacity(nUnit, &cap); + if (!m_cEntry.isEnable(nUnit) || m_cEntry.isMediaOffline(nUnit)) { + cap.clusters = 4; // This is totally fine, right? + cap.sectors = 64; + cap.bytes = 512; + } + else { + // Get drive status + m_cEntry.GetCapacity(nUnit, &cap); + } } } else { - none: cap.clusters = 4; // This is totally fine, right? cap.sectors = 64; cap.bytes = 512; diff --git a/cpp/devices/cfilesystem.h b/cpp/devices/cfilesystem.h index 8c4c8703..e44339a2 100644 --- a/cpp/devices/cfilesystem.h +++ b/cpp/devices/cfilesystem.h @@ -13,6 +13,15 @@ #pragma once +#include +#include +#include +#include +#include +#include +#include +#include + using TCHAR = char; static const int FILEPATH_MAX = 260; diff --git a/cpp/devices/ctapdriver.cpp b/cpp/devices/ctapdriver.cpp index 2c868e51..c010c93c 100644 --- a/cpp/devices/ctapdriver.cpp +++ b/cpp/devices/ctapdriver.cpp @@ -9,126 +9,71 @@ // //--------------------------------------------------------------------------- -#include "shared/log.h" #include "shared/piscsi_util.h" -#include "shared/piscsi_exceptions.h" +#include "shared/network_util.h" #include #include #include #include "ctapdriver.h" +#include #include #include #include #ifdef __linux__ -#include -#include #include #include #endif using namespace std; using namespace piscsi_util; +using namespace network_util; -//--------------------------------------------------------------------------- -// -// Initialization -// -//--------------------------------------------------------------------------- -static bool br_setif(int br_socket_fd, const char* bridgename, const char* ifname, bool add) { +const string CTapDriver::BRIDGE_NAME = "piscsi_bridge"; + +static string br_setif(int br_socket_fd, const string& bridgename, const string& ifname, bool add) { #ifndef __linux__ - return false; + return "if_nametoindex: Linux is required"; #else ifreq ifr; - ifr.ifr_ifindex = if_nametoindex(ifname); + ifr.ifr_ifindex = if_nametoindex(ifname.c_str()); if (ifr.ifr_ifindex == 0) { - LOGERROR("Can't if_nametoindex %s: %s", ifname, strerror(errno)) - return false; + return "Can't if_nametoindex " + ifname; } - strncpy(ifr.ifr_name, bridgename, IFNAMSIZ - 1); + strncpy(ifr.ifr_name, bridgename.c_str(), IFNAMSIZ - 1); //NOSONAR Using strncpy is safe if (ioctl(br_socket_fd, add ? SIOCBRADDIF : SIOCBRDELIF, &ifr) < 0) { - LOGERROR("Can't ioctl %s: %s", add ? "SIOCBRADDIF" : "SIOCBRDELIF", strerror(errno)) - return false; + return "Can't ioctl " + string(add ? "SIOCBRADDIF" : "SIOCBRDELIF"); } - return true; + return ""; #endif } -CTapDriver::~CTapDriver() -{ - if (m_hTAP != -1) { - if (int br_socket_fd; (br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) { - LOGERROR("Can't open bridge socket: %s", strerror(errno)) - } else { - LOGDEBUG("brctl delif %s piscsi0", BRIDGE_NAME) - if (!br_setif(br_socket_fd, BRIDGE_NAME, "piscsi0", false)) { //NOSONAR No exception is raised here - LOGWARN("Warning: Removing piscsi0 from the bridge failed.") - LOGWARN("You may need to manually remove the piscsi0 tap device from the bridge") - } - close(br_socket_fd); - } - - // Release TAP defice - close(m_hTAP); - } - - if (m_pcap_dumper != nullptr) { - pcap_dump_close(m_pcap_dumper); - } - - if (m_pcap != nullptr) { - pcap_close(m_pcap); - } -} - -static bool ip_link(int fd, const char* ifname, bool up) { +string ip_link(int fd, const char* ifname, bool up) { #ifndef __linux__ - return false; + return "Can't ip_link: Linux is required"; #else ifreq ifr; - strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); // Need to save room for null terminator - int err = ioctl(fd, SIOCGIFFLAGS, &ifr); - if (err) { - LOGERROR("Can't ioctl SIOCGIFFLAGS: %s", strerror(errno)) - return false; + strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); //NOSONAR Using strncpy is safe + if (ioctl(fd, SIOCGIFFLAGS, &ifr)) { + return "Can't ioctl SIOCGIFFLAGS"; } ifr.ifr_flags &= ~IFF_UP; if (up) { ifr.ifr_flags |= IFF_UP; } - err = ioctl(fd, SIOCSIFFLAGS, &ifr); - if (err) { - LOGERROR("Can't ioctl SIOCSIFFLAGS: %s", strerror(errno)) - return false; + if (ioctl(fd, SIOCSIFFLAGS, &ifr)) { + return "Can't ioctl SIOCSIFFLAGS"; } - return true; + return ""; #endif } -static bool is_interface_up(string_view interface) { - string file = "/sys/class/net/"; - file += interface; - file += "/carrier"; - - bool status = true; - FILE *fp = fopen(file.c_str(), "r"); - if (!fp || fgetc(fp) != '1') { - status = false; - } - - if (fp) { - fclose(fp); - } - - return status; -} - -bool CTapDriver::Init(const unordered_map& const_params) +bool CTapDriver::Init(const param_map& const_params) { #ifndef __linux__ return false; #else - unordered_map params = const_params; + param_map params = const_params; stringstream s(params["interface"]); string interface; while (getline(s, interface, ',')) { @@ -136,36 +81,33 @@ bool CTapDriver::Init(const unordered_map& const_params) } inet = params["inet"]; - LOGTRACE("Opening tap device") + spdlog::trace("Opening tap device"); // TAP device initilization if ((m_hTAP = open("/dev/net/tun", O_RDWR)) < 0) { - LOGERROR("Can't open tun: %s", strerror(errno)) + LogErrno("Can't open tun"); return false; } - LOGTRACE("Opened tap device %d", m_hTAP) - // IFF_NO_PI for no extra packet information ifreq ifr = {}; ifr.ifr_flags = IFF_TAP | IFF_NO_PI; - string dev = "piscsi0"; - strncpy(ifr.ifr_name, dev.c_str(), IFNAMSIZ - 1); + strncpy(ifr.ifr_name, "piscsi0", IFNAMSIZ - 1); //NOSONAR Using strncpy is safe - LOGTRACE("Going to open %s", ifr.ifr_name) + spdlog::trace("Going to open " + string(ifr.ifr_name)); - int ret = ioctl(m_hTAP, TUNSETIFF, (void *)&ifr); + const int ret = ioctl(m_hTAP, TUNSETIFF, (void *)&ifr); if (ret < 0) { - LOGERROR("Can't ioctl TUNSETIFF: %s", strerror(errno)) + LogErrno("Can't ioctl TUNSETIFF"); close(m_hTAP); return false; } - LOGTRACE("Return code from ioctl was %d", ret) + spdlog::trace("Return code from ioctl was " + to_string(ret)); const int ip_fd = socket(PF_INET, SOCK_DGRAM, 0); if (ip_fd < 0) { - LOGERROR("Can't open ip socket: %s", strerror(errno)) + LogErrno("Can't open ip socket"); close(m_hTAP); return false; @@ -173,179 +115,72 @@ bool CTapDriver::Init(const unordered_map& const_params) const int br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0); if (br_socket_fd < 0) { - LOGERROR("Can't open bridge socket: %s", strerror(errno)) + LogErrno("Can't open bridge socket"); close(m_hTAP); close(ip_fd); return false; } + auto cleanUp = [&] (const string& error) { + LogErrno(error); + close(m_hTAP); + close(ip_fd); + close(br_socket_fd); + return false; + }; + // Check if the bridge has already been created - string sys_file = "/sys/class/net/"; - sys_file += BRIDGE_NAME; - if (access(sys_file.c_str(), F_OK)) { - LOGINFO("%s is not yet available", BRIDGE_NAME) + // TODO Find an alternative to accessing a file, there is most likely a system call/ioctl + if (access(string("/sys/class/net/" + BRIDGE_NAME).c_str(), F_OK)) { + spdlog::trace("Checking which interface is available for creating the bridge " + BRIDGE_NAME); - LOGTRACE("Checking which interface is available for creating the bridge") - - string bridge_interface; - for (const string& iface : interfaces) { - if (is_interface_up(iface)) { - LOGTRACE("%s", string("Interface " + iface + " is up").c_str()) - - bridge_interface = iface; - break; - } - else { - LOGTRACE("%s", string("Interface " + iface + " is not available or is not up").c_str()) - } + const auto& it = ranges::find_if(interfaces, [] (const string& iface) { return IsInterfaceUp(iface); } ); + if (it == interfaces.end()) { + return cleanUp("No interface is up, not creating bridge " + BRIDGE_NAME); } - if (bridge_interface.empty()) { - LOGERROR("No interface is up, not creating bridge") - return false; - } + const string bridge_interface = *it; - LOGINFO("Creating %s for interface %s", BRIDGE_NAME, bridge_interface.c_str()) + spdlog::info("Creating " + BRIDGE_NAME + " for interface " + bridge_interface); if (bridge_interface == "eth0") { - LOGTRACE("brctl addbr %s", BRIDGE_NAME) - - if (ioctl(br_socket_fd, SIOCBRADDBR, BRIDGE_NAME) < 0) { - LOGERROR("Can't ioctl SIOCBRADDBR: %s", strerror(errno)) - - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; - } - - LOGTRACE("brctl addif %s %s", BRIDGE_NAME, bridge_interface.c_str()) - - if (!br_setif(br_socket_fd, BRIDGE_NAME, bridge_interface.c_str(), true)) { - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; + if (const string error = SetUpEth0(br_socket_fd, bridge_interface); !error.empty()) { + return cleanUp(error); } } - else { - string address = inet; - string netmask = "255.255.255.0"; //NOSONAR This hardcoded IP address is safe - if (size_t separatorPos = inet.find('/'); separatorPos != string::npos) { - address = inet.substr(0, separatorPos); - - int m; - if (!GetAsUnsignedInt(inet.substr(separatorPos + 1), m) || m < 8 || m > 32) { - LOGERROR("Invalid CIDR netmask notation '%s'", inet.substr(separatorPos + 1).c_str()) - - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; - } - - // long long is required for compatibility with 32 bit platforms - const auto mask = (long long)(pow(2, 32) - (1 << (32 - m))); - netmask = to_string((mask >> 24) & 0xff) + '.' + to_string((mask >> 16) & 0xff) + '.' + - to_string((mask >> 8) & 0xff) + '.' + to_string(mask & 0xff); - - } - - LOGTRACE("brctl addbr %s", BRIDGE_NAME) - - if (ioctl(br_socket_fd, SIOCBRADDBR, BRIDGE_NAME) < 0) { - LOGERROR("Can't ioctl SIOCBRADDBR: %s", strerror(errno)) - - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; - } - - ifreq ifr_a; - ifr_a.ifr_addr.sa_family = AF_INET; - strncpy(ifr_a.ifr_name, BRIDGE_NAME, IFNAMSIZ); - if (auto addr = (sockaddr_in*)&ifr_a.ifr_addr; - inet_pton(AF_INET, address.c_str(), &addr->sin_addr) != 1) { - LOGERROR("Can't convert '%s' into a network address: %s", address.c_str(), strerror(errno)) - - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; - } - - ifreq ifr_n; - ifr_n.ifr_addr.sa_family = AF_INET; - strncpy(ifr_n.ifr_name, BRIDGE_NAME, IFNAMSIZ); - if (auto mask = (sockaddr_in*)&ifr_n.ifr_addr; - inet_pton(AF_INET, netmask.c_str(), &mask->sin_addr) != 1) { - LOGERROR("Can't convert '%s' into a netmask: %s", netmask.c_str(), strerror(errno)) - - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; - } - - LOGTRACE("ip address add %s dev %s", inet.c_str(), BRIDGE_NAME) - - if (ioctl(ip_fd, SIOCSIFADDR, &ifr_a) < 0 || ioctl(ip_fd, SIOCSIFNETMASK, &ifr_n) < 0) { - LOGERROR("Can't ioctl SIOCSIFADDR or SIOCSIFNETMASK: %s", strerror(errno)) - - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; - } + else if (const string error = SetUpNonEth0(br_socket_fd, ip_fd, inet); !error.empty()) { + return cleanUp(error); } - LOGTRACE("ip link set dev %s up", BRIDGE_NAME) + spdlog::trace(">ip link set dev " + BRIDGE_NAME + " up"); - if (!ip_link(ip_fd, BRIDGE_NAME, true)) { - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; + if (const string error = ip_link(ip_fd, BRIDGE_NAME.c_str(), true); !error.empty()) { + return cleanUp(error); } } - else - { - LOGINFO("%s is already available", BRIDGE_NAME) + else { + spdlog::info(BRIDGE_NAME + " is already available"); } - LOGTRACE("ip link set piscsi0 up") + spdlog::trace(">ip link set piscsi0 up"); - if (!ip_link(ip_fd, "piscsi0", true)) { - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; + if (const string error = ip_link(ip_fd, "piscsi0", true); !error.empty()) { + return cleanUp(error); } - LOGTRACE("brctl addif %s piscsi0", BRIDGE_NAME) + spdlog::trace(">brctl addif " + BRIDGE_NAME + " piscsi0"); - if (!br_setif(br_socket_fd, BRIDGE_NAME, "piscsi0", true)) { - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; + if (const string error = br_setif(br_socket_fd, BRIDGE_NAME, "piscsi0", true); !error.empty()) { + return cleanUp(error); } - // Get MAC address - LOGTRACE("Getting the MAC address") + spdlog::trace("Getting the MAC address"); ifr.ifr_addr.sa_family = AF_INET; if (ioctl(m_hTAP, SIOCGIFHWADDR, &ifr) < 0) { - LOGERROR("Can't ioctl SIOCGIFHWADDR: %s", strerror(errno)) - - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; + return cleanUp("Can't ioctl SIOCGIFHWADDR"); } - LOGTRACE("Got the MAC") // Save MAC address memcpy(m_MacAddr.data(), ifr.ifr_hwaddr.sa_data, m_MacAddr.size()); @@ -353,50 +188,124 @@ bool CTapDriver::Init(const unordered_map& const_params) close(ip_fd); close(br_socket_fd); - LOGINFO("Tap device %s created", ifr.ifr_name) + spdlog::info("Tap device " + string(ifr.ifr_name) + " created"); return true; #endif } -void CTapDriver::OpenDump(const string& path) { - if (m_pcap == nullptr) { - m_pcap = pcap_open_dead(DLT_EN10MB, 65535); - } - if (m_pcap_dumper != nullptr) { - pcap_dump_close(m_pcap_dumper); - } - m_pcap_dumper = pcap_dump_open(m_pcap, path.c_str()); - if (m_pcap_dumper == nullptr) { - LOGERROR("Can't open pcap file: %s", pcap_geterr(m_pcap)) - throw io_exception("Can't open pcap file"); - } +void CTapDriver::CleanUp() const +{ + if (m_hTAP != -1) { + if (const int br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0); br_socket_fd < 0) { + LogErrno("Can't open bridge socket"); + } else { + spdlog::trace(">brctl delif " + BRIDGE_NAME + " piscsi0"); + if (const string error = br_setif(br_socket_fd, BRIDGE_NAME, "piscsi0", false); !error.empty()) { + spdlog::warn("Warning: Removing piscsi0 from the bridge failed: " + error); + spdlog::warn("You may need to manually remove the piscsi0 tap device from the bridge"); + } + close(br_socket_fd); + } - LOGTRACE("%s Opened %s for dumping", __PRETTY_FUNCTION__, path.c_str()) + // Release TAP device + close(m_hTAP); + } } -bool CTapDriver::Enable() const +pair CTapDriver::ExtractAddressAndMask(const string& s) +{ + string address = s; + string netmask = "255.255.255.0"; //NOSONAR This hardcoded IP address is safe + if (const auto& components = Split(s, '/', 2); components.size() == 2) { + address = components[0]; + + int m; + if (!GetAsUnsignedInt(components[1], m) || m < 8 || m > 32) { + spdlog::error("Invalid CIDR netmask notation '" + components[1] + "'"); + return { "", "" }; + } + + // long long is required for compatibility with 32 bit platforms + const auto mask = (long long)(pow(2, 32) - (1 << (32 - m))); + netmask = to_string((mask >> 24) & 0xff) + '.' + to_string((mask >> 16) & 0xff) + '.' + + to_string((mask >> 8) & 0xff) + '.' + to_string(mask & 0xff); + } + + return { address, netmask }; +} + +string CTapDriver::SetUpEth0(int socket_fd, const string& bridge_interface) +{ +#ifdef __linux__ + spdlog::trace(">brctl addbr " + BRIDGE_NAME); + + if (ioctl(socket_fd, SIOCBRADDBR, BRIDGE_NAME.c_str()) < 0) { + return "Can't ioctl SIOCBRADDBR"; + } + + spdlog::trace(">brctl addif " + BRIDGE_NAME + " " + bridge_interface); + + if (const string error = br_setif(socket_fd, BRIDGE_NAME, bridge_interface, true); !error.empty()) { + return error; + } +#endif + + return ""; +} + +string CTapDriver::SetUpNonEth0(int socket_fd, int ip_fd, const string& s) +{ +#ifdef __linux__ + const auto [address, netmask] = ExtractAddressAndMask(s); + if (address.empty() || netmask.empty()) { + return "Error extracting inet address and netmask"; + } + + spdlog::trace(">brctl addbr " + BRIDGE_NAME); + + if (ioctl(socket_fd, SIOCBRADDBR, BRIDGE_NAME.c_str()) < 0) { + return "Can't ioctl SIOCBRADDBR"; + } + + ifreq ifr_a; + ifr_a.ifr_addr.sa_family = AF_INET; + strncpy(ifr_a.ifr_name, BRIDGE_NAME.c_str(), IFNAMSIZ - 1); //NOSONAR Using strncpy is safe + if (auto addr = (sockaddr_in*)&ifr_a.ifr_addr; + inet_pton(AF_INET, address.c_str(), &addr->sin_addr) != 1) { + return "Can't convert '" + address + "' into a network address"; + } + + ifreq ifr_n; + ifr_n.ifr_addr.sa_family = AF_INET; + strncpy(ifr_n.ifr_name, BRIDGE_NAME.c_str(), IFNAMSIZ - 1); //NOSONAR Using strncpy is safe + if (auto mask = (sockaddr_in*)&ifr_n.ifr_addr; + inet_pton(AF_INET, netmask.c_str(), &mask->sin_addr) != 1) { + return "Can't convert '" + netmask + "' into a netmask"; + } + + spdlog::trace(">ip address add " + s + " dev " + BRIDGE_NAME); + + if (ioctl(ip_fd, SIOCSIFADDR, &ifr_a) < 0 || ioctl(ip_fd, SIOCSIFNETMASK, &ifr_n) < 0) { + return "Can't ioctl SIOCSIFADDR or SIOCSIFNETMASK"; + } +#endif + + return ""; +} + +string CTapDriver::IpLink(bool enable) const { const int fd = socket(PF_INET, SOCK_DGRAM, 0); - LOGDEBUG("%s: ip link set piscsi0 up", __PRETTY_FUNCTION__) - const bool result = ip_link(fd, "piscsi0", true); + spdlog::trace(string(">ip link set piscsi0 ") + (enable ? "up" : "down")); + const string result = ip_link(fd, "piscsi0", enable); close(fd); return result; } -bool CTapDriver::Disable() const +void CTapDriver::Flush() const { - const int fd = socket(PF_INET, SOCK_DGRAM, 0); - LOGDEBUG("%s: ip link set piscsi0 down", __PRETTY_FUNCTION__) - const bool result = ip_link(fd, "piscsi0", false); - close(fd); - return result; -} - -void CTapDriver::Flush() -{ - LOGTRACE("%s", __PRETTY_FUNCTION__) - while (PendingPackets()) { + while (HasPendingPackets()) { array m_garbage_buffer; (void)Receive(m_garbage_buffer.data()); } @@ -409,12 +318,7 @@ void CTapDriver::GetMacAddr(uint8_t *mac) const memcpy(mac, m_MacAddr.data(), m_MacAddr.size()); } -//--------------------------------------------------------------------------- -// -// Receive -// -//--------------------------------------------------------------------------- -bool CTapDriver::PendingPackets() const +bool CTapDriver::HasPendingPackets() const { assert(m_hTAP != -1); @@ -424,20 +328,16 @@ bool CTapDriver::PendingPackets() const fds.events = POLLIN | POLLERR; fds.revents = 0; poll(&fds, 1, 0); - LOGTRACE("%s %u revents", __PRETTY_FUNCTION__, fds.revents) - if (!(fds.revents & POLLIN)) { - return false; - } else { - return true; - } + spdlog::trace(to_string(fds.revents) + " revents"); + return !(fds.revents & POLLIN); } // See https://stackoverflow.com/questions/21001659/crc32-algorithm-implementation-in-c-without-a-look-up-table-and-with-a-public-li -uint32_t CTapDriver::Crc32(const uint8_t *buf, int length) { +uint32_t CTapDriver::Crc32(span data) { uint32_t crc = 0xffffffff; - for (int i = 0; i < length; i++) { - crc ^= buf[i]; - for (int j = 0; j < 8; j++) { + for (const auto d: data) { + crc ^= d; + for (int i = 0; i < 8; i++) { const uint32_t mask = -(static_cast(crc) & 1); crc = (crc >> 1) ^ (0xEDB88320 & mask); } @@ -445,19 +345,19 @@ uint32_t CTapDriver::Crc32(const uint8_t *buf, int length) { return ~crc; } -int CTapDriver::Receive(uint8_t *buf) +int CTapDriver::Receive(uint8_t *buf) const { assert(m_hTAP != -1); // Check if there is data that can be received - if (!PendingPackets()) { + if (!HasPendingPackets()) { return 0; } // Receive auto dwReceived = static_cast(read(m_hTAP, buf, ETH_FRAME_LEN)); if (dwReceived == static_cast(-1)) { - LOGWARN("%s Error occured while receiving a packet", __PRETTY_FUNCTION__) + spdlog::warn("Error occured while receiving a packet"); return 0; } @@ -466,49 +366,28 @@ int CTapDriver::Receive(uint8_t *buf) // We need to add the Frame Check Status (FCS) CRC back onto the end of the packet. // The Linux network subsystem removes it, since most software apps shouldn't ever // need it. - const int crc = Crc32(buf, dwReceived); + const int crc = Crc32(span(buf, dwReceived)); buf[dwReceived + 0] = (uint8_t)((crc >> 0) & 0xFF); buf[dwReceived + 1] = (uint8_t)((crc >> 8) & 0xFF); buf[dwReceived + 2] = (uint8_t)((crc >> 16) & 0xFF); buf[dwReceived + 3] = (uint8_t)((crc >> 24) & 0xFF); - LOGDEBUG("%s CRC is %08X - %02X %02X %02X %02X\n", __PRETTY_FUNCTION__, crc, buf[dwReceived+0], buf[dwReceived+1], buf[dwReceived+2], buf[dwReceived+3]) + spdlog::trace("CRC is " + to_string(crc) + " - " + to_string(buf[dwReceived+0]) + " " + to_string(buf[dwReceived+1]) + + " " + to_string(buf[dwReceived+2]) + " " + to_string(buf[dwReceived+3])); // Add FCS size to the received message size dwReceived += 4; } - if (m_pcap_dumper != nullptr) { - pcap_pkthdr h = { - .ts = {}, - .caplen = dwReceived, - .len = dwReceived - }; - gettimeofday(&h.ts, nullptr); - pcap_dump((u_char*)m_pcap_dumper, &h, buf); - LOGTRACE("%s Dumped %d byte packet (first byte: %02x last byte: %02x)", __PRETTY_FUNCTION__, (unsigned int)dwReceived, buf[0], buf[dwReceived-1]) - } - // Return the number of bytes return dwReceived; } -int CTapDriver::Send(const uint8_t *buf, int len) +int CTapDriver::Send(const uint8_t *buf, int len) const { assert(m_hTAP != -1); - if (m_pcap_dumper != nullptr) { - pcap_pkthdr h = { - .ts = {}, - .caplen = (bpf_u_int32)len, - .len = (bpf_u_int32)len, - }; - gettimeofday(&h.ts, nullptr); - pcap_dump((u_char*)m_pcap_dumper, &h, buf); - LOGTRACE("%s Dumped %d byte packet (first byte: %02x last byte: %02x)", __PRETTY_FUNCTION__, (unsigned int)h.len, buf[0], buf[h.len-1]) - } - // Start sending return static_cast(write(m_hTAP, buf, len)); } diff --git a/cpp/devices/ctapdriver.h b/cpp/devices/ctapdriver.h index bb3bc371..f8b3bdfd 100644 --- a/cpp/devices/ctapdriver.h +++ b/cpp/devices/ctapdriver.h @@ -11,46 +11,55 @@ #pragma once -#include -#include +#include "devices/device.h" #include #include #include #include +#include + +#ifndef ETH_FRAME_LEN +static const int ETH_FRAME_LEN = 1514; +#endif +#ifndef ETH_FCS_LEN +static const int ETH_FCS_LEN = 4; +#endif using namespace std; class CTapDriver { - static constexpr const char *BRIDGE_NAME = "piscsi_bridge"; + static const string BRIDGE_NAME; public: CTapDriver() = default; - ~CTapDriver(); + ~CTapDriver() = default; CTapDriver(CTapDriver&) = default; CTapDriver& operator=(const CTapDriver&) = default; - bool Init(const unordered_map&); - void OpenDump(const string& path); // Capture packets - void GetMacAddr(uint8_t *mac) const; - int Receive(uint8_t *buf); - int Send(const uint8_t *buf, int len); - bool PendingPackets() const; // Check if there are IP packets available - bool Enable() const; // Enable the piscsi0 interface - bool Disable() const; // Disable the piscsi0 interface - void Flush(); // Purge all of the packets that are waiting to be processed + bool Init(const param_map&); + void CleanUp() const; - static uint32_t Crc32(const uint8_t *, int); + void GetMacAddr(uint8_t *) const; + int Receive(uint8_t *) const; + int Send(const uint8_t *, int) const; + bool HasPendingPackets() const; // Check if there are IP packets available + string IpLink(bool) const; // Enable/Disable the piscsi0 interface + void Flush() const; // Purge all of the packets that are waiting to be processed + + static uint32_t Crc32(span); private: + + static string SetUpEth0(int, const string&); + static string SetUpNonEth0(int, int, const string&); + static pair ExtractAddressAndMask(const string&); + array m_MacAddr; // MAC Address int m_hTAP = -1; // File handle - pcap_t *m_pcap = nullptr; - pcap_dumper_t *m_pcap_dumper = nullptr; - // Prioritized comma-separated list of interfaces to create the bridge for vector interfaces; diff --git a/cpp/devices/device.cpp b/cpp/devices/device.cpp index 2aedf5d9..58b071f8 100644 --- a/cpp/devices/device.cpp +++ b/cpp/devices/device.cpp @@ -3,22 +3,24 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "shared/piscsi_version.h" #include "device.h" +#include #include #include #include +#include using namespace std; Device::Device(PbDeviceType type, int lun) : type(type), lun(lun) { ostringstream os; - os << setw(2) << setfill('0') << piscsi_major_version << setw(2) << setfill('0') << piscsi_minor_version; + os << setfill('0') << setw(2) << piscsi_major_version << setw(2) << piscsi_minor_version; revision = os.str(); } @@ -39,7 +41,7 @@ void Device::SetProtected(bool b) void Device::SetVendor(const string& v) { if (v.empty() || v.length() > 8) { - throw invalid_argument("Vendor '" + v + "' must be between 1 and 8 characters"); + throw invalid_argument("Vendor '" + v + "' must have between 1 and 8 characters"); } vendor = v; @@ -48,7 +50,7 @@ void Device::SetVendor(const string& v) void Device::SetProduct(const string& p, bool force) { if (p.empty() || p.length() > 16) { - throw invalid_argument("Product '" + p + "' must be between 1 and 16 characters"); + throw invalid_argument("Product '" + p + "' must have between 1 and 16 characters"); } // Changing vital product data is not SCSI compliant @@ -62,7 +64,7 @@ void Device::SetProduct(const string& p, bool force) void Device::SetRevision(const string& r) { if (r.empty() || r.length() > 4) { - throw invalid_argument("Revision '" + r + "' must be between 1 and 4 characters"); + throw invalid_argument("Revision '" + r + "' must have between 1 and 4 characters"); } revision = r; @@ -85,7 +87,7 @@ string Device::GetParam(const string& key) const return it == params.end() ? "" : it->second; } -void Device::SetParams(const unordered_map& set_params) +void Device::SetParams(const param_map& set_params) { params = default_params; @@ -96,11 +98,11 @@ void Device::SetParams(const unordered_map& set_params) for (const auto& [key, value] : set_params) { // It is assumed that there are default parameters for all supported parameters - if (params.find(key) != params.end()) { + if (params.contains(key)) { params[key] = value; } else { - LOGWARN("%s", string("Ignored unknown parameter '" + key + "'").c_str()) + spdlog::warn("Ignored unknown parameter '" + key + "'"); } } } diff --git a/cpp/devices/device.h b/cpp/devices/device.h index f971bc9d..80f1a272 100644 --- a/cpp/devices/device.h +++ b/cpp/devices/device.h @@ -3,20 +3,26 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once -#include "shared/log.h" #include "generated/piscsi_interface.pb.h" +#include "shared/piscsi_util.h" #include #include using namespace std; using namespace piscsi_interface; +// A combination of device ID and LUN +using id_set = pair; + +// The map used for storing/passing device parameters +using param_map = unordered_map>; + class Device //NOSONAR The number of fields and methods is justified, the complexity is low { const string DEFAULT_VENDOR = "PiSCSI"; @@ -60,10 +66,10 @@ class Device //NOSONAR The number of fields and methods is justified, the comple string revision; // The parameters the device was created with - unordered_map params; + param_map params; // The default parameters - unordered_map default_params; + param_map default_params; // Sense Key and ASC // MSB Reserved (0x00) @@ -90,14 +96,15 @@ protected: int GetStatusCode() const { return status_code; } string GetParam(const string&) const; - void SetParams(const unordered_map&); + void SetParams(const param_map&); public: virtual ~Device() = default; PbDeviceType GetType() const { return type; } - const char *GetTypeString() const { return PbDeviceType_Name(type).c_str(); } + string GetTypeString() const { return PbDeviceType_Name(type); } + string GetIdentifier() const { return GetTypeString() + " " + to_string(GetId()) + ":" + to_string(lun); } bool IsReady() const { return ready; } virtual void Reset(); @@ -131,8 +138,8 @@ public: bool SupportsFile() const { return supports_file; } void SupportsParams(bool b) { supports_params = b; } void SupportsFile(bool b) { supports_file = b; } - unordered_map GetParams() const { return params; } - void SetDefaultParams(const unordered_map& p) { default_params = p; } + auto GetParams() const { return params; } + void SetDefaultParams(const param_map& p) { default_params = p; } void SetStatusCode(int s) { status_code = s; } diff --git a/cpp/devices/device_factory.cpp b/cpp/devices/device_factory.cpp index 321162f7..da69fa98 100644 --- a/cpp/devices/device_factory.cpp +++ b/cpp/devices/device_factory.cpp @@ -3,11 +3,11 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "shared/piscsi_util.h" +#include "shared/network_util.h" #include "scsihd.h" #include "scsihd_nec.h" #include "scsimo.h" @@ -17,15 +17,10 @@ #include "scsi_daynaport.h" #include "host_services.h" #include "device_factory.h" -#include -#include -#include -#include -#include using namespace std; -using namespace piscsi_interface; using namespace piscsi_util; +using namespace network_util; DeviceFactory::DeviceFactory() { @@ -34,19 +29,11 @@ DeviceFactory::DeviceFactory() sector_sizes[SCMO] = { 512, 1024, 2048, 4096 }; sector_sizes[SCCD] = { 512, 2048}; - string network_interfaces; - for (const auto& network_interface : GetNetworkInterfaces()) { - if (network_interface.rfind("dummy", 0) == string::npos) { - if (!network_interfaces.empty()) { - network_interfaces += ","; - } - network_interfaces += network_interface; - } - } + const string interfaces = Join(GetNetworkInterfaces(), ","); - default_params[SCBR]["interface"] = network_interfaces; + default_params[SCBR]["interface"] = interfaces; default_params[SCBR]["inet"] = DEFAULT_IP; - default_params[SCDP]["interface"] = network_interfaces; + default_params[SCDP]["interface"] = interfaces; default_params[SCDP]["inet"] = DEFAULT_IP; default_params[SCLP]["cmd"] = "lp -oraw %f"; @@ -96,8 +83,8 @@ shared_ptr DeviceFactory::CreateDevice(PbDeviceType type, int lun if (const string ext = GetExtensionLowerCase(filename); ext == "hdn" || ext == "hdi" || ext == "nhd") { device = make_shared(lun); } else { - device = make_shared(lun, sector_sizes.find(SCHD)->second, false, - ext == "hd1" ? scsi_level::SCSI_1_CCS : scsi_level::SCSI_2); + device = make_shared(lun, sector_sizes.find(type)->second, false, + ext == "hd1" ? scsi_level::scsi_1_ccs : scsi_level::scsi_2); // Some Apple tools require a particular drive identification if (ext == "hda") { @@ -109,18 +96,18 @@ shared_ptr DeviceFactory::CreateDevice(PbDeviceType type, int lun } case SCRM: - device = make_shared(lun, sector_sizes.find(SCRM)->second, true); + device = make_shared(lun, sector_sizes.find(type)->second, true); device->SetProduct("SCSI HD (REM.)"); break; case SCMO: - device = make_shared(lun, sector_sizes.find(SCMO)->second); + device = make_shared(lun, sector_sizes.find(type)->second); device->SetProduct("SCSI MO"); break; case SCCD: - device = make_shared(lun, sector_sizes.find(SCCD)->second, - GetExtensionLowerCase(filename) == "is1" ? scsi_level::SCSI_1_CCS : scsi_level::SCSI_2); + device = make_shared(lun, sector_sizes.find(type)->second, + GetExtensionLowerCase(filename) == "is1" ? scsi_level::scsi_1_ccs : scsi_level::scsi_2); device->SetProduct("SCSI CD-ROM"); break; @@ -128,7 +115,7 @@ shared_ptr DeviceFactory::CreateDevice(PbDeviceType type, int lun device = make_shared(lun); // Since this is an emulation for a specific driver the product name has to be set accordingly device->SetProduct("RASCSI BRIDGE"); - device->SetDefaultParams(default_params.find(SCBR)->second); + device->SetDefaultParams(default_params.find(type)->second); break; case SCDP: @@ -137,7 +124,7 @@ shared_ptr DeviceFactory::CreateDevice(PbDeviceType type, int lun device->SetVendor("Dayna"); device->SetProduct("SCSI/Link"); device->SetRevision("1.4a"); - device->SetDefaultParams(default_params.find(SCDP)->second); + device->SetDefaultParams(default_params.find(type)->second); break; case SCHS: @@ -150,7 +137,7 @@ shared_ptr DeviceFactory::CreateDevice(PbDeviceType type, int lun case SCLP: device = make_shared(lun); device->SetProduct("SCSI PRINTER"); - device->SetDefaultParams(default_params.find(SCLP)->second); + device->SetDefaultParams(default_params.find(type)->second); break; default: @@ -163,44 +150,11 @@ shared_ptr DeviceFactory::CreateDevice(PbDeviceType type, int lun const unordered_set& DeviceFactory::GetSectorSizes(PbDeviceType type) const { const auto& it = sector_sizes.find(type); - return it != sector_sizes.end() ? it->second : empty_set; + return it != sector_sizes.end() ? it->second : EMPTY_SET; } -const unordered_map& DeviceFactory::GetDefaultParams(PbDeviceType type) const +const param_map& DeviceFactory::GetDefaultParams(PbDeviceType type) const { const auto& it = default_params.find(type); - return it != default_params.end() ? it->second : empty_map; -} - -vector DeviceFactory::GetNetworkInterfaces() const -{ - vector network_interfaces; - -#ifdef __linux__ - ifaddrs *addrs; - getifaddrs(&addrs); - ifaddrs *tmp = addrs; - - while (tmp) { - if (tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_PACKET && - strcmp(tmp->ifa_name, "lo") && strcmp(tmp->ifa_name, "piscsi_bridge")) { - const int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); - - ifreq ifr = {}; - strcpy(ifr.ifr_name, tmp->ifa_name); //NOSONAR Using strcpy is safe here - // Only list interfaces that are up - if (!ioctl(fd, SIOCGIFFLAGS, &ifr) && (ifr.ifr_flags & IFF_UP)) { - network_interfaces.emplace_back(tmp->ifa_name); - } - - close(fd); - } - - tmp = tmp->ifa_next; - } - - freeifaddrs(addrs); -#endif - - return network_interfaces; + return it != default_params.end() ? it->second : EMPTY_PARAM_MAP; } diff --git a/cpp/devices/device_factory.h b/cpp/devices/device_factory.h index a2865d88..cdce0ba2 100644 --- a/cpp/devices/device_factory.h +++ b/cpp/devices/device_factory.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // // The DeviceFactory creates devices based on their type and the image file extension // @@ -11,8 +11,9 @@ #pragma once +#include "shared/piscsi_util.h" +#include "devices/device.h" #include -#include #include #include #include "generated/piscsi_interface.pb.h" @@ -34,20 +35,19 @@ public: shared_ptr CreateDevice(PbDeviceType, int, const string&) const; PbDeviceType GetTypeForFile(const string&) const; const unordered_set& GetSectorSizes(PbDeviceType type) const; - const unordered_map& GetDefaultParams(PbDeviceType type) const; - vector GetNetworkInterfaces() const; - const unordered_map& GetExtensionMapping() const { return extension_mapping; } + const param_map& GetDefaultParams(PbDeviceType type) const; + const auto& GetExtensionMapping() const { return extension_mapping; } private: unordered_map> sector_sizes; - unordered_map> default_params; + unordered_map default_params; - unordered_map extension_mapping; + unordered_map> extension_mapping; - unordered_map device_mapping; + unordered_map> device_mapping; - unordered_set empty_set; - unordered_map empty_map; + inline static const unordered_set EMPTY_SET; + inline static const param_map EMPTY_PARAM_MAP; }; diff --git a/cpp/devices/device_logger.cpp b/cpp/devices/device_logger.cpp index 98c6d8d3..65afb129 100644 --- a/cpp/devices/device_logger.cpp +++ b/cpp/devices/device_logger.cpp @@ -3,63 +3,52 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "shared/log.h" #include "device_logger.h" using namespace std; +using namespace spdlog; void DeviceLogger::Trace(const string& message) const { - if (const string m = GetLogMessage(message); !m.empty()) { - LOGTRACE("%s", m.c_str()) - } + Log(level::trace, message); } void DeviceLogger::Debug(const string& message) const { - if (const string m = GetLogMessage(message); !m.empty()) { - LOGDEBUG("%s", m.c_str()) - } + Log(level::debug, message); } void DeviceLogger::Info(const string& message) const { - if (const string m = GetLogMessage(message); !m.empty()) { - LOGINFO("%s", m.c_str()) - } + Log(level::info, message); } void DeviceLogger::Warn(const string& message) const { - if (const string m = GetLogMessage(message); !m.empty()) { - LOGWARN("%s", m.c_str()) - } + Log(level::warn, message); } void DeviceLogger::Error(const string& message) const { - if (const string m = GetLogMessage(message); !m.empty()) { - LOGERROR("%s", m.c_str()) - } + Log(level::err, message); } -string DeviceLogger::GetLogMessage(const string& message) const +void DeviceLogger::Log(level::level_enum level, const string& message) const { - if (log_device_id == -1 || (log_device_id == id && (log_device_lun == -1 || log_device_lun == lun))) - { + if (!message.empty() && + (log_device_id == -1 || + (log_device_id == id && (log_device_lun == -1 || log_device_lun == lun)))) { if (lun == -1) { - return "(ID " + to_string(id) + ") - " + message; + log(level, "(ID " + to_string(id) + ") - " + message); } else { - return "(ID:LUN " + to_string(id) + ":" + to_string(lun) + ") - " + message; + log(level, "(ID:LUN " + to_string(id) + ":" + to_string(lun) + ") - " + message); } } - - return ""; } void DeviceLogger::SetIdAndLun(int i, int l) diff --git a/cpp/devices/device_logger.h b/cpp/devices/device_logger.h index fb5b8f7a..dc662718 100644 --- a/cpp/devices/device_logger.h +++ b/cpp/devices/device_logger.h @@ -3,12 +3,13 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once +#include "spdlog/spdlog.h" #include using namespace std; @@ -27,14 +28,13 @@ public: void Warn(const string&) const; void Error(const string&) const; - string GetLogMessage(const string&) const; - void SetIdAndLun(int, int); - static void SetLogIdAndLun(int, int); private: + void Log(spdlog::level::level_enum, const string&) const; + int id = -1; int lun = -1; diff --git a/cpp/devices/disk.cpp b/cpp/devices/disk.cpp index 8649103f..8a32273e 100644 --- a/cpp/devices/disk.cpp +++ b/cpp/devices/disk.cpp @@ -23,15 +23,7 @@ using namespace scsi_defs; using namespace scsi_command_util; -Disk::~Disk() -{ - // Save disk cache, only if ready - if (IsReady() && cache != nullptr) { - cache->Save(); - } -} - -bool Disk::Init(const unordered_map& params) +bool Disk::Init(const param_map& params) { StorageDevice::Init(params); @@ -64,6 +56,13 @@ bool Disk::Init(const unordered_map& params) return true; } +void Disk::CleanUp() +{ + FlushCache(); + + StorageDevice::CleanUp(); +} + void Disk::Dispatch(scsi_command cmd) { // Media changes must be reported on the next access, i.e. not only for TEST UNIT READY @@ -72,7 +71,7 @@ void Disk::Dispatch(scsi_command cmd) SetMediumChanged(false); - GetController()->Error(sense_key::UNIT_ATTENTION, asc::NOT_READY_TO_READY_CHANGE); + GetController()->Error(sense_key::unit_attention, asc::not_ready_to_ready_change); } else { PrimaryDevice::Dispatch(cmd); @@ -93,7 +92,7 @@ void Disk::ResizeCache(const string& path, bool raw) void Disk::FlushCache() { - if (cache != nullptr) { + if (cache != nullptr && IsReady()) { cache->Save(); } } @@ -103,8 +102,8 @@ void Disk::FormatUnit() CheckReady(); // FMTDATA=1 is not supported (but OK if there is no DEFECT LIST) - if ((GetController()->GetCmd(1) & 0x10) != 0 && GetController()->GetCmd(4) != 0) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + if ((GetController()->GetCmdByte(1) & 0x10) != 0 && GetController()->GetCmdByte(4) != 0) { + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } EnterStatusPhase(); @@ -115,9 +114,9 @@ void Disk::Read(access_mode mode) const auto& [valid, start, blocks] = CheckAndGetStartAndCount(mode); if (valid) { GetController()->SetBlocks(blocks); - GetController()->SetLength(Read(GetController()->GetCmd(), GetController()->GetBuffer(), start)); + GetController()->SetLength(Read(GetController()->GetBuffer(), start)); - GetLogger().Trace("Length is " + to_string(GetController()->GetLength())); + LogTrace("Length is " + to_string(GetController()->GetLength())); // Set next block GetController()->SetNext(start + 1); @@ -135,7 +134,7 @@ void Disk::ReadWriteLong10() const // Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard if (GetInt16(GetController()->GetCmd(), 7) != 0) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } EnterStatusPhase(); @@ -147,7 +146,7 @@ void Disk::ReadWriteLong16() const // Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard if (GetInt16(GetController()->GetCmd(), 12) != 0) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } EnterStatusPhase(); @@ -156,7 +155,7 @@ void Disk::ReadWriteLong16() const void Disk::Write(access_mode mode) const { if (IsProtected()) { - throw scsi_exception(sense_key::DATA_PROTECT, asc::WRITE_PROTECTED); + throw scsi_exception(sense_key::data_protect, asc::write_protected); } const auto& [valid, start, blocks] = CheckAndGetStartAndCount(mode); @@ -179,14 +178,14 @@ void Disk::Verify(access_mode mode) const auto& [valid, start, blocks] = CheckAndGetStartAndCount(mode); if (valid) { // if BytChk=0 - if ((GetController()->GetCmd(1) & 0x02) == 0) { + if ((GetController()->GetCmdByte(1) & 0x02) == 0) { Seek(); return; } // Test reading GetController()->SetBlocks(blocks); - GetController()->SetLength(Read(GetController()->GetCmd(), GetController()->GetBuffer(), start)); + GetController()->SetLength(Read(GetController()->GetBuffer(), start)); // Set next block GetController()->SetNext(start + 1); @@ -200,14 +199,14 @@ void Disk::Verify(access_mode mode) void Disk::StartStopUnit() { - const bool start = GetController()->GetCmd(4) & 0x01; - const bool load = GetController()->GetCmd(4) & 0x02; + const bool start = GetController()->GetCmdByte(4) & 0x01; + const bool load = GetController()->GetCmdByte(4) & 0x02; if (load) { - GetLogger().Trace(start ? "Loading medium" : "Ejecting medium"); + LogTrace(start ? "Loading medium" : "Ejecting medium"); } else { - GetLogger().Trace(start ? "Starting unit" : "Stopping unit"); + LogTrace(start ? "Starting unit" : "Stopping unit"); SetStopped(!start); } @@ -217,12 +216,12 @@ void Disk::StartStopUnit() if (load) { if (IsLocked()) { // Cannot be ejected because it is locked - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LOAD_OR_EJECT_FAILED); + throw scsi_exception(sense_key::illegal_request, asc::load_or_eject_failed); } // Eject if (!Eject(false)) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LOAD_OR_EJECT_FAILED); + throw scsi_exception(sense_key::illegal_request, asc::load_or_eject_failed); } } else { @@ -237,9 +236,9 @@ void Disk::PreventAllowMediumRemoval() { CheckReady(); - const bool lock = GetController()->GetCmd(4) & 0x01; + const bool lock = GetController()->GetCmdByte(4) & 0x01; - GetLogger().Trace(lock ? "Locking medium" : "Unlocking medium"); + LogTrace(lock ? "Locking medium" : "Unlocking medium"); SetLocked(lock); @@ -279,7 +278,7 @@ bool Disk::Eject(bool force) return status; } -int Disk::ModeSense6(const vector& cdb, vector& buf) const +int Disk::ModeSense6(cdb_t cdb, vector& buf) const { // Get length, clear buffer const auto length = static_cast(min(buf.size(), static_cast(cdb[4]))); @@ -315,7 +314,7 @@ int Disk::ModeSense6(const vector& cdb, vector& buf) const return size; } -int Disk::ModeSense10(const vector& cdb, vector& buf) const +int Disk::ModeSense10(cdb_t cdb, vector& buf) const { // Get length, clear buffer const auto length = static_cast(min(buf.size(), static_cast(GetInt16(cdb, 7)))); @@ -497,27 +496,27 @@ void Disk::AddCachePage(map>& pages, bool changeable) const pages[8] = buf; } -int Disk::Read(const vector&, vector& buf, uint64_t block) +int Disk::Read(span buf, uint64_t block) { assert(block < GetBlockCount()); CheckReady(); if (!cache->ReadSector(buf, static_cast(block))) { - throw scsi_exception(sense_key::MEDIUM_ERROR, asc::READ_FAULT); + throw scsi_exception(sense_key::medium_error, asc::read_fault); } return GetSectorSizeInBytes(); } -void Disk::Write(const vector&, const vector& buf, uint64_t block) +void Disk::Write(span buf, uint64_t block) { assert(block < GetBlockCount()); CheckReady(); if (!cache->WriteSector(buf, static_cast(block))) { - throw scsi_exception(sense_key::MEDIUM_ERROR, asc::WRITE_FAULT); + throw scsi_exception(sense_key::medium_error, asc::write_fault); } } @@ -553,7 +552,7 @@ void Disk::ReadCapacity10() CheckReady(); if (GetBlockCount() == 0) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::MEDIUM_NOT_PRESENT); + throw scsi_exception(sense_key::illegal_request, asc::medium_not_present); } vector& buf = GetController()->GetBuffer(); @@ -580,7 +579,7 @@ void Disk::ReadCapacity16() CheckReady(); if (GetBlockCount() == 0) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::MEDIUM_NOT_PRESENT); + throw scsi_exception(sense_key::illegal_request, asc::medium_not_present); } vector& buf = GetController()->GetBuffer(); @@ -604,7 +603,7 @@ void Disk::ReadCapacity16() void Disk::ReadCapacity16_ReadLong16() { // The service action determines the actual command - switch (GetController()->GetCmd(1) & 0x1f) { + switch (GetController()->GetCmdByte(1) & 0x1f) { case 0x10: ReadCapacity16(); break; @@ -614,7 +613,7 @@ void Disk::ReadCapacity16_ReadLong16() break; default: - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); break; } } @@ -624,9 +623,9 @@ void Disk::ValidateBlockAddress(access_mode mode) const const uint64_t block = mode == RW16 ? GetInt64(GetController()->GetCmd(), 2) : GetInt32(GetController()->GetCmd(), 2); if (block > GetBlockCount()) { - GetLogger().Trace("Capacity of " + to_string(GetBlockCount()) + " block(s) exceeded: Trying to access block " + LogTrace("Capacity of " + to_string(GetBlockCount()) + " block(s) exceeded: Trying to access block " + to_string(block)); - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE); + throw scsi_exception(sense_key::illegal_request, asc::lba_out_of_range); } } @@ -638,7 +637,7 @@ tuple Disk::CheckAndGetStartAndCount(access_mode mode) if (mode == RW6 || mode == SEEK6) { start = GetInt24(GetController()->GetCmd(), 1); - count = GetController()->GetCmd(4); + count = GetController()->GetCmdByte(4); if (!count) { count= 0x100; } @@ -659,13 +658,13 @@ tuple Disk::CheckAndGetStartAndCount(access_mode mode) stringstream s; s << "READ/WRITE/VERIFY/SEEK, start block: $" << setfill('0') << setw(8) << hex << start; - GetLogger().Trace(s.str() + ", blocks: " + to_string(count)); + LogTrace(s.str() + ", blocks: " + to_string(count)); // Check capacity if (uint64_t capacity = GetBlockCount(); !capacity || start > capacity || start + count > capacity) { - GetLogger().Trace("Capacity of " + to_string(capacity) + " block(s) exceeded: Trying to access block " + LogTrace("Capacity of " + to_string(capacity) + " block(s) exceeded: Trying to access block " + to_string(start) + ", block count " + to_string(count)); - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE); + throw scsi_exception(sense_key::illegal_request, asc::lba_out_of_range); } // Do not process 0 blocks @@ -689,10 +688,8 @@ uint32_t Disk::GetSectorSizeInBytes() const void Disk::SetSectorSizeInBytes(uint32_t size_in_bytes) { - DeviceFactory device_factory; - if (const auto& sizes = device_factory.GetSectorSizes(GetType()); - !sizes.empty() && sizes.find(size_in_bytes) == sizes.end()) { - throw io_exception("Invalid sector size of " + to_string(size_in_bytes) + " byte(s)"); + if (DeviceFactory device_factory; !device_factory.GetSectorSizes(GetType()).contains(size_in_bytes)) { + throw io_exception("Invalid sector size of " + to_string(size_in_bytes) + " byte(s)"); } size_shift_count = CalculateShiftCount(size_in_bytes); @@ -706,12 +703,11 @@ uint32_t Disk::GetConfiguredSectorSize() const bool Disk::SetConfiguredSectorSize(const DeviceFactory& device_factory, uint32_t configured_size) { - if (unordered_set sizes = device_factory.GetSectorSizes(GetType()); - sizes.find(configured_size) == sizes.end()) { + if (!device_factory.GetSectorSizes(GetType()).contains(configured_size)) { return false; } - configured_sector_size = configured_size; + configured_sector_size = configured_size; return true; } diff --git a/cpp/devices/disk.h b/cpp/devices/disk.h index 75af2697..1b22262d 100644 --- a/cpp/devices/disk.h +++ b/cpp/devices/disk.h @@ -22,6 +22,7 @@ #include "interfaces/scsi_block_commands.h" #include "storage_device.h" #include +#include #include #include #include @@ -43,18 +44,18 @@ class Disk : public StorageDevice, private ScsiBlockCommands public: - Disk(PbDeviceType type, int lun) : StorageDevice(type, lun) {} - ~Disk() override; + using StorageDevice::StorageDevice; - bool Init(const unordered_map&) override; + bool Init(const param_map&) override; + void CleanUp() override; void Dispatch(scsi_command) override; bool Eject(bool) override; - virtual void Write(const vector&, const vector&, uint64_t); + virtual void Write(span, uint64_t); - virtual int Read(const vector&, vector& , uint64_t); + virtual int Read(span , uint64_t); uint32_t GetSectorSizeInBytes() const; bool IsSectorSizeConfigurable() const { return !sector_sizes.empty(); } @@ -92,8 +93,8 @@ private: void ValidateBlockAddress(access_mode) const; tuple CheckAndGetStartAndCount(access_mode) const; - int ModeSense6(const vector&, vector&) const override; - int ModeSense10(const vector&, vector&) const override; + int ModeSense6(cdb_t, vector&) const override; + int ModeSense10(cdb_t, vector&) const override; static inline const unordered_map shift_counts = { { 512, 9 }, { 1024, 10 }, { 2048, 11 }, { 4096, 12 } }; diff --git a/cpp/devices/disk_cache.cpp b/cpp/devices/disk_cache.cpp index 8fc126db..23bd724a 100644 --- a/cpp/devices/disk_cache.cpp +++ b/cpp/devices/disk_cache.cpp @@ -30,7 +30,7 @@ DiskCache::DiskCache(const string& path, int size, uint32_t blocks, off_t imgoff bool DiskCache::Save() const { // Save valid tracks - return none_of(cache.begin(), cache.end(), [this](const cache_t& c) + return ranges::none_of(cache.begin(), cache.end(), [this](const cache_t& c) { return c.disktrk != nullptr && !c.disktrk->Save(sec_path); }); } @@ -46,7 +46,7 @@ shared_ptr DiskCache::GetTrack(uint32_t block) return Assign(track); } -bool DiskCache::ReadSector(vector& buf, uint32_t block) +bool DiskCache::ReadSector(span buf, uint32_t block) { shared_ptr disktrk = GetTrack(block); if (disktrk == nullptr) { @@ -57,7 +57,7 @@ bool DiskCache::ReadSector(vector& buf, uint32_t block) return disktrk->ReadSector(buf, block & 0xff); } -bool DiskCache::WriteSector(const vector& buf, uint32_t block) +bool DiskCache::WriteSector(span buf, uint32_t block) { shared_ptr disktrk = GetTrack(block); if (disktrk == nullptr) { diff --git a/cpp/devices/disk_cache.h b/cpp/devices/disk_cache.h index d9199b5c..4400e27d 100644 --- a/cpp/devices/disk_cache.h +++ b/cpp/devices/disk_cache.h @@ -15,6 +15,7 @@ #pragma once +#include #include #include #include @@ -41,8 +42,8 @@ public: // Access bool Save() const; // Save and release all - bool ReadSector(vector&, uint32_t); // Sector Read - bool WriteSector(const vector&, uint32_t); // Sector Write + bool ReadSector(span, uint32_t); // Sector Read + bool WriteSector(span, uint32_t); // Sector Write private: diff --git a/cpp/devices/disk_track.cpp b/cpp/devices/disk_track.cpp index 2b4f4008..35e1e56b 100644 --- a/cpp/devices/disk_track.cpp +++ b/cpp/devices/disk_track.cpp @@ -14,8 +14,10 @@ // //--------------------------------------------------------------------------- -#include "shared/log.h" #include "disk_track.h" +#include +#include +#include #include DiskTrack::~DiskTrack() @@ -75,7 +77,7 @@ bool DiskTrack::Load(const string& path) if (dt.buffer == nullptr) { if (posix_memalign((void **)&dt.buffer, 512, ((length + 511) / 512) * 512)) { - LOGWARN("posix_memalign failed") + spdlog::warn("posix_memalign failed"); } dt.length = length; } @@ -88,14 +90,14 @@ bool DiskTrack::Load(const string& path) if (dt.length != static_cast(length)) { free(dt.buffer); if (posix_memalign((void **)&dt.buffer, 512, ((length + 511) / 512) * 512)) { - LOGWARN("posix_memalign failed") + spdlog::warn("posix_memalign failed"); } dt.length = length; } // Resize and clear changemap dt.changemap.resize(dt.sectors); - fill(dt.changemap.begin(), dt.changemap.end(), false); + fill(dt.changemap.begin(), dt.changemap.end(), false); //NOSONAR ranges::fill() cannot be applied to vector ifstream in(path, ios::binary); if (in.fail()) { @@ -209,13 +211,13 @@ bool DiskTrack::Save(const string& path) } // Drop the change flag and exit - fill(dt.changemap.begin(), dt.changemap.end(), false); + fill(dt.changemap.begin(), dt.changemap.end(), false); //NOSONAR ranges::fill() cannot be applied to vector dt.changed = false; return true; } -bool DiskTrack::ReadSector(vector& buf, int sec) const +bool DiskTrack::ReadSector(span buf, int sec) const { assert(sec >= 0 && sec < 0x100); @@ -238,7 +240,7 @@ bool DiskTrack::ReadSector(vector& buf, int sec) const return true; } -bool DiskTrack::WriteSector(const vector& buf, int sec) +bool DiskTrack::WriteSector(span buf, int sec) { assert((sec >= 0) && (sec < 0x100)); assert(!dt.raw); diff --git a/cpp/devices/disk_track.h b/cpp/devices/disk_track.h index 786ab6e5..91ca5e86 100644 --- a/cpp/devices/disk_track.h +++ b/cpp/devices/disk_track.h @@ -16,6 +16,8 @@ #pragma once #include +#include +#include #include #include @@ -52,8 +54,8 @@ private: bool Save(const string& path); // Read / Write - bool ReadSector(vector&, int) const; // Sector Read - bool WriteSector(const vector& buf, int); // Sector Write + bool ReadSector(span, int) const; // Sector Read + bool WriteSector(span buf, int); // Sector Write int GetTrack() const { return dt.track; } // Get track }; diff --git a/cpp/devices/host_services.cpp b/cpp/devices/host_services.cpp index 5cb74b9a..3d02bbd0 100644 --- a/cpp/devices/host_services.cpp +++ b/cpp/devices/host_services.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // Host Services with realtime clock and shutdown support // @@ -25,11 +25,13 @@ #include "scsi_command_util.h" #include "host_services.h" #include +#include +using namespace std::chrono; using namespace scsi_defs; using namespace scsi_command_util; -bool HostServices::Init(const unordered_map& params) +bool HostServices::Init(const param_map& params) { ModePageDevice::Init(params); @@ -49,13 +51,13 @@ void HostServices::TestUnitReady() vector HostServices::InquiryInternal() const { - return HandleInquiry(device_type::PROCESSOR, scsi_level::SPC_3, false); + return HandleInquiry(device_type::processor, scsi_level::spc_3, false); } void HostServices::StartStopUnit() const { - const bool start = GetController()->GetCmd(4) & 0x01; - const bool load = GetController()->GetCmd(4) & 0x02; + const bool start = GetController()->GetCmdByte(4) & 0x01; + const bool load = GetController()->GetCmdByte(4) & 0x02; if (!start) { if (load) { @@ -69,17 +71,17 @@ void HostServices::StartStopUnit() const GetController()->ScheduleShutdown(AbstractController::piscsi_shutdown_mode::RESTART_PI); } else { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } EnterStatusPhase(); } -int HostServices::ModeSense6(const vector& cdb, vector& buf) const +int HostServices::ModeSense6(cdb_t cdb, vector& buf) const { // Block descriptors cannot be returned if (!(cdb[1] & 0x08)) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } const auto length = static_cast(min(buf.size(), static_cast(cdb[4]))); @@ -93,11 +95,11 @@ int HostServices::ModeSense6(const vector& cdb, vector& buf) const return size; } -int HostServices::ModeSense10(const vector& cdb, vector& buf) const +int HostServices::ModeSense10(cdb_t cdb, vector& buf) const { // Block descriptors cannot be returned if (!(cdb[1] & 0x08)) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } const auto length = static_cast(min(buf.size(), static_cast(GetInt16(cdb, 7)))); @@ -123,7 +125,8 @@ void HostServices::AddRealtimeClockPage(map>& pages, bool chan pages[32] = vector(10); if (!changeable) { - time_t t = time(nullptr); + const auto now = system_clock::now(); + const time_t t = system_clock::to_time_t(now); tm localtime; localtime_r(&t, &localtime); diff --git a/cpp/devices/host_services.h b/cpp/devices/host_services.h index a269b2ac..76a41f8c 100644 --- a/cpp/devices/host_services.h +++ b/cpp/devices/host_services.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // Host Services with realtime clock and shutdown support // @@ -12,6 +12,7 @@ #pragma once #include "mode_page_device.h" +#include #include #include @@ -23,7 +24,7 @@ public: explicit HostServices(int lun) : ModePageDevice(SCHS, lun) {} ~HostServices() override = default; - bool Init(const unordered_map&) override; + bool Init(const param_map&) override; vector InquiryInternal() const override; void TestUnitReady() override; @@ -48,8 +49,8 @@ private: }; void StartStopUnit() const; - int ModeSense6(const vector&, vector&) const override; - int ModeSense10(const vector&, vector&) const override; + int ModeSense6(cdb_t, vector&) const override; + int ModeSense10(cdb_t, vector&) const override; void AddRealtimeClockPage(map>&, bool) const; }; diff --git a/cpp/devices/interfaces/byte_writer.h b/cpp/devices/interfaces/byte_writer.h deleted file mode 100644 index bfbfa5ee..00000000 --- a/cpp/devices/interfaces/byte_writer.h +++ /dev/null @@ -1,27 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Copyright (C) 2022 Uwe Seimet -// -// Abstraction for the DaynaPort and the host bridge, which both have methods for writing byte sequences -// -//--------------------------------------------------------------------------- - -#pragma once - -#include - -using namespace std; - -class ByteWriter -{ - -public: - - ByteWriter() = default; - virtual ~ByteWriter() = default; - - virtual bool WriteBytes(const vector&, vector&, uint32_t) = 0; -}; diff --git a/cpp/devices/mode_page_device.cpp b/cpp/devices/mode_page_device.cpp index bb292097..7335d5a3 100644 --- a/cpp/devices/mode_page_device.cpp +++ b/cpp/devices/mode_page_device.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // A basic device with mode page support, to be used for subclassing // @@ -20,7 +20,7 @@ using namespace std; using namespace scsi_defs; using namespace scsi_command_util; -bool ModePageDevice::Init(const unordered_map& params) +bool ModePageDevice::Init(const param_map& params) { PrimaryDevice::Init(params); @@ -32,7 +32,7 @@ bool ModePageDevice::Init(const unordered_map& params) return true; } -int ModePageDevice::AddModePages(const vector& cdb, vector& buf, int offset, int length, int max_size) const +int ModePageDevice::AddModePages(cdb_t cdb, vector& buf, int offset, int length, int max_size) const { const int max_length = length - offset; if (max_length < 0) { @@ -46,7 +46,7 @@ int ModePageDevice::AddModePages(const vector& cdb, vector& buf, i stringstream s; s << "Requesting mode page $" << setfill('0') << setw(2) << hex << page; - GetLogger().Trace(s.str()); + LogTrace(s.str()); // Mode page data mapped to the respective page numbers, C++ maps are ordered by key map> pages; @@ -54,8 +54,8 @@ int ModePageDevice::AddModePages(const vector& cdb, vector& buf, i if (pages.empty()) { s << "Unsupported mode page $" << page; - GetLogger().Trace(s.str()); - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + LogTrace(s.str()); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } // Holds all mode page data @@ -90,7 +90,7 @@ int ModePageDevice::AddModePages(const vector& cdb, vector& buf, i } if (static_cast(result.size()) > max_size) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } const auto size = static_cast(min(static_cast(max_length), result.size())); @@ -114,15 +114,15 @@ void ModePageDevice::ModeSense10() const EnterDataInPhase(); } -void ModePageDevice::ModeSelect(scsi_command, const vector&, const vector&, int) const +void ModePageDevice::ModeSelect(scsi_command, cdb_t, span, int) const { - // There is no default implementation of MDOE SELECT - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE); + // There is no default implementation of MODE SELECT + throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code); } void ModePageDevice::ModeSelect6() const { - SaveParametersCheck(GetController()->GetCmd(4)); + SaveParametersCheck(GetController()->GetCmdByte(4)); } void ModePageDevice::ModeSelect10() const @@ -134,8 +134,8 @@ void ModePageDevice::ModeSelect10() const void ModePageDevice::SaveParametersCheck(int length) const { - if (!SupportsSaveParameters() && (GetController()->GetCmd(1) & 0x01)) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + if (!SupportsSaveParameters() && (GetController()->GetCmdByte(1) & 0x01)) { + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } GetController()->SetLength(length); diff --git a/cpp/devices/mode_page_device.h b/cpp/devices/mode_page_device.h index e8fc06d7..4a487fb2 100644 --- a/cpp/devices/mode_page_device.h +++ b/cpp/devices/mode_page_device.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -11,25 +11,25 @@ #include "primary_device.h" #include +#include #include #include -// TODO Maybe this should better be a mixin class because not all storage devicess have mode pages class ModePageDevice : public PrimaryDevice { public: using PrimaryDevice::PrimaryDevice; - bool Init(const unordered_map&) override; + bool Init(const param_map&) override; - virtual void ModeSelect(scsi_defs::scsi_command, const vector&, const vector&, int) const; + virtual void ModeSelect(scsi_defs::scsi_command, cdb_t, span, int) const; protected: bool SupportsSaveParameters() const { return supports_save_parameters; } void SupportsSaveParameters(bool b) { supports_save_parameters = b; } - int AddModePages(const vector&, vector&, int, int, int) const; + int AddModePages(cdb_t, vector&, int, int, int) const; virtual void SetUpModePages(map>&, int, bool) const = 0; virtual void AddVendorPage(map>&, int, bool) const { // Nothing to add by default @@ -39,8 +39,8 @@ private: bool supports_save_parameters = false; - virtual int ModeSense6(const vector&, vector&) const = 0; - virtual int ModeSense10(const vector&, vector&) const = 0; + virtual int ModeSense6(cdb_t, vector&) const = 0; + virtual int ModeSense10(cdb_t, vector&) const = 0; void ModeSense6() const; void ModeSense10() const; diff --git a/cpp/devices/primary_device.cpp b/cpp/devices/primary_device.cpp index 10989167..4249ed4e 100644 --- a/cpp/devices/primary_device.cpp +++ b/cpp/devices/primary_device.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -17,7 +17,7 @@ using namespace std; using namespace scsi_defs; using namespace scsi_command_util; -bool PrimaryDevice::Init(const unordered_map& params) +bool PrimaryDevice::Init(const param_map& params) { // Mandatory SCSI primary commands AddCommand(scsi_command::eCmdTestUnitReady, [this] { TestUnitReady(); }); @@ -35,9 +35,9 @@ bool PrimaryDevice::Init(const unordered_map& params) return true; } -void PrimaryDevice::AddCommand(scsi_command opcode, const operation& execute) +void PrimaryDevice::AddCommand(scsi_command cmd, const operation& execute) { - commands[opcode] = execute; + commands[cmd] = execute; } void PrimaryDevice::Dispatch(scsi_command cmd) @@ -46,15 +46,14 @@ void PrimaryDevice::Dispatch(scsi_command cmd) s << "$" << setfill('0') << setw(2) << hex << static_cast(cmd); if (const auto& it = commands.find(cmd); it != commands.end()) { - GetLogger().Debug("Device is executing " + string(command_mapping.find(cmd)->second.second) + - " (" + s.str() + ")"); + LogDebug("Device is executing " + command_mapping.find(cmd)->second.second + " (" + s.str() + ")"); it->second(); } else { - GetLogger().Trace("Received unsupported command: " + s.str()); + LogTrace("Received unsupported command: " + s.str()); - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE); + throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code); } } @@ -67,18 +66,14 @@ void PrimaryDevice::Reset() int PrimaryDevice::GetId() const { - if (GetController() == nullptr) { - GetLogger().Error("Device is missing its controller"); - } - return GetController() != nullptr ? GetController()->GetTargetId() : -1; } -void PrimaryDevice::SetController(shared_ptr c) +void PrimaryDevice::SetController(AbstractController *c) { controller = c; - logger.SetIdAndLun(c != nullptr ? c->GetTargetId() : -1, GetLun()); + device_logger.SetIdAndLun(GetId(), GetLun()); } void PrimaryDevice::TestUnitReady() @@ -91,11 +86,11 @@ void PrimaryDevice::TestUnitReady() void PrimaryDevice::Inquiry() { // EVPD and page code check - if ((GetController()->GetCmd(1) & 0x01) || GetController()->GetCmd(2)) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + if ((GetController()->GetCmdByte(1) & 0x01) || GetController()->GetCmdByte(2)) { + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } - vector buf = InquiryInternal(); + const vector buf = InquiryInternal(); const size_t allocation_length = min(buf.size(), static_cast(GetInt16(GetController()->GetCmd(), 3))); @@ -103,8 +98,8 @@ void PrimaryDevice::Inquiry() GetController()->SetLength(static_cast(allocation_length)); // Report if the device does not support the requested LUN - if (int lun = GetController()->GetEffectiveLun(); !GetController()->HasDeviceForLun(lun)) { - GetLogger().Trace("LUN is not available"); + if (const int lun = GetController()->GetEffectiveLun(); !GetController()->HasDeviceForLun(lun)) { + LogTrace("LUN is not available"); // Signal that the requested LUN does not exist GetController()->GetBuffer().data()[0] = 0x7f; @@ -116,8 +111,8 @@ void PrimaryDevice::Inquiry() void PrimaryDevice::ReportLuns() { // Only SELECT REPORT mode 0 is supported - if (GetController()->GetCmd(2)) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + if (GetController()->GetCmdByte(2)) { + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } const uint32_t allocation_length = GetInt32(GetController()->GetCmd(), 6); @@ -155,14 +150,14 @@ void PrimaryDevice::RequestSense() lun = 0; // Do not raise an exception here because the rest of the code must be executed - GetController()->Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_LUN); + GetController()->Error(sense_key::illegal_request, asc::invalid_lun); - GetController()->SetStatus(status::GOOD); + GetController()->SetStatus(status::good); } vector buf = GetController()->GetDeviceForLun(lun)->HandleRequestSense(); - const size_t allocation_length = min(buf.size(), static_cast(GetController()->GetCmd(4))); + const size_t allocation_length = min(buf.size(), static_cast(GetController()->GetCmdByte(4))); memcpy(GetController()->GetBuffer().data(), buf.data(), allocation_length); GetController()->SetLength(static_cast(allocation_length)); @@ -173,13 +168,13 @@ void PrimaryDevice::RequestSense() void PrimaryDevice::SendDiagnostic() { // Do not support PF bit - if (GetController()->GetCmd(1) & 0x10) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + if (GetController()->GetCmdByte(1) & 0x10) { + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } // Do not support parameter list - if ((GetController()->GetCmd(3) != 0) || (GetController()->GetCmd(4) != 0)) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + if ((GetController()->GetCmdByte(3) != 0) || (GetController()->GetCmdByte(4) != 0)) { + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } EnterStatusPhase(); @@ -190,24 +185,24 @@ void PrimaryDevice::CheckReady() // Not ready if reset if (IsReset()) { SetReset(false); - GetLogger().Trace("Device in reset"); - throw scsi_exception(sense_key::UNIT_ATTENTION, asc::POWER_ON_OR_RESET); + LogTrace("Device in reset"); + throw scsi_exception(sense_key::unit_attention, asc::power_on_or_reset); } // Not ready if it needs attention if (IsAttn()) { SetAttn(false); - GetLogger().Trace("Device in needs attention"); - throw scsi_exception(sense_key::UNIT_ATTENTION, asc::NOT_READY_TO_READY_CHANGE); + LogTrace("Device in needs attention"); + throw scsi_exception(sense_key::unit_attention, asc::not_ready_to_ready_change); } // Return status if not ready if (!IsReady()) { - GetLogger().Trace("Device not ready"); - throw scsi_exception(sense_key::NOT_READY, asc::MEDIUM_NOT_PRESENT); + LogTrace("Device not ready"); + throw scsi_exception(sense_key::not_ready, asc::medium_not_present); } - GetLogger().Trace("Device is ready"); + LogTrace("Device is ready"); } vector PrimaryDevice::HandleInquiry(device_type type, scsi_level level, bool is_removable) const @@ -223,8 +218,8 @@ vector PrimaryDevice::HandleInquiry(device_type type, scsi_level level, buf[0] = static_cast(type); buf[1] = is_removable ? 0x80 : 0x00; buf[2] = static_cast(level); - buf[3] = level >= scsi_level::SCSI_2 ? - static_cast(scsi_level::SCSI_2) : static_cast(scsi_level::SCSI_1_CCS); + buf[3] = level >= scsi_level::scsi_2 ? + static_cast(scsi_level::scsi_2) : static_cast(scsi_level::scsi_1_ccs); buf[4] = 0x1F; // Padded vendor, product, revision @@ -237,7 +232,7 @@ vector PrimaryDevice::HandleRequestSense() const { // Return not ready only if there are no errors if (!GetStatusCode() && !IsReady()) { - throw scsi_exception(sense_key::NOT_READY, asc::MEDIUM_NOT_PRESENT); + throw scsi_exception(sense_key::not_ready, asc::medium_not_present); } // Set 18 bytes including extended sense data @@ -257,14 +252,14 @@ vector PrimaryDevice::HandleRequestSense() const << "Status $" << static_cast(GetController()->GetStatus()) << ", Sense Key $" << static_cast(buf[2]) << ", ASC $" << static_cast(buf[12]); - GetLogger().Trace(s.str()); + LogTrace(s.str()); return buf; } -bool PrimaryDevice::WriteByteSequence(vector&, uint32_t) +bool PrimaryDevice::WriteByteSequence(span) { - GetLogger().Error("Writing bytes is not supported by this device"); + LogError("Writing bytes is not supported by this device"); return false; } @@ -274,10 +269,10 @@ void PrimaryDevice::ReserveUnit() reserving_initiator = GetController()->GetInitiatorId(); if (reserving_initiator != -1) { - GetLogger().Trace("Reserved device for initiator ID " + to_string(reserving_initiator)); + LogTrace("Reserved device for initiator ID " + to_string(reserving_initiator)); } else { - GetLogger().Trace("Reserved device for unknown initiator"); + LogTrace("Reserved device for unknown initiator"); } EnterStatusPhase(); @@ -286,10 +281,10 @@ void PrimaryDevice::ReserveUnit() void PrimaryDevice::ReleaseUnit() { if (reserving_initiator != -1) { - GetLogger().Trace("Released device reserved by initiator ID " + to_string(reserving_initiator)); + LogTrace("Released device reserved by initiator ID " + to_string(reserving_initiator)); } else { - GetLogger().Trace("Released device reserved by unknown initiator"); + LogTrace("Released device reserved by unknown initiator"); } DiscardReservation(); @@ -313,10 +308,10 @@ bool PrimaryDevice::CheckReservation(int initiator_id, scsi_command cmd, bool pr } if (initiator_id != -1) { - GetLogger().Trace("Initiator ID " + to_string(initiator_id) + " tries to access reserved device"); + LogTrace("Initiator ID " + to_string(initiator_id) + " tries to access reserved device"); } else { - GetLogger().Trace("Unknown initiator tries to access reserved device"); + LogTrace("Unknown initiator tries to access reserved device"); } return false; diff --git a/cpp/devices/primary_device.h b/cpp/devices/primary_device.h index e8b10bf1..682a4f97 100644 --- a/cpp/devices/primary_device.h +++ b/cpp/devices/primary_device.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // A device implementing mandatory SCSI primary commands, to be used for subclassing // @@ -18,6 +18,7 @@ #include "device_logger.h" #include #include +#include #include using namespace std; @@ -25,6 +26,8 @@ using namespace scsi_defs; class PrimaryDevice: private ScsiPrimaryCommands, public Device { + friend class AbstractController; + using operation = function; public: @@ -32,15 +35,16 @@ public: PrimaryDevice(PbDeviceType type, int lun) : Device(type, lun) {} ~PrimaryDevice() override = default; - virtual bool Init(const unordered_map&); + virtual bool Init(const param_map&); + virtual void CleanUp() { + // Override if cleanup work is required for a derived device + }; virtual void Dispatch(scsi_command); int GetId() const override; - void SetController(shared_ptr); - - virtual bool WriteByteSequence(vector&, uint32_t); + virtual bool WriteByteSequence(span); int GetSendDelay() const { return send_delay; } @@ -57,8 +61,6 @@ protected: void AddCommand(scsi_command, const operation&); - const DeviceLogger& GetLogger() const { return logger; } - vector HandleInquiry(scsi_defs::device_type, scsi_level, bool) const; virtual vector InquiryInternal() const = 0; void CheckReady(); @@ -69,16 +71,24 @@ protected: void ReserveUnit() override; void ReleaseUnit() override; - void EnterStatusPhase() const { controller.lock()->Status(); } - void EnterDataInPhase() const { controller.lock()->DataIn(); } - void EnterDataOutPhase() const { controller.lock()->DataOut(); } + void EnterStatusPhase() const { controller->Status(); } + void EnterDataInPhase() const { controller->DataIn(); } + void EnterDataOutPhase() const { controller->DataOut(); } - inline shared_ptr GetController() const { return controller.lock(); } + auto GetController() const { return controller; } + + void LogTrace(const string& s) const { device_logger.Trace(s); } + void LogDebug(const string& s) const { device_logger.Debug(s); } + void LogInfo(const string& s) const { device_logger.Info(s); } + void LogWarn(const string& s) const { device_logger.Warn(s); } + void LogError(const string& s) const { device_logger.Error(s); } private: static const int NOT_RESERVED = -2; + void SetController(AbstractController *); + void TestUnitReady() override; void RequestSense() override; void ReportLuns() override; @@ -86,9 +96,11 @@ private: vector HandleRequestSense() const; - DeviceLogger logger; + // TODO Try to remove this field and use controller->Log*() methods instead + DeviceLogger device_logger; - weak_ptr controller; + // Owned by the controller manager + AbstractController *controller = nullptr; unordered_map commands; diff --git a/cpp/devices/scsi_command_util.cpp b/cpp/devices/scsi_command_util.cpp index 346bdf6f..20bb83f7 100644 --- a/cpp/devices/scsi_command_util.cpp +++ b/cpp/devices/scsi_command_util.cpp @@ -3,31 +3,31 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "shared/piscsi_exceptions.h" -#include "device_logger.h" #include "scsi_command_util.h" +#include #include -#include #include #include using namespace scsi_defs; -void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd, const vector& cdb, - const vector& buf, int length, int sector_size) +string scsi_command_util::ModeSelect(scsi_command cmd, cdb_t cdb, span buf, int length, int sector_size) { assert(cmd == scsi_command::eCmdModeSelect6 || cmd == scsi_command::eCmdModeSelect10); assert(length >= 0); + string result; + // PF if (!(cdb[1] & 0x10)) { // Vendor-specific parameters (SCSI-1) are not supported. // Do not report an error in order to support Apple's HD SC Setup. - return; + return result; } // Skip block descriptors @@ -45,9 +45,9 @@ void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd, // Parse the pages while (length > 0) { // Format device page - if (int page = buf[offset]; page == 0x03) { + if (const int page = buf[offset]; page == 0x03) { if (length < 14) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_PARAMETER_LIST); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_parameter_list); } // With this page the sector size for a subsequent FORMAT can be selected, but only very few @@ -56,8 +56,8 @@ void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd, if (GetInt16(buf, offset + 12) != sector_size) { // With piscsi it is not possible to permanently (by formatting) change the sector size, // because the size is an externally configurable setting only - logger.Warn("In order to change the sector size use the -b option when launching piscsi"); - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_PARAMETER_LIST); + spdlog::warn("In order to change the sector size use the -b option when launching piscsi"); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_parameter_list); } has_valid_page_code = true; @@ -65,7 +65,7 @@ void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd, else { stringstream s; s << "Unknown MODE SELECT page code: $" << setfill('0') << setw(2) << hex << page; - logger.Warn(s.str()); + result = s.str(); } // Advance to the next page @@ -76,8 +76,10 @@ void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd, } if (!has_valid_page_code) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_PARAMETER_LIST); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_parameter_list); } + + return result; } void scsi_command_util::EnrichFormatPage(map>& pages, bool changeable, int sector_size) @@ -97,33 +99,19 @@ void scsi_command_util::AddAppleVendorModePage(map>& pages, bo // No changeable area if (!changeable) { - const char APPLE_DATA[] = "APPLE COMPUTER, INC "; + constexpr const char APPLE_DATA[] = "APPLE COMPUTER, INC "; memcpy(&pages[48].data()[2], APPLE_DATA, sizeof(APPLE_DATA)); } } -int scsi_command_util::GetInt16(const vector& buf, int offset) -{ - assert(buf.size() > static_cast(offset) + 1); - - return (static_cast(buf[offset]) << 8) | buf[offset + 1]; -} - -int scsi_command_util::GetInt16(const vector& buf, int offset) -{ - assert(buf.size() > static_cast(offset) + 1); - - return (buf[offset] << 8) | buf[offset + 1]; -} - -int scsi_command_util::GetInt24(const vector& buf, int offset) +int scsi_command_util::GetInt24(span buf, int offset) { assert(buf.size() > static_cast(offset) + 2); return (buf[offset] << 16) | (buf[offset + 1] << 8) | buf[offset + 2]; } -uint32_t scsi_command_util::GetInt32(const vector& buf, int offset) +uint32_t scsi_command_util::GetInt32(span buf, int offset) { assert(buf.size() > static_cast(offset) + 3); @@ -131,7 +119,7 @@ uint32_t scsi_command_util::GetInt32(const vector& buf, int offset) (static_cast(buf[offset + 2]) << 8) | static_cast(buf[offset + 3]); } -uint64_t scsi_command_util::GetInt64(const vector& buf, int offset) +uint64_t scsi_command_util::GetInt64(span buf, int offset) { assert(buf.size() > static_cast(offset) + 7); @@ -141,42 +129,6 @@ uint64_t scsi_command_util::GetInt64(const vector& buf, int offset) (static_cast(buf[offset + 6]) << 8) | static_cast(buf[offset + 7]); } -void scsi_command_util::SetInt16(vector& buf, int offset, int value) -{ - assert(buf.size() > static_cast(offset) + 1); - - buf[offset] = static_cast(value >> 8); - buf[offset + 1] = static_cast(value); -} - -void scsi_command_util::SetInt32(vector& buf, int offset, uint32_t value) -{ - assert(buf.size() > static_cast(offset) + 3); - - buf[offset] = static_cast(value >> 24); - buf[offset + 1] = static_cast(value >> 16); - buf[offset + 2] = static_cast(value >> 8); - buf[offset + 3] = static_cast(value); -} - -void scsi_command_util::SetInt16(vector& buf, int offset, int value) -{ - assert(buf.size() > static_cast(offset) + 1); - - buf[offset] = static_cast(value >> 8); - buf[offset + 1] = static_cast(value); -} - -void scsi_command_util::SetInt32(vector& buf, int offset, uint32_t value) -{ - assert(buf.size() > static_cast(offset) + 3); - - buf[offset] = static_cast(value >> 24); - buf[offset + 1] = static_cast(value >> 16); - buf[offset + 2] = static_cast(value >> 8); - buf[offset + 3] = static_cast(value); -} - void scsi_command_util::SetInt64(vector& buf, int offset, uint64_t value) { assert(buf.size() > static_cast(offset) + 7); diff --git a/cpp/devices/scsi_command_util.h b/cpp/devices/scsi_command_util.h index bc688252..0fecac8d 100644 --- a/cpp/devices/scsi_command_util.h +++ b/cpp/devices/scsi_command_util.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // Shared code for SCSI command implementations // @@ -12,27 +12,49 @@ #pragma once #include "shared/scsi.h" +#include +#include +#include #include #include using namespace std; -class DeviceLogger; - namespace scsi_command_util { - void ModeSelect(const DeviceLogger&, scsi_defs::scsi_command, const vector&, const vector&, int, int); + string ModeSelect(scsi_defs::scsi_command, cdb_t, span, int, int); void EnrichFormatPage(map>&, bool, int); void AddAppleVendorModePage(map>&, bool); - int GetInt16(const vector&, int); - int GetInt16(const vector&, int); - int GetInt24(const vector&, int); - uint32_t GetInt32(const vector&, int); - uint64_t GetInt64(const vector&, int); - void SetInt16(vector&, int, int); - void SetInt32(vector&, int, uint32_t); - void SetInt16(vector&, int, int); - void SetInt32(vector&, int, uint32_t); + int GetInt16(const auto buf, int offset) + { + assert(buf.size() > static_cast(offset) + 1); + + return (static_cast(buf[offset]) << 8) | buf[offset + 1]; + }; + + template + void SetInt16(vector& buf, int offset, int value) + { + assert(buf.size() > static_cast(offset) + 1); + + buf[offset] = static_cast(value >> 8); + buf[offset + 1] = static_cast(value); + } + + template + void SetInt32(vector& buf, int offset, uint32_t value) + { + assert(buf.size() > static_cast(offset) + 3); + + buf[offset] = static_cast(value >> 24); + buf[offset + 1] = static_cast(value >> 16); + buf[offset + 2] = static_cast(value >> 8); + buf[offset + 3] = static_cast(value); + } + + int GetInt24(span, int); + uint32_t GetInt32(span , int); + uint64_t GetInt64(span, int); void SetInt64(vector&, int, uint64_t); } diff --git a/cpp/devices/scsi_daynaport.cpp b/cpp/devices/scsi_daynaport.cpp index 1fab6e6e..e710ab33 100644 --- a/cpp/devices/scsi_daynaport.cpp +++ b/cpp/devices/scsi_daynaport.cpp @@ -38,7 +38,7 @@ SCSIDaynaPort::SCSIDaynaPort(int lun) : PrimaryDevice(SCDP, lun) SupportsParams(true); } -bool SCSIDaynaPort::Init(const unordered_map& params) +bool SCSIDaynaPort::Init(const param_map& params) { PrimaryDevice::Init(params); @@ -55,15 +55,13 @@ bool SCSIDaynaPort::Init(const unordered_map& params) SetSendDelay(DAYNAPORT_READ_HEADER_SZ); m_bTapEnable = m_tap.Init(GetParams()); - if(!m_bTapEnable){ - GetLogger().Error("Unable to open the TAP interface"); - + if (!m_bTapEnable) { // Not terminating on regular Linux PCs is helpful for testing #if !defined(__x86_64__) && !defined(__X86__) - return false; + return false; #endif } else { - GetLogger().Trace("Tap interface created"); + LogTrace("Tap interface created"); } Reset(); @@ -73,9 +71,14 @@ bool SCSIDaynaPort::Init(const unordered_map& params) return true; } +void SCSIDaynaPort::CleanUp() +{ + m_tap.CleanUp(); +} + vector SCSIDaynaPort::InquiryInternal() const { - vector buf = HandleInquiry(device_type::PROCESSOR, scsi_level::SCSI_2, false); + vector buf = HandleInquiry(device_type::processor, scsi_level::scsi_2, false); // The Daynaport driver for the Mac expects 37 bytes: Increase additional length and // add a vendor-specific byte in order to satisfy this driver. @@ -116,14 +119,14 @@ vector SCSIDaynaPort::InquiryInternal() const // - The SCSI/Link apparently has about 6KB buffer space for packets. // //--------------------------------------------------------------------------- -int SCSIDaynaPort::Read(const vector& cdb, vector& buf, uint64_t) +int SCSIDaynaPort::Read(cdb_t cdb, vector& buf, uint64_t) const { int rx_packet_size = 0; const auto response = (scsi_resp_read_t*)buf.data(); const int requested_length = cdb[4]; - GetLogger().Trace("Read maximum length: " + to_string(requested_length)); + LogTrace("Read maximum length: " + to_string(requested_length)); // At startup the host may send a READ(6) command with a sector count of 1 to read the root sector. // We should respond by going into the status mode with a code of 0x02. @@ -146,13 +149,13 @@ int SCSIDaynaPort::Read(const vector& cdb, vector& buf, uint64_t) // If we didn't receive anything, return size of 0 if (rx_packet_size <= 0) { - GetLogger().Trace("No packet received"); + LogTrace("No packet received"); response->length = 0; response->flags = read_data_flags_t::e_no_more_data; return DAYNAPORT_READ_HEADER_SZ; } - GetLogger().Trace("Packet Size " + to_string(rx_packet_size) + ", read count: " + to_string(read_count)); + LogTrace("Packet Size " + to_string(rx_packet_size) + ", read count: " + to_string(read_count)); // This is a very basic filter to prevent unnecessary packets from // being sent to the SCSI initiator. @@ -187,11 +190,11 @@ int SCSIDaynaPort::Read(const vector& cdb, vector& buf, uint64_t) for (int i = 0 ; i < 6; i++) { s << " $" << static_cast(response->data[i]); } - GetLogger().Debug(s.str()); + LogDebug(s.str()); // If there are pending packets to be processed, we'll tell the host that the read // length was 0. - if (!m_tap.PendingPackets()) { + if (!m_tap.HasPendingPackets()) { response->length = 0; response->flags = read_data_flags_t::e_no_more_data; return DAYNAPORT_READ_HEADER_SZ; @@ -216,7 +219,7 @@ int SCSIDaynaPort::Read(const vector& cdb, vector& buf, uint64_t) size = 64; } SetInt16(buf, 0, size); - SetInt32(buf, 2, m_tap.PendingPackets() ? 0x10 : 0x00); + SetInt32(buf, 2, m_tap.HasPendingPackets() ? 0x10 : 0x00); // Return the packet size + 2 for the length + 4 for the flag field // The CRC was already appended by the ctapdriver @@ -249,25 +252,23 @@ int SCSIDaynaPort::Read(const vector& cdb, vector& buf, uint64_t) // XX XX ... is the actual packet // //--------------------------------------------------------------------------- -bool SCSIDaynaPort::WriteBytes(const vector& cdb, vector& buf, uint32_t) +bool SCSIDaynaPort::Write(cdb_t cdb, span buf) const { - const int data_format = cdb[5]; - int data_length = GetInt16(cdb, 3); - - if (data_format == 0x00) { + if (const int data_format = cdb[5]; data_format == 0x00) { + const int data_length = GetInt16(cdb, 3); m_tap.Send(buf.data(), data_length); - GetLogger().Trace("Transmitted " + to_string(data_length) + " byte(s) (00 format)"); + LogTrace("Transmitted " + to_string(data_length) + " byte(s) (00 format)"); } else if (data_format == 0x80) { // The data length is specified in the first 2 bytes of the payload - data_length = buf[1] + ((static_cast(buf[0]) & 0xff) << 8); + const int data_length = buf[1] + ((static_cast(buf[0]) & 0xff) << 8); m_tap.Send(&(buf.data()[4]), data_length); - GetLogger().Trace("Transmitted " + to_string(data_length) + "byte(s) (80 format)"); + LogTrace("Transmitted " + to_string(data_length) + "byte(s) (80 format)"); } else { stringstream s; s << "Unknown data format: " << setfill('0') << setw(2) << hex << data_format; - GetLogger().Warn(s.str()); + LogWarn(s.str()); } GetController()->SetBlocks(0); @@ -291,7 +292,7 @@ bool SCSIDaynaPort::WriteBytes(const vector& cdb, vector& buf, uin // - long #3: frames lost // //--------------------------------------------------------------------------- -int SCSIDaynaPort::RetrieveStats(const vector& cdb, vector& buf) const +int SCSIDaynaPort::RetrieveStats(cdb_t cdb, vector& buf) const { memcpy(buf.data(), &m_scsi_link_stats, sizeof(m_scsi_link_stats)); @@ -304,7 +305,7 @@ void SCSIDaynaPort::TestUnitReady() EnterStatusPhase(); } -void SCSIDaynaPort::Read6() +void SCSIDaynaPort::Read6() const { // Get record number and block number const uint32_t record = GetInt24(GetController()->GetCmd(), 1) & 0x1fffff; @@ -312,17 +313,17 @@ void SCSIDaynaPort::Read6() // If any commands have a bogus control value, they were probably not // generated by the DaynaPort driver so ignore them - if (GetController()->GetCmd(5) != 0xc0 && GetController()->GetCmd(5) != 0x80) { - GetLogger().Trace("Control value: " + to_string(GetController()->GetCmd(5))); - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + if (GetController()->GetCmdByte(5) != 0xc0 && GetController()->GetCmdByte(5) != 0x80) { + LogTrace("Control value: " + to_string(GetController()->GetCmdByte(5))); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } stringstream s; s << "READ(6) command, record: $" << setfill('0') << setw(8) << hex << record; - GetLogger().Trace(s.str() + ", blocks: " + to_string(GetController()->GetBlocks())); + LogTrace(s.str()); GetController()->SetLength(Read(GetController()->GetCmd(), GetController()->GetBuffer(), record)); - GetLogger().Trace("Length is " + to_string(GetController()->GetLength())); + LogTrace("Length is " + to_string(GetController()->GetLength())); // Set next block GetController()->SetNext(record + 1); @@ -335,7 +336,7 @@ void SCSIDaynaPort::Write6() const // Ensure a sufficient buffer size (because it is not transfer for each block) GetController()->AllocateBuffer(DAYNAPORT_BUFFER_SIZE); - const int data_format = GetController()->GetCmd(5); + const int data_format = GetController()->GetCmdByte(5); if (data_format == 0x00) { GetController()->SetLength(GetInt16(GetController()->GetCmd(), 3)); @@ -346,15 +347,15 @@ void SCSIDaynaPort::Write6() const else { stringstream s; s << "Unknown data format: " << setfill('0') << setw(2) << hex << data_format; - GetLogger().Warn(s.str()); + LogWarn(s.str()); } stringstream s; s << "Length: " << GetController()->GetLength() << ", format: $" << setfill('0') << setw(2) << hex << data_format; - GetLogger().Trace(s.str()); + LogTrace(s.str()); if (GetController()->GetLength() <= 0) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } // Set next block @@ -406,7 +407,7 @@ void SCSIDaynaPort::SetInterfaceMode() const // Check whether this command is telling us to "Set Interface Mode" or "Set MAC Address" GetController()->SetLength(RetrieveStats(GetController()->GetCmd(), GetController()->GetBuffer())); - switch(GetController()->GetCmd(5)){ + switch(GetController()->GetCmdByte(5)){ case CMD_SCSILINK_SETMODE: // Not implemented, do nothing EnterStatusPhase(); @@ -419,21 +420,21 @@ void SCSIDaynaPort::SetInterfaceMode() const default: stringstream s; - s << "Unsupported SetInterface command: " << setfill('0') << setw(2) << hex << GetController()->GetCmd(5); - GetLogger().Warn(s.str()); - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE); + s << "Unsupported SetInterface command: " << setfill('0') << setw(2) << hex << GetController()->GetCmdByte(5); + LogWarn(s.str()); + throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code); break; } } void SCSIDaynaPort::SetMcastAddr() const { - GetController()->SetLength(GetController()->GetCmd(4)); + GetController()->SetLength(GetController()->GetCmdByte(4)); if (GetController()->GetLength() == 0) { stringstream s; - s << "Unsupported SetMcastAddr command: " << setfill('0') << setw(2) << hex << GetController()->GetCmd(2); - GetLogger().Warn(s.str()); - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + s << "Unsupported SetMcastAddr command: " << setfill('0') << setw(2) << hex << GetController()->GetCmdByte(2); + LogWarn(s.str()); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } EnterDataOutPhase(); @@ -451,27 +452,27 @@ void SCSIDaynaPort::SetMcastAddr() const // seconds // //--------------------------------------------------------------------------- -void SCSIDaynaPort::EnableInterface() +void SCSIDaynaPort::EnableInterface() const { - if (GetController()->GetCmd(5) & 0x80) { - if (!m_tap.Enable()) { - GetLogger().Warn("Unable to enable the DaynaPort Interface"); + if (GetController()->GetCmdByte(5) & 0x80) { + if (const string error = m_tap.IpLink(true); !error.empty()) { + LogWarn("Unable to enable the DaynaPort Interface: " + error); - throw scsi_exception(sense_key::ABORTED_COMMAND); + throw scsi_exception(sense_key::aborted_command); } m_tap.Flush(); - GetLogger().Info("The DaynaPort interface has been ENABLED"); + LogInfo("The DaynaPort interface has been ENABLED"); } else { - if (!m_tap.Disable()) { - GetLogger().Warn("Unable to disable the DaynaPort Interface"); + if (const string error = m_tap.IpLink(false); !error.empty()) { + LogWarn("Unable to disable the DaynaPort Interface: " + error); - throw scsi_exception(sense_key::ABORTED_COMMAND); + throw scsi_exception(sense_key::aborted_command); } - GetLogger().Info("The DaynaPort interface has been DISABLED"); + LogInfo("The DaynaPort interface has been DISABLED"); } EnterStatusPhase(); diff --git a/cpp/devices/scsi_daynaport.h b/cpp/devices/scsi_daynaport.h index f0ca53f4..45d0ea50 100644 --- a/cpp/devices/scsi_daynaport.h +++ b/cpp/devices/scsi_daynaport.h @@ -29,10 +29,11 @@ #pragma once -#include "interfaces/byte_writer.h" #include "primary_device.h" #include "ctapdriver.h" +#include #include +#include #include #include @@ -41,29 +42,30 @@ // DaynaPort SCSI Link // //=========================================================================== -class SCSIDaynaPort : public PrimaryDevice, public ByteWriter +class SCSIDaynaPort : public PrimaryDevice { public: explicit SCSIDaynaPort(int); ~SCSIDaynaPort() override = default; - bool Init(const unordered_map&) override; + bool Init(const param_map&) override; + void CleanUp() override; // Commands vector InquiryInternal() const override; - int Read(const vector&, vector&, uint64_t); - bool WriteBytes(const vector&, vector&, uint32_t) override; + int Read(cdb_t, vector&, uint64_t) const; + bool Write(cdb_t, span) const; - int RetrieveStats(const vector&, vector&) const; + int RetrieveStats(cdb_t, vector&) const; void TestUnitReady() override; - void Read6(); + void Read6() const; void Write6() const; void RetrieveStatistics() const; void SetInterfaceMode() const; void SetMcastAddr() const; - void EnableInterface(); + void EnableInterface() const; static const int DAYNAPORT_BUFFER_SIZE = 0x1000000; diff --git a/cpp/devices/scsi_host_bridge.cpp b/cpp/devices/scsi_host_bridge.cpp index 6e2038c8..93ef1c0d 100644 --- a/cpp/devices/scsi_host_bridge.cpp +++ b/cpp/devices/scsi_host_bridge.cpp @@ -20,7 +20,6 @@ #include "scsi_command_util.h" #include "scsi_host_bridge.h" #include -#include using namespace std; using namespace scsi_defs; @@ -31,7 +30,7 @@ SCSIBR::SCSIBR(int lun) : PrimaryDevice(SCBR, lun) SupportsParams(true); } -bool SCSIBR::Init(const unordered_map& params) +bool SCSIBR::Init(const param_map& params) { PrimaryDevice::Init(params); @@ -43,10 +42,8 @@ bool SCSIBR::Init(const unordered_map& params) AddCommand(scsi_command::eCmdSendMessage10, [this] { SendMessage10(); }); #ifdef __linux__ - // TAP Driver Generation m_bTapEnable = tap.Init(GetParams()); if (!m_bTapEnable){ - GetLogger().Error("Unable to open the TAP interface"); return false; } #endif @@ -70,9 +67,14 @@ bool SCSIBR::Init(const unordered_map& params) #endif } +void SCSIBR::CleanUp() +{ + tap.CleanUp(); +} + vector SCSIBR::InquiryInternal() const { - vector buf = HandleInquiry(device_type::COMMUNICATIONS, scsi_level::SCSI_2, false); + vector buf = HandleInquiry(device_type::communications, scsi_level::scsi_2, false); // The bridge returns more additional bytes than the other devices buf.resize(0x1F + 8 + 5); @@ -99,7 +101,7 @@ void SCSIBR::TestUnitReady() EnterStatusPhase(); } -int SCSIBR::GetMessage10(const vector& cdb, vector& buf) +int SCSIBR::GetMessage10(cdb_t cdb, vector& buf) { // Type const int type = cdb[2]; @@ -187,7 +189,7 @@ int SCSIBR::GetMessage10(const vector& cdb, vector& buf) return 0; } -bool SCSIBR::WriteBytes(const vector& cdb, vector& buf, uint32_t) +bool SCSIBR::ReadWrite(cdb_t cdb, vector& buf) { // Type const int type = cdb[2]; @@ -252,7 +254,7 @@ void SCSIBR::GetMessage10() GetController()->SetLength(GetMessage10(GetController()->GetCmd(), GetController()->GetBuffer())); if (GetController()->GetLength() <= 0) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } // Set next block @@ -273,7 +275,7 @@ void SCSIBR::SendMessage10() const { GetController()->SetLength(GetInt24(GetController()->GetCmd(), 6)); if (GetController()->GetLength() <= 0) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } // Ensure a sufficient buffer size (because it is not a transfer for each block) @@ -292,7 +294,7 @@ int SCSIBR::GetMacAddr(vector& mac) const return static_cast(mac_addr.size()); } -void SCSIBR::SetMacAddr(const vector& mac) +void SCSIBR::SetMacAddr(span mac) { memcpy(mac_addr.data(), mac.data(), mac_addr.size()); } @@ -339,7 +341,7 @@ void SCSIBR::GetPacketBuf(vector& buf, int index) packet_enable = false; } -void SCSIBR::SendPacket(const vector& buf, int len) +void SCSIBR::SendPacket(span buf, int len) const { tap.Send(buf.data(), len); } @@ -837,7 +839,7 @@ void SCSIBR::FS_GetCapacity(vector& buf) auto dp = (uint32_t*)buf.data(); const uint32_t nUnit = ntohl(*dp); - Human68k::capacity_t cap; + Human68k::capacity_t cap = {}; fsresult = fs.GetCapacity(nUnit, &cap); cap.freearea = htons(cap.freearea); @@ -878,7 +880,7 @@ void SCSIBR::FS_GetDPB(vector& buf) auto dp = (uint32_t*)buf.data(); const uint32_t nUnit = ntohl(*dp); - Human68k::dpb_t dpb; + Human68k::dpb_t dpb = {}; fsresult = fs.GetDPB(nUnit, &dpb); dpb.sector_size = htons(dpb.sector_size); diff --git a/cpp/devices/scsi_host_bridge.h b/cpp/devices/scsi_host_bridge.h index e5f2dbcd..f3a80096 100644 --- a/cpp/devices/scsi_host_bridge.h +++ b/cpp/devices/scsi_host_bridge.h @@ -17,16 +17,16 @@ //--------------------------------------------------------------------------- #pragma once -#include "interfaces/byte_writer.h" #include "primary_device.h" #include "ctapdriver.h" #include "cfilesystem.h" #include +#include #include using namespace std; -class SCSIBR : public PrimaryDevice, public ByteWriter +class SCSIBR : public PrimaryDevice { static constexpr const array bcast_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; @@ -35,12 +35,13 @@ public: explicit SCSIBR(int); ~SCSIBR() override = default; - bool Init(const unordered_map&) override; + bool Init(const param_map&) override; + void CleanUp() override; // Commands vector InquiryInternal() const override; - int GetMessage10(const vector&, vector&); - bool WriteBytes(const vector&, vector&, uint32_t) override; + int GetMessage10(cdb_t, vector&); + bool ReadWrite(cdb_t, vector&); void TestUnitReady() override; void GetMessage10(); void SendMessage10() const; @@ -48,16 +49,16 @@ public: private: int GetMacAddr(vector&) const; // Get MAC address - void SetMacAddr(const vector&); // Set MAC address + void SetMacAddr(span); // Set MAC address void ReceivePacket(); // Receive a packet - void GetPacketBuf(vector&, int); // Get a packet - void SendPacket(const vector&, int); // Send a packet + void GetPacketBuf(vector&, int); // Get a packet + void SendPacket(span, int) const; // Send a packet CTapDriver tap; // TAP driver bool m_bTapEnable = false; // TAP valid flag - array mac_addr = {}; // MAC Address + array mac_addr = {}; // MAC Address int packet_len = 0; // Receive packet size - array packet_buf; // Receive packet buffer + array packet_buf; // Receive packet buffer bool packet_enable = false; // Received packet valid int ReadFsResult(vector&) const; // Read filesystem (result code) diff --git a/cpp/devices/scsi_printer.cpp b/cpp/devices/scsi_printer.cpp index 23046ee4..da4bff40 100644 --- a/cpp/devices/scsi_printer.cpp +++ b/cpp/devices/scsi_printer.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // Implementation of a SCSI printer (see SCSI-2 specification for a command description) // @@ -32,7 +32,6 @@ #include "shared/piscsi_exceptions.h" #include "scsi_command_util.h" #include "scsi_printer.h" -#include #include using namespace std; @@ -45,7 +44,7 @@ SCSIPrinter::SCSIPrinter(int lun) : PrimaryDevice(SCLP, lun) SupportsParams(true); } -bool SCSIPrinter::Init(const unordered_map& params) +bool SCSIPrinter::Init(const param_map& params) { PrimaryDevice::Init(params); @@ -61,7 +60,7 @@ bool SCSIPrinter::Init(const unordered_map& params) AddCommand(scsi_command::eCmdSendDiagnostic, [this] { SendDiagnostic(); }); if (GetParam("cmd").find("%f") == string::npos) { - GetLogger().Trace("Missing filename specifier %f"); + LogTrace("Missing filename specifier %f"); return false; } @@ -82,20 +81,20 @@ void SCSIPrinter::TestUnitReady() vector SCSIPrinter::InquiryInternal() const { - return HandleInquiry(device_type::PRINTER, scsi_level::SCSI_2, false); + return HandleInquiry(device_type::printer, scsi_level::scsi_2, false); } void SCSIPrinter::Print() { const uint32_t length = GetInt24(GetController()->GetCmd(), 2); - GetLogger().Trace("Receiving " + to_string(length) + " byte(s) to be printed"); + LogTrace("Expecting to receive " + to_string(length) + " byte(s) to be printed"); if (length > GetController()->GetBuffer().size()) { - GetLogger().Error("Transfer buffer overflow: Buffer size is " + to_string(GetController()->GetBuffer().size()) + + LogError("Transfer buffer overflow: Buffer size is " + to_string(GetController()->GetBuffer().size()) + " bytes, " + to_string(length) + " bytes expected"); - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } GetController()->SetLength(length); @@ -107,9 +106,9 @@ void SCSIPrinter::Print() void SCSIPrinter::SynchronizeBuffer() { if (!out.is_open()) { - GetLogger().Warn("Nothing to print"); + LogWarn("Nothing to print"); - throw scsi_exception(sense_key::ABORTED_COMMAND); + throw scsi_exception(sense_key::aborted_command); } string cmd = GetParam("cmd"); @@ -118,25 +117,24 @@ void SCSIPrinter::SynchronizeBuffer() cmd.replace(file_position, 2, filename); error_code error; + LogTrace("Printing file '" + filename + "' with " + to_string(file_size(path(filename), error)) + " byte(s)"); - GetLogger().Trace("Printing file '" + filename + "' with " + to_string(file_size(path(filename), error)) + " byte(s)"); - - GetLogger().Debug("Executing '" + cmd + "'"); + LogDebug("Executing print command '" + cmd + "'"); if (system(cmd.c_str())) { - GetLogger().Error("Printing file '" + filename + "' failed, the printing system might not be configured"); + LogError("Printing file '" + filename + "' failed, the printing system might not be configured"); - Cleanup(); + CleanUp(); - throw scsi_exception(sense_key::ABORTED_COMMAND); + throw scsi_exception(sense_key::aborted_command); } - Cleanup(); + CleanUp(); EnterStatusPhase(); } -bool SCSIPrinter::WriteByteSequence(vector& buf, uint32_t length) +bool SCSIPrinter::WriteByteSequence(span buf) { if (!out.is_open()) { vector f(file_template.begin(), file_template.end()); @@ -145,7 +143,7 @@ bool SCSIPrinter::WriteByteSequence(vector& buf, uint32_t length) // There is no C++ API that generates a file with a unique name const int fd = mkstemp(f.data()); if (fd == -1) { - GetLogger().Error("Can't create printer output file for pattern '" + filename + "': " + strerror(errno)); + LogError("Can't create printer output file for pattern '" + filename + "': " + strerror(errno)); return false; } close(fd); @@ -154,21 +152,23 @@ bool SCSIPrinter::WriteByteSequence(vector& buf, uint32_t length) out.open(filename, ios::binary); if (out.fail()) { - throw scsi_exception(sense_key::ABORTED_COMMAND); + throw scsi_exception(sense_key::aborted_command); } - GetLogger().Trace("Created printer output file '" + filename + "'"); + LogTrace("Created printer output file '" + filename + "'"); } - GetLogger().Trace("Appending " + to_string(length) + " byte(s) to printer output file ''" + filename + "'"); + LogTrace("Appending " + to_string(buf.size()) + " byte(s) to printer output file ''" + filename + "'"); - out.write((const char*)buf.data(), length); + out.write((const char *)buf.data(), buf.size()); return !out.fail(); } -void SCSIPrinter::Cleanup() +void SCSIPrinter::CleanUp() { + PrimaryDevice::CleanUp(); + if (out.is_open()) { out.close(); diff --git a/cpp/devices/scsi_printer.h b/cpp/devices/scsi_printer.h index bbad49d1..6e626e54 100644 --- a/cpp/devices/scsi_printer.h +++ b/cpp/devices/scsi_printer.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // Implementation of a SCSI printer (see SCSI-2 specification for a command description) // @@ -15,6 +15,7 @@ #include #include #include +#include using namespace std; @@ -29,11 +30,12 @@ public: explicit SCSIPrinter(int); ~SCSIPrinter() override = default; - bool Init(const unordered_map&) override; + bool Init(const param_map&) override; + void CleanUp() override; vector InquiryInternal() const override; - bool WriteByteSequence(vector&, uint32_t) override; + bool WriteByteSequence(span) override; private: @@ -44,8 +46,6 @@ private: void Print() override; void SynchronizeBuffer(); - void Cleanup(); - string file_template; string filename; diff --git a/cpp/devices/scsicd.cpp b/cpp/devices/scsicd.cpp index bcea0386..200c36c3 100644 --- a/cpp/devices/scsicd.cpp +++ b/cpp/devices/scsicd.cpp @@ -21,7 +21,7 @@ using namespace scsi_defs; using namespace scsi_command_util; -SCSICD::SCSICD(int lun, const unordered_set& sector_sizes, scsi_defs::scsi_level level) +SCSICD::SCSICD(int lun, const unordered_set& sector_sizes, scsi_defs::scsi_level level) : Disk(SCCD, lun), scsi_level(level) { SetSectorSizes(sector_sizes); @@ -31,7 +31,7 @@ SCSICD::SCSICD(int lun, const unordered_set& sector_sizes, scsi_defs:: SetLockable(true); } -bool SCSICD::Init(const unordered_map& params) +bool SCSICD::Init(const param_map& params) { Disk::Init(params); @@ -116,7 +116,7 @@ void SCSICD::OpenIso() if (rawfile) { if (size % 2536) { - GetLogger().Warn("Raw ISO CD-ROM file size is not a multiple of 2536 bytes but is " + LogWarn("Raw ISO CD-ROM file size is not a multiple of 2536 bytes but is " + to_string(size) + " bytes"); } @@ -165,7 +165,7 @@ void SCSICD::ReadToc() vector SCSICD::InquiryInternal() const { - return HandleInquiry(device_type::CD_ROM, scsi_level, true); + return HandleInquiry(device_type::cd_rom, scsi_level, true); } void SCSICD::SetUpModePages(map>& pages, int page, bool changeable) const @@ -216,13 +216,13 @@ void SCSICD::AddVendorPage(map>& pages, int page, bool changea } } -int SCSICD::Read(const vector& cdb, vector& buf, uint64_t block) +int SCSICD::Read(span buf, uint64_t block) { CheckReady(); const int index = SearchTrack(static_cast(block)); if (index < 0) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE); + throw scsi_exception(sense_key::illegal_request, asc::lba_out_of_range); } assert(tracks[index]); @@ -241,15 +241,15 @@ int SCSICD::Read(const vector& cdb, vector& buf, uint64_t block) } assert(dataindex >= 0); - return Disk::Read(cdb, buf, block); + return Disk::Read(buf, block); } -int SCSICD::ReadTocInternal(const vector& cdb, vector& buf) +int SCSICD::ReadTocInternal(cdb_t cdb, vector& buf) { CheckReady(); // If ready, there is at least one track - assert(tracks.size() > 0); + assert(!tracks.empty()); assert(tracks[0]); // Get allocation length, clear buffer @@ -263,7 +263,7 @@ int SCSICD::ReadTocInternal(const vector& cdb, vector& buf) const int last = tracks[tracks.size() - 1]->GetTrackNo(); // Except for AA if (cdb[6] > last && cdb[6] != 0xaa) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } // Check start index @@ -280,7 +280,7 @@ int SCSICD::ReadTocInternal(const vector& cdb, vector& buf) // AA if not found or internal error if (!tracks[index]) { if (cdb[6] != 0xaa) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } // Returns the final LBA+1 because it is AA diff --git a/cpp/devices/scsicd.h b/cpp/devices/scsicd.h index e30ab743..db2620de 100644 --- a/cpp/devices/scsicd.h +++ b/cpp/devices/scsicd.h @@ -17,20 +17,23 @@ #include "cd_track.h" #include "disk.h" #include "interfaces/scsi_mmc_commands.h" +#include +#include +#include class SCSICD : public Disk, private ScsiMmcCommands { public: - SCSICD(int, const unordered_set&, scsi_defs::scsi_level = scsi_level::SCSI_2); + SCSICD(int, const unordered_set&, scsi_defs::scsi_level = scsi_level::scsi_2); ~SCSICD() override = default; - bool Init(const unordered_map&) override; + bool Init(const param_map&) override; void Open() override; vector InquiryInternal() const override; - int Read(const vector&, vector&, uint64_t) override; + int Read(span, uint64_t) override; protected: @@ -39,7 +42,7 @@ protected: private: - int ReadTocInternal(const vector&, vector&); + int ReadTocInternal(cdb_t, vector&); void AddCDROMPage(map>&, bool) const; void AddCDDAPage(map>&, bool) const; diff --git a/cpp/devices/scsihd.cpp b/cpp/devices/scsihd.cpp index 0b0cca89..316c7575 100644 --- a/cpp/devices/scsihd.cpp +++ b/cpp/devices/scsihd.cpp @@ -5,7 +5,7 @@ // // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) // Copyright (C) 2014-2020 GIMONS -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // Copyright (C) akuker // // Licensed under the BSD 3-Clause License. @@ -14,8 +14,8 @@ //--------------------------------------------------------------------------- #include "shared/piscsi_exceptions.h" -#include "scsihd.h" #include "scsi_command_util.h" +#include "scsihd.h" using namespace scsi_command_util; @@ -70,7 +70,7 @@ void SCSIHD::Open() { assert(!IsReady()); - off_t size = GetFileSize(); + const off_t size = GetFileSize(); // Sector size (default 512 bytes) and number of blocks SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 512); @@ -81,12 +81,15 @@ void SCSIHD::Open() vector SCSIHD::InquiryInternal() const { - return HandleInquiry(device_type::DIRECT_ACCESS, scsi_level, IsRemovable()); + return HandleInquiry(device_type::direct_access, scsi_level, IsRemovable()); } -void SCSIHD::ModeSelect(scsi_command cmd, const vector& cdb, const vector& buf, int length) const +void SCSIHD::ModeSelect(scsi_command cmd, cdb_t cdb, span buf, int length) const { - scsi_command_util::ModeSelect(GetLogger(), cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount()); + if (const string result = scsi_command_util::ModeSelect(cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount()); + !result.empty()) { + LogWarn(result); + } } void SCSIHD::AddFormatPage(map>& pages, bool changeable) const diff --git a/cpp/devices/scsihd.h b/cpp/devices/scsihd.h index 44cef7ed..8216ae2a 100644 --- a/cpp/devices/scsihd.h +++ b/cpp/devices/scsihd.h @@ -5,7 +5,7 @@ // // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) // Copyright (C) 2014-2020 GIMONS -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // Copyright (C) akuker // // Licensed under the BSD 3-Clause License. @@ -18,6 +18,7 @@ #include "shared/scsi.h" #include "disk.h" #include +#include #include #include @@ -27,7 +28,7 @@ class SCSIHD : public Disk public: - SCSIHD(int, const unordered_set&, bool, scsi_defs::scsi_level = scsi_level::SCSI_2); + SCSIHD(int, const unordered_set&, bool, scsi_defs::scsi_level = scsi_level::scsi_2); ~SCSIHD() override = default; void FinalizeSetup(off_t); @@ -36,7 +37,7 @@ public: // Commands vector InquiryInternal() const override; - void ModeSelect(scsi_defs::scsi_command, const vector&, const vector&, int) const override; + void ModeSelect(scsi_defs::scsi_command, cdb_t, span, int) const override; void AddFormatPage(map>&, bool) const override; void AddVendorPage(map>&, int, bool) const override; diff --git a/cpp/devices/scsihd_nec.cpp b/cpp/devices/scsihd_nec.cpp index 4a50d8a0..5592e95c 100644 --- a/cpp/devices/scsihd_nec.cpp +++ b/cpp/devices/scsihd_nec.cpp @@ -1,14 +1,15 @@ //--------------------------------------------------------------------------- // -// SCSI Target Emulator PiSCSI -// for Raspberry Pi +// SCSI Target Emulator PiSCSI +// for Raspberry Pi // -// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) -// Copyright (C) 2014-2020 GIMONS -// Copyright (C) akuker +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) 2021-2023 Uwe Seimet +// Copyright (C) akuker // -// Licensed under the BSD 3-Clause License. -// See LICENSE file in the project root folder. +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. // //--------------------------------------------------------------------------- @@ -47,7 +48,7 @@ void SCSIHD_NEC::Open() FinalizeSetup(image_offset); } -pair SCSIHD_NEC::SetParameters(const array& data, int size) +pair SCSIHD_NEC::SetParameters(span data, int size) { array root_sector = {}; memcpy(root_sector.data(), data.data(), root_sector.size()); @@ -107,12 +108,12 @@ pair SCSIHD_NEC::SetParameters(const array& data, int size) throw io_exception("Invalid NEC sector size of " + to_string(sector_size) + " byte(s)"); } - return make_pair(image_size, sector_size); + return { image_size, sector_size }; } vector SCSIHD_NEC::InquiryInternal() const { - return HandleInquiry(device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, false); + return HandleInquiry(device_type::direct_access, scsi_level::scsi_1_ccs, false); } void SCSIHD_NEC::AddFormatPage(map>& pages, bool changeable) const diff --git a/cpp/devices/scsihd_nec.h b/cpp/devices/scsihd_nec.h index ce710347..0bf7db36 100644 --- a/cpp/devices/scsihd_nec.h +++ b/cpp/devices/scsihd_nec.h @@ -47,7 +47,7 @@ protected: private: - pair SetParameters(const array&, int); + pair SetParameters(span, int); static int GetInt16LittleEndian(const uint8_t *); static int GetInt32LittleEndian(const uint8_t *); diff --git a/cpp/devices/scsimo.cpp b/cpp/devices/scsimo.cpp index 2eed267d..167064a1 100644 --- a/cpp/devices/scsimo.cpp +++ b/cpp/devices/scsimo.cpp @@ -1,14 +1,15 @@ //--------------------------------------------------------------------------- // -// SCSI Target Emulator PiSCSI -// for Raspberry Pi +// SCSI Target Emulator PiSCSI +// for Raspberry Pi // -// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) -// Copyright (C) 2014-2020 GIMONS -// Copyright (C) akuker +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) akuker // -// Licensed under the BSD 3-Clause License. -// See LICENSE file in the project root folder. +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. // //--------------------------------------------------------------------------- @@ -23,13 +24,13 @@ SCSIMO::SCSIMO(int lun, const unordered_set& sector_sizes) : Disk(SCMO SetSectorSizes(sector_sizes); // 128 MB, 512 bytes per sector, 248826 sectors - geometries[512 * 248826] = make_pair(512, 248826); + geometries[512 * 248826] = { 512, 248826 }; // 230 MB, 512 bytes per block, 446325 sectors - geometries[512 * 446325] = make_pair(512, 446325); + geometries[512 * 446325] = { 512, 446325 }; // 540 MB, 512 bytes per sector, 1041500 sectors - geometries[512 * 1041500] = make_pair(512, 1041500); + geometries[512 * 1041500] = { 512, 1041500 }; // 640 MB, 20248 bytes per sector, 310352 sectors - geometries[2048 * 310352] = make_pair(2048, 310352); + geometries[2048 * 310352] = { 2048, 310352 }; SetProtectable(true); SetRemovable(true); @@ -61,7 +62,7 @@ void SCSIMO::Open() vector SCSIMO::InquiryInternal() const { - return HandleInquiry(device_type::OPTICAL_MEMORY, scsi_level::SCSI_2, true); + return HandleInquiry(device_type::optical_memory, scsi_level::scsi_2, true); } void SCSIMO::SetUpModePages(map>& pages, int page, bool changeable) const @@ -89,9 +90,12 @@ void SCSIMO::AddOptionPage(map>& pages, bool) const // Do not report update blocks } -void SCSIMO::ModeSelect(scsi_command cmd, const vector& cdb, const vector& buf, int length) const +void SCSIMO::ModeSelect(scsi_command cmd, cdb_t cdb, span buf, int length) const { - scsi_command_util::ModeSelect(GetLogger(), cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount()); + if (const string result = scsi_command_util::ModeSelect(cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount()); + !result.empty()) { + LogWarn(result); + } } // diff --git a/cpp/devices/scsimo.h b/cpp/devices/scsimo.h index c3253efc..41735d3b 100644 --- a/cpp/devices/scsimo.h +++ b/cpp/devices/scsimo.h @@ -5,6 +5,7 @@ // // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) // Copyright (C) 2014-2020 GIMONS +// Copyright (C) 2022-2023 Uwe Seimet // Copyright (C) akuker // // Licensed under the BSD 3-Clause License. @@ -15,6 +16,7 @@ #pragma once #include "disk.h" +#include #include #include @@ -30,7 +32,7 @@ public: void Open() override; vector InquiryInternal() const override; - void ModeSelect(scsi_defs::scsi_command, const vector&, const vector&, int) const override; + void ModeSelect(scsi_defs::scsi_command, cdb_t, span, int) const override; protected: diff --git a/cpp/devices/storage_device.cpp b/cpp/devices/storage_device.cpp index 991c1073..41b0ad8a 100644 --- a/cpp/devices/storage_device.cpp +++ b/cpp/devices/storage_device.cpp @@ -3,15 +3,13 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "shared/piscsi_exceptions.h" #include "storage_device.h" -#include #include -#include using namespace std; using namespace filesystem; @@ -22,22 +20,43 @@ StorageDevice::StorageDevice(PbDeviceType type, int lun) : ModePageDevice(type, SetStoppable(true); } +void StorageDevice::CleanUp() +{ + UnreserveFile(); + + ModePageDevice::CleanUp(); +} + +void StorageDevice::SetFilename(string_view f) +{ + filename = filesystem::path(f); + + // Permanently write-protected + SetReadOnly(IsReadOnlyFile()); + + SetProtectable(!IsReadOnlyFile()); + + if (IsReadOnlyFile()) { + SetProtected(false); + } +} + void StorageDevice::ValidateFile() { if (blocks == 0) { throw io_exception(string(GetTypeString()) + " device has 0 blocks"); } - if (!exists(path(filename))) { - throw file_not_found_exception("Image file '" + filename + "' for " + GetTypeString() + " device does not exist"); + if (!exists(filename)) { + throw file_not_found_exception("Image file '" + filename.string() + "' for " + GetTypeString() + " device does not exist"); } if (GetFileSize() > 2LL * 1024 * 1024 * 1024 * 1024) { - throw io_exception("Drive capacity cannot exceed 2 TiB"); + throw io_exception("Image files > 2 TiB are not supported"); } // TODO Check for duplicate handling of these properties (-> piscsi_executor.cpp) - if (access(filename.c_str(), W_OK)) { + if (IsReadOnlyFile()) { // Permanently write-protected SetReadOnly(true); SetProtectable(false); @@ -50,28 +69,28 @@ void StorageDevice::ValidateFile() SetReady(true); } -void StorageDevice::ReserveFile(const string& file, int id, int lun) const +void StorageDevice::ReserveFile() const { - assert(!file.empty()); - assert(reserved_files.find(file) == reserved_files.end()); + assert(!filename.empty()); + assert(!reserved_files.contains(filename.string())); - reserved_files[file] = make_pair(id, lun); + reserved_files[filename.string()] = { GetId(), GetLun() }; } void StorageDevice::UnreserveFile() { - reserved_files.erase(filename); + reserved_files.erase(filename.string()); - filename = ""; + filename.clear(); } id_set StorageDevice::GetIdsForReservedFile(const string& file) { if (const auto& it = reserved_files.find(file); it != reserved_files.end()) { - return make_pair(it->second.first, it->second.second); + return { it->second.first, it->second.second }; } - return make_pair(-1, -1); + return { -1, -1 }; } void StorageDevice::UnreserveAll() @@ -79,9 +98,9 @@ void StorageDevice::UnreserveAll() reserved_files.clear(); } -bool StorageDevice::FileExists(const string& file) +bool StorageDevice::FileExists(string_view file) { - return exists(file); + return exists(path(file)); } bool StorageDevice::IsReadOnlyFile() const @@ -91,10 +110,10 @@ bool StorageDevice::IsReadOnlyFile() const off_t StorageDevice::GetFileSize() const { - // filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle more than 2 GiB - if (struct stat st; !stat(filename.c_str(), &st)) { - return st.st_size; + try { + return file_size(filename); + } + catch (const filesystem_error& e) { + throw io_exception("Can't get size of '" + filename.string() + "': " + e.what()); } - - throw io_exception("Can't get size of '" + filename + "'"); } diff --git a/cpp/devices/storage_device.h b/cpp/devices/storage_device.h index 10c6b37a..85fc3c6d 100644 --- a/cpp/devices/storage_device.h +++ b/cpp/devices/storage_device.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // The base class for all mass storage devices with image file support // @@ -11,14 +11,14 @@ #pragma once +#include "shared/piscsi_util.h" #include "mode_page_device.h" #include #include +#include using namespace std; -using id_set = pair; - class StorageDevice : public ModePageDevice { public: @@ -26,24 +26,27 @@ public: StorageDevice(PbDeviceType, int); ~StorageDevice() override = default; + void CleanUp() override; + virtual void Open() = 0; - string GetFilename() const { return filename; } - void SetFilename(string_view f) { filename = f; } + string GetFilename() const { return filename.string(); } + void SetFilename(string_view); uint64_t GetBlockCount() const { return blocks; } - void ReserveFile(const string&, int, int) const; + void ReserveFile() const; void UnreserveFile(); + // TODO Remove this method, it is only used by the unit tests static void UnreserveAll(); - static bool FileExists(const string&); - bool IsReadOnlyFile() const; + static bool FileExists(string_view); void SetMediumChanged(bool b) { medium_changed = b; } - static unordered_map GetReservedFiles() { return reserved_files; } - static void SetReservedFiles(const unordered_map& r) { reserved_files = r; } + static auto GetReservedFiles() { return reserved_files; } + static void SetReservedFiles(const unordered_map>& r) + { reserved_files = r; } static id_set GetIdsForReservedFile(const string&); protected: @@ -58,13 +61,14 @@ protected: private: - // Total number of blocks + bool IsReadOnlyFile() const; + uint64_t blocks = 0; - string filename; + filesystem::path filename; bool medium_changed = false; // The list of image files in use and the IDs and LUNs using these files - static inline unordered_map reserved_files; + static inline unordered_map> reserved_files; }; diff --git a/cpp/hal/data_sample.h b/cpp/hal/data_sample.h index 189c519d..c5a57e30 100644 --- a/cpp/hal/data_sample.h +++ b/cpp/hal/data_sample.h @@ -12,6 +12,7 @@ #pragma once #include "shared/scsi.h" +#include #include using namespace scsi_defs; diff --git a/cpp/hal/gpiobus.cpp b/cpp/hal/gpiobus.cpp index e028883c..a6bf2555 100644 --- a/cpp/hal/gpiobus.cpp +++ b/cpp/hal/gpiobus.cpp @@ -13,7 +13,7 @@ #include "hal/gpiobus.h" #include "hal/sbc_version.h" #include "hal/systimer.h" -#include "shared/log.h" +#include #include #include #include @@ -280,8 +280,8 @@ int GPIOBUS::SendHandShake(uint8_t *buf, int count, int delay_after_bytes) if (actmode == mode_e::TARGET) { for (i = 0; i < count; i++) { if (i == delay_after_bytes) { - LOGTRACE("%s DELAYING for %dus after %d bytes", __PRETTY_FUNCTION__, SCSI_DELAY_SEND_DATA_DAYNAPORT_US, - (int)delay_after_bytes) + spdlog::trace("DELAYING for " + to_string(SCSI_DELAY_SEND_DATA_DAYNAPORT_US) + " after " + + to_string(delay_after_bytes) + " bytes"); SysTimer::SleepUsec(SCSI_DELAY_SEND_DATA_DAYNAPORT_US); } @@ -325,8 +325,8 @@ int GPIOBUS::SendHandShake(uint8_t *buf, int count, int delay_after_bytes) for (i = 0; i < count; i++) { if (i == delay_after_bytes) { - LOGTRACE("%s DELAYING for %dus after %d bytes", __PRETTY_FUNCTION__, SCSI_DELAY_SEND_DATA_DAYNAPORT_US, - (int)delay_after_bytes) + spdlog::trace("DELAYING for " + to_string(SCSI_DELAY_SEND_DATA_DAYNAPORT_US) + " after " + + to_string(delay_after_bytes) + " bytes"); SysTimer::SleepUsec(SCSI_DELAY_SEND_DATA_DAYNAPORT_US); } @@ -392,7 +392,7 @@ bool GPIOBUS::PollSelectEvent() return false; #else GPIO_FUNCTION_TRACE - LOGTRACE("%s", __PRETTY_FUNCTION__) + spdlog::trace(__PRETTY_FUNCTION__); errno = 0; int prev_mode = -1; if (SBC_Version::IsBananaPi()) { @@ -401,12 +401,12 @@ bool GPIOBUS::PollSelectEvent() } if (epoll_event epev; epoll_wait(epfd, &epev, 1, -1) <= 0) { - LOGWARN("%s epoll_wait failed", __PRETTY_FUNCTION__) + spdlog::warn("epoll_wait failed"); return false; } if (gpioevent_data gpev; read(selevreq.fd, &gpev, sizeof(gpev)) < 0) { - LOGWARN("%s read failed", __PRETTY_FUNCTION__) + spdlog::warn("read failed"); return false; } @@ -455,4 +455,4 @@ bool GPIOBUS::WaitSignal(int pin, bool ast) // We timed out waiting for the signal return false; -} \ No newline at end of file +} diff --git a/cpp/hal/gpiobus.h b/cpp/hal/gpiobus.h index f7f927b4..74fb820b 100644 --- a/cpp/hal/gpiobus.h +++ b/cpp/hal/gpiobus.h @@ -214,7 +214,7 @@ class GPIOBUS : public BUS // SEL signal event request struct gpioevent_request selevreq = {}; // NOSONAR: This protected so derived classes can access it // epoll file descriptor - int epfd; // NOSONAR: This protected so derived classes can access it + int epfd = 0; // NOSONAR: This is protected so derived classes can access it #endif }; diff --git a/cpp/hal/gpiobus_bananam2p.cpp b/cpp/hal/gpiobus_bananam2p.cpp index a1e1a332..7d42f3a5 100644 --- a/cpp/hal/gpiobus_bananam2p.cpp +++ b/cpp/hal/gpiobus_bananam2p.cpp @@ -34,7 +34,9 @@ #include #include #include +#ifdef __linux__ #include +#endif #include #include @@ -43,7 +45,7 @@ #include "hal/pi_defs/bpi-gpio.h" #include "hal/sunxi_utils.h" #include "hal/systimer.h" -#include "shared/log.h" +#include "hal/log.h" #define ARRAY_SIZE(x) (sizeof(x) / (sizeof(x[0]))) @@ -62,7 +64,7 @@ bool GPIOBUS_BananaM2p::Init(mode_e mode) int gpio_bank = SunXI::GPIO_BANK(gpio_num); - if (std::find(gpio_banks.begin(), gpio_banks.end(), gpio_bank) != gpio_banks.end()) { + if (std::ranges::find(gpio_banks, gpio_bank) != gpio_banks.end()) { LOGTRACE("Duplicate bank: %d", gpio_bank) } else { @@ -723,9 +725,9 @@ void GPIOBUS_BananaM2p::SetSignal(int pin, bool ast) } if (sunxi_gpio_state == SunXI::HIGH) - pio->DAT &= ~(1 << num); + pio->DAT = pio->DAT & ~(1 << num); else - pio->DAT |= (1 << num); + pio->DAT = pio->DAT | (1 << num); } void GPIOBUS_BananaM2p::DisableIRQ() @@ -978,9 +980,9 @@ void GPIOBUS_BananaM2p::sunxi_output_gpio(int pin, int value) } if (value == 0) - pio->DAT &= ~(1 << num); + pio->DAT = pio->DAT & ~(1 << num); else - pio->DAT |= (1 << num); + pio->DAT = pio->DAT | (1 << num); } void GPIOBUS_BananaM2p::sunxi_set_all_gpios(array &mask, array &value) @@ -1043,11 +1045,11 @@ void GPIOBUS_BananaM2p::set_pullupdn(int pin, int pud) else if (pud == SunXI::PUD_UP) *(gpio_map + SunXI::PULLUPDN_OFFSET) = (*(gpio_map + SunXI::PULLUPDN_OFFSET) & ~3) | SunXI::PUD_UP; else // pud == PUD_OFF - *(gpio_map + SunXI::PULLUPDN_OFFSET) &= ~3; + *(gpio_map + SunXI::PULLUPDN_OFFSET) = *(gpio_map + SunXI::PULLUPDN_OFFSET) & ~3; SunXI::short_wait(); *(gpio_map + clk_offset) = 1 << shift; SunXI::short_wait(); - *(gpio_map + SunXI::PULLUPDN_OFFSET) &= ~3; + *(gpio_map + SunXI::PULLUPDN_OFFSET) = *(gpio_map + SunXI::PULLUPDN_OFFSET) & ~3; *(gpio_map + clk_offset) = 0; } diff --git a/cpp/hal/gpiobus_bananam2p.h b/cpp/hal/gpiobus_bananam2p.h index 2da28f7a..a404763f 100644 --- a/cpp/hal/gpiobus_bananam2p.h +++ b/cpp/hal/gpiobus_bananam2p.h @@ -16,7 +16,6 @@ #include "hal/pi_defs/bpi-gpio.h" #include "hal/sbc_version.h" #include "hal/sunxi_utils.h" -#include "shared/log.h" #include "shared/scsi.h" #include diff --git a/cpp/hal/gpiobus_factory.cpp b/cpp/hal/gpiobus_factory.cpp index 3a4a6914..a6fa6901 100644 --- a/cpp/hal/gpiobus_factory.cpp +++ b/cpp/hal/gpiobus_factory.cpp @@ -15,7 +15,7 @@ #include "hal/gpiobus_raspberry.h" #include "hal/gpiobus_virtual.h" #include "hal/sbc_version.h" -#include "shared/log.h" +#include using namespace std; @@ -29,13 +29,13 @@ unique_ptr GPIOBUS_Factory::Create(BUS::mode_e mode) // Also make Init() private. SBC_Version::Init(); if (SBC_Version::IsBananaPi()) { - LOGTRACE("Creating GPIOBUS_BananaM2p") + spdlog::trace("Creating GPIOBUS_BananaM2p"); return_ptr = make_unique(); } else if (SBC_Version::IsRaspberryPi()) { - LOGTRACE("Creating GPIOBUS_Raspberry") + spdlog::trace("Creating GPIOBUS_Raspberry"); return_ptr = make_unique(); } else { - LOGINFO("Creating Virtual GPIOBUS") + spdlog::trace("Creating Virtual GPIOBUS"); return_ptr = make_unique(); } if (!return_ptr->Init(mode)) { @@ -43,7 +43,7 @@ unique_ptr GPIOBUS_Factory::Create(BUS::mode_e mode) } return_ptr->Reset(); } catch (const invalid_argument&) { - LOGERROR("Exception while trying to initialize GPIO bus. Are you running as root?") + spdlog::error("Exception while trying to initialize GPIO bus. Are you running as root?"); return_ptr = nullptr; } diff --git a/cpp/hal/gpiobus_raspberry.cpp b/cpp/hal/gpiobus_raspberry.cpp index fb3ba249..1fdd9ea3 100644 --- a/cpp/hal/gpiobus_raspberry.cpp +++ b/cpp/hal/gpiobus_raspberry.cpp @@ -18,10 +18,12 @@ #include "hal/gpiobus_raspberry.h" #include "hal/gpiobus.h" #include "hal/systimer.h" -#include "shared/log.h" +#include "hal/log.h" #include -#include +#include +#ifdef __linux__ #include +#endif #include #include #include @@ -80,14 +82,14 @@ bool GPIOBUS_Raspberry::Init(mode_e mode) // Open /dev/mem int fd = open("/dev/mem", O_RDWR | O_SYNC); if (fd == -1) { - LOGERROR("Error: Unable to open /dev/mem. Are you running as root?") + spdlog::error("Error: Unable to open /dev/mem. Are you running as root?"); return false; } // Map peripheral region memory void *map = mmap(NULL, 0x1000100, PROT_READ | PROT_WRITE, MAP_SHARED, fd, baseaddr); if (map == MAP_FAILED) { - LOGERROR("Error: Unable to map memory: %s", strerror(errno)) + spdlog::error("Error: Unable to map memory: "+ string(strerror(errno))); close(fd); return false; } diff --git a/cpp/hal/gpiobus_raspberry.h b/cpp/hal/gpiobus_raspberry.h index 3ebd0175..b0f7fd17 100644 --- a/cpp/hal/gpiobus_raspberry.h +++ b/cpp/hal/gpiobus_raspberry.h @@ -13,7 +13,6 @@ #include "hal/data_sample_raspberry.h" #include "hal/gpiobus.h" -#include "shared/log.h" #include "shared/scsi.h" #include diff --git a/cpp/hal/gpiobus_virtual.cpp b/cpp/hal/gpiobus_virtual.cpp index 5f396d18..1e15b338 100644 --- a/cpp/hal/gpiobus_virtual.cpp +++ b/cpp/hal/gpiobus_virtual.cpp @@ -13,12 +13,14 @@ #include "hal/gpiobus_virtual.h" #include "hal/gpiobus.h" #include "hal/systimer.h" -#include "shared/log.h" +#include "hal/log.h" #include #include #include #include +#ifdef __linux__ #include +#endif #include #include #include diff --git a/cpp/hal/gpiobus_virtual.h b/cpp/hal/gpiobus_virtual.h index dbfcd83a..0fba1cd0 100644 --- a/cpp/hal/gpiobus_virtual.h +++ b/cpp/hal/gpiobus_virtual.h @@ -13,7 +13,6 @@ #include "hal/data_sample_raspberry.h" #include "hal/gpiobus.h" -#include "shared/log.h" #include "shared/scsi.h" #include @@ -171,4 +170,4 @@ class GPIOBUS_Virtual final : public GPIOBUS sem_t *mutex_sem, *buffer_count_sem, *spool_signal_sem; int fd_shm, fd_log; #endif -}; \ No newline at end of file +}; diff --git a/cpp/shared/log.h b/cpp/hal/log.h similarity index 86% rename from cpp/shared/log.h rename to cpp/hal/log.h index 379a7732..31286504 100644 --- a/cpp/shared/log.h +++ b/cpp/hal/log.h @@ -9,9 +9,11 @@ // //--------------------------------------------------------------------------- +// The legacy code in this file is deprecated and can cause a buffer overflow. Use spdlog directly instead. + #pragma once -#include "spdlog/spdlog.h" +#include static const int LOGBUF_SIZE = 512; diff --git a/cpp/hal/sbc_version.cpp b/cpp/hal/sbc_version.cpp index 211d8c46..99971b41 100644 --- a/cpp/hal/sbc_version.cpp +++ b/cpp/hal/sbc_version.cpp @@ -10,7 +10,7 @@ //--------------------------------------------------------------------------- #include "sbc_version.h" -#include "shared/log.h" +#include "log.h" #include #include #include diff --git a/cpp/hal/sbc_version.h b/cpp/hal/sbc_version.h index d55bdf21..36922b5a 100644 --- a/cpp/hal/sbc_version.h +++ b/cpp/hal/sbc_version.h @@ -11,6 +11,7 @@ #pragma once +#include #include #include diff --git a/cpp/hal/sunxi_utils.cpp b/cpp/hal/sunxi_utils.cpp index 8c103d00..f4b150a1 100644 --- a/cpp/hal/sunxi_utils.cpp +++ b/cpp/hal/sunxi_utils.cpp @@ -40,12 +40,6 @@ using namespace std; -static const string BLACK = "\033[30m"; /* Black */ -static const string RED = "\033[31m"; /* Red */ -static const string GREEN = "\033[32m"; /* Green */ -static const string YELLOW = "\033[33m"; /* Yellow */ -static const string BLUE = "\033[34m"; /* Blue */ -static const string MAGENTA = "\033[35m"; /* Magenta */ static const string CYAN = "\033[36m"; /* Cyan */ static const string WHITE = "\033[37m"; /* White */ @@ -62,4 +56,4 @@ void dump_gpio_registers(const SunXI::sunxi_gpio_reg_t *regs) printf("--- GPIO INT CFG: %08X %08X %08X\n", regs->gpio_int.CFG[0], regs->gpio_int.CFG[1], regs->gpio_int.CFG[2]); printf("--- CTL: (%08X) STA: %08X DEB: %08X\n %s", regs->gpio_int.CTL, regs->gpio_int.STA, regs->gpio_int.DEB, WHITE.c_str()); -} \ No newline at end of file +} diff --git a/cpp/hal/systimer.cpp b/cpp/hal/systimer.cpp index 8ae8635f..21dc7b22 100644 --- a/cpp/hal/systimer.cpp +++ b/cpp/hal/systimer.cpp @@ -14,13 +14,12 @@ #include "hal/systimer.h" #include "hal/systimer_allwinner.h" #include "hal/systimer_raspberry.h" +#include #include #include "hal/gpiobus.h" #include "hal/sbc_version.h" -#include "shared/log.h" - bool SysTimer::initialized = false; bool SysTimer::is_allwinnner = false; bool SysTimer::is_raspberry = false; @@ -29,7 +28,7 @@ std::unique_ptr SysTimer::systimer_ptr; void SysTimer::Init() { - LOGTRACE("%s", __PRETTY_FUNCTION__) + spdlog::trace(__PRETTY_FUNCTION__); if (!initialized) { if (SBC_Version::IsRaspberryPi()) { diff --git a/cpp/hal/systimer_allwinner.cpp b/cpp/hal/systimer_allwinner.cpp index b698c405..077dba57 100644 --- a/cpp/hal/systimer_allwinner.cpp +++ b/cpp/hal/systimer_allwinner.cpp @@ -14,7 +14,7 @@ #include "hal/gpiobus.h" -#include "shared/log.h" +#include "hal/log.h" const std::string SysTimer_AllWinner::dev_mem_filename = "/dev/mem"; @@ -71,14 +71,14 @@ void SysTimer_AllWinner::enable_hs_timer() hsitimer_regs->hs_tmr_intv_hi_reg = (1 << 20) - 1; //(0xFFFFF) hsitimer_regs->hs_tmr_intv_lo_reg = UINT32_MAX; - // Select prescale value of 1, continuouse mode + // Select prescale value of 1, continuous mode hsitimer_regs->hs_tmr_ctrl_reg = HS_TMR_CLK_PRE_SCALE_1; // Set reload bit - hsitimer_regs->hs_tmr_ctrl_reg |= HS_TMR_RELOAD; + hsitimer_regs->hs_tmr_ctrl_reg = hsitimer_regs->hs_tmr_ctrl_reg | HS_TMR_RELOAD; // Enable HSTimer - hsitimer_regs->hs_tmr_ctrl_reg |= HS_TMR_EN; + hsitimer_regs->hs_tmr_ctrl_reg = hsitimer_regs->hs_tmr_ctrl_reg | HS_TMR_EN; } // TODO: According to the data sheet, we should turn off the HS timer when we're done with it. But, its just going to diff --git a/cpp/hal/systimer_raspberry.cpp b/cpp/hal/systimer_raspberry.cpp index 6ae647a4..249642cb 100644 --- a/cpp/hal/systimer_raspberry.cpp +++ b/cpp/hal/systimer_raspberry.cpp @@ -12,6 +12,7 @@ //--------------------------------------------------------------------------- #include "hal/systimer_raspberry.h" +#include #include #include #include @@ -19,8 +20,6 @@ #include "hal/gpiobus.h" #include "hal/sbc_version.h" -#include "shared/log.h" - // System timer address volatile uint32_t *SysTimer_Raspberry::systaddr = nullptr; // ARM timer address @@ -40,14 +39,14 @@ void SysTimer_Raspberry::Init() // Open /dev/mem int mem_fd = open("/dev/mem", O_RDWR | O_SYNC); if (mem_fd == -1) { - LOGERROR("Error: Unable to open /dev/mem. Are you running as root?") + spdlog::error("Error: Unable to open /dev/mem. Are you running as root?"); return; } // Map peripheral region memory void *map = mmap(nullptr, 0x1000100, PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, baseaddr); if (map == MAP_FAILED) { - LOGERROR("Error: Unable to map memory") + spdlog::error("Error: Unable to map memory"); close(mem_fd); return; } diff --git a/cpp/monitor/sm_core.cpp b/cpp/monitor/sm_core.cpp index dbb12fff..9805bab4 100644 --- a/cpp/monitor/sm_core.cpp +++ b/cpp/monitor/sm_core.cpp @@ -10,11 +10,10 @@ //--------------------------------------------------------------------------- #include "monitor/sm_core.h" -#include "hal/data_sample.h" #include "hal/gpiobus.h" #include "hal/gpiobus_factory.h" #include "monitor/sm_reports.h" -#include "shared/log.h" +#include "hal/log.h" #include "shared/piscsi_version.h" #include "shared/piscsi_util.h" #include @@ -77,28 +76,28 @@ void ScsiMon::ParseArguments(const vector &args) void ScsiMon::PrintHelpText(const vector &args) const { - LOGINFO("%s -i [input file json] -b [buffer size] [output file]", args[0]) - LOGINFO(" -i [input file json] - scsimon will parse the json file instead of capturing new data") - LOGINFO(" If -i option is not specified, scsimon will read the gpio pins") - LOGINFO(" -b [buffer size] - Override the default buffer size of %d.", buff_size) - LOGINFO(" [output file] - Base name of the output files. The file extension (ex: .json)") - LOGINFO(" will be appended to this file name") + spdlog::info(string(args[0]) + " -i [input file json] -b [buffer size] [output file]"); + spdlog::info(" -i [input file json] - scsimon will parse the json file instead of capturing new data"); + spdlog::info(" If -i option is not specified, scsimon will read the gpio pins"); + spdlog::info(" -b [buffer size] - Override the default buffer size of " + to_string(buff_size)); + spdlog::info(" [output file] - Base name of the output files. The file extension (ex: .json)"); + spdlog::info(" will be appended to this file name"); } void ScsiMon::Banner() const { if (import_data) { - LOGINFO("Reading input file: %s", input_file_name.c_str()) + spdlog::info("Reading input file: " + input_file_name); } else { - LOGINFO("Reading live data from the GPIO pins") - LOGINFO(" Connection type : %s", CONNECT_DESC.c_str()) + spdlog::info("Reading live data from the GPIO pins"); + spdlog::info(" Connection type: " + CONNECT_DESC); } - LOGINFO(" Data buffer size: %u", buff_size) - LOGINFO(" ") - LOGINFO("Generating output files:") - LOGINFO(" %s - Value Change Dump file that can be opened with GTKWave", vcd_file_name.c_str()) - LOGINFO(" %s - JSON file with raw data", json_file_name.c_str()) - LOGINFO(" %s - HTML file with summary of commands", html_file_name.c_str()) + spdlog::info(" Data buffer size: " + to_string(buff_size)); + spdlog::info(" "); + spdlog::info("Generating output files:"); + spdlog::info(" " + vcd_file_name + " - Value Change Dump file that can be opened with GTKWave"); + spdlog::info(" " + json_file_name + " - JSON file with raw data"); + spdlog::info(" " + html_file_name + " - HTML file with summary of commands"); } bool ScsiMon::Init() @@ -116,7 +115,7 @@ bool ScsiMon::Init() bus = GPIOBUS_Factory::Create(BUS::mode_e::TARGET); if (bus == nullptr) { - LOGERROR("Unable to intiailize the GPIO bus. Exiting....") + spdlog::error("Unable to intiailize the GPIO bus. Exiting...."); return false; } @@ -128,14 +127,14 @@ bool ScsiMon::Init() void ScsiMon::Cleanup() const { if (!import_data) { - LOGINFO("Stopping data collection....") + spdlog::info("Stopping data collection ..."); } - LOGINFO(" ") - LOGINFO("Generating %s...", vcd_file_name.c_str()) + spdlog::info(" "); + spdlog::info("Generating " + vcd_file_name + "..."); scsimon_generate_value_change_dump(vcd_file_name, data_buffer); - LOGINFO("Generating %s...", json_file_name.c_str()) + spdlog::info("Generating " + json_file_name + "..."); scsimon_generate_json(json_file_name, data_buffer); - LOGINFO("Generating %s...", html_file_name.c_str()) + spdlog::info("Generating " + html_file_name + "..."); scsimon_generate_html(html_file_name, data_buffer); bus->Cleanup(); @@ -179,15 +178,15 @@ int ScsiMon::run(const vector &args) if (import_data) { data_idx = scsimon_read_json(input_file_name, data_buffer); if (data_idx > 0) { - LOGDEBUG("Read %d samples from %s", data_idx, input_file_name.c_str()) + spdlog::debug("Read " + to_string(data_idx) + " samples from '" + input_file_name + "'"); Cleanup(); } exit(0); } - LOGINFO(" ") - LOGINFO("Now collecting data.... Press CTRL-C to stop.") - LOGINFO(" ") + spdlog::info(" "); + spdlog::info("Now collecting data.... Press CTRL-C to stop."); + spdlog::info(" "); // Initialize int ret = 0; @@ -219,12 +218,12 @@ int ScsiMon::run(const vector &args) while (running) { loop_count++; if (loop_count > LLONG_MAX - 1) { - LOGINFO("Maximum amount of time has elapsed. SCSIMON is terminating.") + spdlog::info("Maximum amount of time has elapsed. SCSIMON is terminating."); running = false; } if (data_idx >= (buff_size - 2)) { - LOGINFO("Internal data buffer is full. SCSIMON is terminating.") + spdlog::info("Internal data buffer is full. SCSIMON is terminating."); running = false; } @@ -249,15 +248,13 @@ int ScsiMon::run(const vector &args) timersub(&stop_time, &start_time, &time_diff); elapsed_us = ((time_diff.tv_sec * 1000000) + time_diff.tv_usec); - LOGINFO("%s", ("Elapsed time: " + to_string(elapsed_us) + " microseconds (" + to_string(elapsed_us / 1000000) + - " seconds") - .c_str()) - LOGINFO("%s", ("Collected " + to_string(data_idx) + " changes").c_str()) + spdlog::info("Elapsed time: " + to_string(elapsed_us) + " microseconds (" + to_string(elapsed_us / 1000000) + + " seconds"); + spdlog::info("Collected " + to_string(data_idx) + " changes"); ns_per_loop = (double)(elapsed_us * 1000) / (double)loop_count; - LOGINFO("%s", ("Read the SCSI bus " + to_string(loop_count) + " times with an average of " + - to_string(ns_per_loop) + " ns for each read") - .c_str()) + spdlog::info("Read the SCSI bus " + to_string(loop_count) + " times with an average of " + + to_string(ns_per_loop) + " ns for each read"); Cleanup(); diff --git a/cpp/monitor/sm_core.h b/cpp/monitor/sm_core.h index 1ee25675..25c18d1e 100644 --- a/cpp/monitor/sm_core.h +++ b/cpp/monitor/sm_core.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -13,6 +13,7 @@ #include "hal/data_sample.h" #include #include +#include using namespace std; @@ -37,7 +38,7 @@ class ScsiMon static void KillHandler(int); - static inline volatile bool running; + static inline atomic running; shared_ptr bus; diff --git a/cpp/monitor/sm_html_report.cpp b/cpp/monitor/sm_html_report.cpp index 7b7481f6..8b778ccd 100644 --- a/cpp/monitor/sm_html_report.cpp +++ b/cpp/monitor/sm_html_report.cpp @@ -9,7 +9,7 @@ // //--------------------------------------------------------------------------- -#include "shared/log.h" +#include "hal/log.h" #include "shared/piscsi_version.h" #include "sm_reports.h" #include @@ -173,7 +173,7 @@ static void print_html_data(ofstream& html_fp, const vector> &data_capture_array) { - LOGINFO("Creating HTML report file (%s)", filename.c_str()) + spdlog::info("Creating HTML report file (" + filename + ")"); ofstream html_ofstream; diff --git a/cpp/monitor/sm_json_report.cpp b/cpp/monitor/sm_json_report.cpp index de06215e..faa6f0e0 100644 --- a/cpp/monitor/sm_json_report.cpp +++ b/cpp/monitor/sm_json_report.cpp @@ -10,7 +10,7 @@ //--------------------------------------------------------------------------- #include "hal/data_sample_raspberry.h" -#include "shared/log.h" +#include "hal/log.h" #include "sm_reports.h" #include "string.h" #include @@ -52,7 +52,7 @@ uint32_t scsimon_read_json(const string &json_filename, vector> &data_capture_array) { - LOGTRACE("Creating JSON file (%s)", filename.c_str()) + spdlog::trace("Creating JSON file (" + filename + ")"); ofstream json_ofstream; json_ofstream.open(filename.c_str(), ios::out); diff --git a/cpp/monitor/sm_vcd_report.cpp b/cpp/monitor/sm_vcd_report.cpp index 5f8f1d68..be6f1035 100644 --- a/cpp/monitor/sm_vcd_report.cpp +++ b/cpp/monitor/sm_vcd_report.cpp @@ -11,7 +11,7 @@ #include "hal/data_sample.h" #include "hal/gpiobus.h" -#include "shared/log.h" +#include "hal/log.h" #include "sm_core.h" #include "sm_reports.h" #include @@ -77,7 +77,7 @@ static void vcd_output_if_changed_byte(ofstream &fp, uint8_t data, int pin, char void scsimon_generate_value_change_dump(const string &filename, const vector> &data_capture_array) { - LOGTRACE("Creating Value Change Dump file (%s)", filename.c_str()) + spdlog::trace("Creating Value Change Dump file (" + filename + ")"); ofstream vcd_ofstream; vcd_ofstream.open(filename.c_str(), ios::out); diff --git a/cpp/piscsi.cpp b/cpp/piscsi.cpp index ab8a674c..9b632aec 100644 --- a/cpp/piscsi.cpp +++ b/cpp/piscsi.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -13,7 +13,7 @@ using namespace std; int main(int argc, char *argv[]) { - const vector args(argv, argv + argc); + vector args(argv, argv + argc); return Piscsi().run(args); } diff --git a/cpp/piscsi/command_context.cpp b/cpp/piscsi/command_context.cpp index ce11e6ef..5b381d9f 100644 --- a/cpp/piscsi/command_context.cpp +++ b/cpp/piscsi/command_context.cpp @@ -3,24 +3,50 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "shared/log.h" -#include "generated/piscsi_interface.pb.h" +#include "shared/piscsi_exceptions.h" +#include "shared/protobuf_util.h" #include "command_context.h" +#include #include using namespace std; using namespace piscsi_interface; +using namespace protobuf_util; -void CommandContext::Cleanup() +bool CommandContext::ReadCommand() { - if (fd != -1) { - close(fd); - fd = -1; + // Read magic string + array magic; + if (const size_t bytes_read = ReadBytes(fd, magic); bytes_read) { + if (bytes_read != magic.size() || memcmp(magic.data(), "RASCSI", magic.size())) { + throw io_exception("Invalid magic"); + } + + // Fetch the command + DeserializeMessage(fd, command); + + return true; } + + return false; +} + +void CommandContext::WriteResult(const PbResult& result) const +{ + // The descriptor is -1 when devices are not attached via the remote interface but by the piscsi tool + if (fd != -1) { + SerializeMessage(fd, result); + } +} + +void CommandContext::WriteSuccessResult(PbResult& result) const +{ + result.set_status(true); + WriteResult(result); } bool CommandContext::ReturnLocalizedError(LocalizationKey key, const string& arg1, const string& arg2, @@ -33,7 +59,7 @@ bool CommandContext::ReturnLocalizedError(LocalizationKey key, PbErrorCode error const string& arg2, const string& arg3) const { // For the logfile always use English - LOGERROR("%s", localizer.Localize(key, "en", arg1, arg2, arg3).c_str()) + spdlog::error(localizer.Localize(key, "en", arg1, arg2, arg3)); return ReturnStatus(false, localizer.Localize(key, locale, arg1, arg2, arg3), error_code, false); } @@ -42,17 +68,12 @@ bool CommandContext::ReturnStatus(bool status, const string& msg, PbErrorCode er { // Do not log twice if logging has already been done in the localized error handling above if (log && !status && !msg.empty()) { - LOGERROR("%s", msg.c_str()) + spdlog::error(msg); } if (fd == -1) { if (!msg.empty()) { - if (status) { - cerr << "Error: " << msg << endl; - } - else { - cout << msg << endl; - } + cerr << "Error: " << msg << endl; } } else { @@ -60,8 +81,18 @@ bool CommandContext::ReturnStatus(bool status, const string& msg, PbErrorCode er result.set_status(status); result.set_error_code(error_code); result.set_msg(msg); - serializer.SerializeMessage(fd, result); + WriteResult(result); } return status; } + +bool CommandContext::ReturnSuccessStatus() const +{ + return ReturnStatus(true, "", PbErrorCode::NO_ERROR_CODE, true); +} + +bool CommandContext::ReturnErrorStatus(const string& msg) const +{ + return ReturnStatus(false, msg, PbErrorCode::NO_ERROR_CODE, true); +} diff --git a/cpp/piscsi/command_context.h b/cpp/piscsi/command_context.h index 3fcc4e20..dcbb2d20 100644 --- a/cpp/piscsi/command_context.h +++ b/cpp/piscsi/command_context.h @@ -3,15 +3,14 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once -#include "generated/piscsi_interface.pb.h" #include "localizer.h" -#include "shared/protobuf_serializer.h" +#include "generated/piscsi_interface.pb.h" #include using namespace std; @@ -19,27 +18,36 @@ using namespace piscsi_interface; class CommandContext { - const ProtobufSerializer serializer; - - const Localizer localizer; - - string locale; - - int fd; public: - CommandContext(const std::string& s = "", int f = -1) : locale(s), fd(f) {} + CommandContext(const PbCommand& cmd, string_view f, string_view l) : command(cmd), default_folder(f), locale(l) {} + explicit CommandContext(int f) : fd(f) {} ~CommandContext() = default; - void Cleanup(); - - const ProtobufSerializer& GetSerializer() const { return serializer; } - int GetFd() const { return fd; } - void SetFd(int f) { fd = f; } - bool IsValid() const { return fd != -1; } + string GetDefaultFolder() const { return default_folder; } + void SetDefaultFolder(string_view f) { default_folder = f; } + bool ReadCommand(); + void WriteResult(const PbResult&) const; + void WriteSuccessResult(PbResult&) const; + const PbCommand& GetCommand() const { return command; } bool ReturnLocalizedError(LocalizationKey, const string& = "", const string& = "", const string& = "") const; bool ReturnLocalizedError(LocalizationKey, PbErrorCode, const string& = "", const string& = "", const string& = "") const; - bool ReturnStatus(bool = true, const string& = "", PbErrorCode = PbErrorCode::NO_ERROR_CODE, bool = true) const; + bool ReturnSuccessStatus() const; + bool ReturnErrorStatus(const string&) const; + +private: + + bool ReturnStatus(bool, const string&, PbErrorCode, bool) const; + + const Localizer localizer; + + PbCommand command; + + string default_folder; + + string locale; + + int fd = -1; }; diff --git a/cpp/piscsi/localizer.cpp b/cpp/piscsi/localizer.cpp index bcb17297..17c92034 100644 --- a/cpp/piscsi/localizer.cpp +++ b/cpp/piscsi/localizer.cpp @@ -3,15 +3,13 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "localizer.h" #include -#include #include -#include using namespace std; @@ -31,12 +29,12 @@ Localizer::Localizer() Add(LocalizationKey::ERROR_OPERATION, "es", "Operación desconocida"); Add(LocalizationKey::ERROR_OPERATION, "zh", "未知操作"); - Add(LocalizationKey::ERROR_LOG_LEVEL, "en", "Invalid log level %1"); - Add(LocalizationKey::ERROR_LOG_LEVEL, "de", "Ungültiger Log-Level %1"); - Add(LocalizationKey::ERROR_LOG_LEVEL, "sv", "Ogiltig loggnivå %1"); - Add(LocalizationKey::ERROR_LOG_LEVEL, "fr", "Niveau de journalisation invalide %1"); - Add(LocalizationKey::ERROR_LOG_LEVEL, "es", "Nivel de registro %1 no válido"); - Add(LocalizationKey::ERROR_LOG_LEVEL, "zh", "无效的日志级别 %1"); + Add(LocalizationKey::ERROR_LOG_LEVEL, "en", "Invalid log level '%1'"); + Add(LocalizationKey::ERROR_LOG_LEVEL, "de", "Ungültiger Log-Level '%1'"); + Add(LocalizationKey::ERROR_LOG_LEVEL, "sv", "Ogiltig loggnivå '%1'"); + Add(LocalizationKey::ERROR_LOG_LEVEL, "fr", "Niveau de journalisation invalide '%1'"); + Add(LocalizationKey::ERROR_LOG_LEVEL, "es", "Nivel de registro '%1' no válido"); + Add(LocalizationKey::ERROR_LOG_LEVEL, "zh", "无效的日志级别 '%1'"); Add(LocalizationKey::ERROR_MISSING_DEVICE_ID, "en", "Missing device ID"); Add(LocalizationKey::ERROR_MISSING_DEVICE_ID, "de", "Fehlende Geräte-ID"); @@ -51,26 +49,26 @@ Localizer::Localizer() Add(LocalizationKey::ERROR_MISSING_FILENAME, "fr", "Nom de fichier manquant"); Add(LocalizationKey::ERROR_MISSING_FILENAME, "es", "Falta el nombre del archivo"); Add(LocalizationKey::ERROR_MISSING_FILENAME, "zh", "缺少文件名"); - + Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "en", "Device type %1 requires a filename"); Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "de", "Gerätetyp %1 erfordert einen Dateinamen"); Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "sv", "Enhetstypen %1 kräver ett filnamn"); Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "es", "El tipo de dispositivo %1 requiere un nombre de archivo"); Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "zh", "设备类型 %1 需要一个文件名"); - - Add(LocalizationKey::ERROR_IMAGE_IN_USE, "en", "Image file '%1' is already being used by ID %2, unit %3"); - Add(LocalizationKey::ERROR_IMAGE_IN_USE, "de", "Image-Datei '%1' wird bereits von ID %2, Einheit %3 benutzt"); - Add(LocalizationKey::ERROR_IMAGE_IN_USE, "sv", "Skivbildsfilen '%1' används redan av id %2, enhetsnummer %3"); - Add(LocalizationKey::ERROR_IMAGE_IN_USE, "fr", "Le fichier d'image '%1' est déjà utilisé par l'ID %2, unité %3"); - Add(LocalizationKey::ERROR_IMAGE_IN_USE, "es", "El archivo de imagen '%1' ya está siendo utilizado por el ID %2, unidad %3"); - Add(LocalizationKey::ERROR_IMAGE_IN_USE, "zh", "图像文件%1已被 ID %2、单元 %3 使用"); - + + Add(LocalizationKey::ERROR_IMAGE_IN_USE, "en", "Image file '%1' is already being used by device %2"); + Add(LocalizationKey::ERROR_IMAGE_IN_USE, "de", "Image-Datei '%1' wird bereits von Gerät %2 benutzt"); + Add(LocalizationKey::ERROR_IMAGE_IN_USE, "sv", "Skivbildsfilen '%1' används redan av nhet %2"); + Add(LocalizationKey::ERROR_IMAGE_IN_USE, "fr", "Le fichier d'image '%1' est déjà utilisé par périphérique %2"); + Add(LocalizationKey::ERROR_IMAGE_IN_USE, "es", "El archivo de imagen '%1' ya está siendo utilizado por dispositivo %2"); + Add(LocalizationKey::ERROR_IMAGE_IN_USE, "zh", "图像文件%1已被 ID %2"); + Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "en", "Can't create image file info for '%1'"); Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "de", "Image-Datei-Information für '%1' kann nicht erzeugt werden"); Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "sv", "Kunde ej skapa skivbildsfilsinfo för '%1'"); Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "es", "No se puede crear información de archivo de imagen para '%1'"); Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "zh", "无法为'%1'创建图像文件信息"); - + Add(LocalizationKey::ERROR_RESERVED_ID, "en", "Device ID %1 is reserved"); Add(LocalizationKey::ERROR_RESERVED_ID, "de", "Geräte-ID %1 ist reserviert"); Add(LocalizationKey::ERROR_RESERVED_ID, "sv", "Enhets-id %1 är reserverat"); @@ -84,21 +82,21 @@ Localizer::Localizer() Add(LocalizationKey::ERROR_NON_EXISTING_DEVICE, "fr", "Commande pour ID %1 non-existant"); Add(LocalizationKey::ERROR_NON_EXISTING_DEVICE, "es", "Comando para ID %1 no existente"); Add(LocalizationKey::ERROR_NON_EXISTING_DEVICE, "zh", "不存在的 ID %1 的指令"); - + Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "en", "Command for non-existing ID %1, unit %2"); Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "de", "Kommando für nicht existente ID %1, Einheit %2"); Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "sv", "Kommando för id %1, enhetsnummer %2 som ej existerar"); Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "fr", "Command pour ID %1, unité %2 non-existant"); Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "es", "Comando para ID %1 inexistente, unidad %2"); Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "zh", "不存在的 ID %1, 单元 %2 的指令"); - + Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "en", "Unknown device type %1"); Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "de", "Unbekannter Gerätetyp %1"); Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "sv", "Obekant enhetstyp: %1"); Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "fr", "Type de périphérique inconnu %1"); Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "es", "Tipo de dispositivo desconocido %1"); Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "zh", "未知设备类型 %1"); - + Add(LocalizationKey::ERROR_MISSING_DEVICE_TYPE, "en", "Device type required for unknown extension of file '%1'"); Add(LocalizationKey::ERROR_MISSING_DEVICE_TYPE, "de", "Gerätetyp erforderlich für unbekannte Extension der Datei '%1'"); Add(LocalizationKey::ERROR_MISSING_DEVICE_TYPE, "sv", "Man måste ange enhetstyp för obekant filändelse '%1'"); @@ -199,11 +197,11 @@ Localizer::Localizer() Add(LocalizationKey::ERROR_LUN0, "es", "El LUN 0 no se puede desconectar mientras haya otro LUN"); Add(LocalizationKey::ERROR_LUN0, "zh", "LUN 0 无法卸载,因为当前仍有另一个 LUN。"); - Add(LocalizationKey::ERROR_INITIALIZATION, "en", "Initialization of %1 device, ID %2, LUN %3 failed"); - Add(LocalizationKey::ERROR_INITIALIZATION, "de", "Initialisierung von %1-Gerät, ID %2, LUN %3 fehlgeschlagen"); - Add(LocalizationKey::ERROR_INITIALIZATION, "sv", "Kunde ej initialisera enheten %1 med id %2 och enhetsnummer %3"); - Add(LocalizationKey::ERROR_INITIALIZATION, "es", "La inicialización del dispositivo %1, ID %2, LUN %3 falló"); - Add(LocalizationKey::ERROR_INITIALIZATION, "zh", "%1 设备、ID %2、LUN %3 的初始化失败"); + Add(LocalizationKey::ERROR_INITIALIZATION, "en", "Initialization of %1 failed"); + Add(LocalizationKey::ERROR_INITIALIZATION, "de", "Initialisierung von %1 fehlgeschlagen"); + Add(LocalizationKey::ERROR_INITIALIZATION, "sv", "Kunde ej initialisera %1 "); + Add(LocalizationKey::ERROR_INITIALIZATION, "es", "La inicialización del %1 falló"); + Add(LocalizationKey::ERROR_INITIALIZATION, "zh", "%1 的初始化失败"); Add(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, "en", "%1 operation denied, %2 isn't stoppable"); Add(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, "de", "%1-Operation verweigert, %2 ist nicht stopbar"); @@ -235,17 +233,16 @@ void Localizer::Add(LocalizationKey key, const string& locale, string_view value // Safeguards against empty messages, duplicate entries and unsupported locales assert(locale.size()); assert(value.size()); - assert(supported_languages.find(locale) != supported_languages.end()); + assert(supported_languages.contains(locale)); assert(localized_messages[locale][key].empty()); - localized_messages[locale][key] = value; } string Localizer::Localize(LocalizationKey key, const string& locale, const string& arg1, const string& arg2, const string &arg3) const { - string locale_lower = locale; - transform(locale_lower.begin(), locale_lower.end(), locale_lower.begin(), ::tolower); + string locale_lower; + ranges::transform(locale, back_inserter(locale_lower), ::tolower); auto it = localized_messages.find(locale_lower); if (it == localized_messages.end()) { @@ -268,9 +265,9 @@ string Localizer::Localize(LocalizationKey key, const string& locale, const stri } string message = m->second; - message = regex_replace(message, regex("%1"), arg1); - message = regex_replace(message, regex("%2"), arg2); - message = regex_replace(message, regex("%3"), arg3); + message = regex_replace(message, regex1, arg1); + message = regex_replace(message, regex2, arg2); + message = regex_replace(message, regex3, arg3); return message; } diff --git a/cpp/piscsi/localizer.h b/cpp/piscsi/localizer.h index c39f4fdb..e1153ec8 100644 --- a/cpp/piscsi/localizer.h +++ b/cpp/piscsi/localizer.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // // Message localization support. Currently only for messages with up to 3 string parameters. // @@ -11,9 +11,11 @@ #pragma once +#include "shared/piscsi_util.h" #include #include #include +#include using namespace std; @@ -64,8 +66,12 @@ public: private: void Add(LocalizationKey, const string&, string_view); - unordered_map> localized_messages; + unordered_map, piscsi_util::StringHash, equal_to<>> localized_messages; // Supported locales, always lower case - unordered_set supported_languages = { "en", "de", "sv", "fr", "es", "zh" }; + unordered_set> supported_languages = { "en", "de", "sv", "fr", "es", "zh" }; + + const regex regex1 = regex("%1"); + const regex regex2 = regex("%2"); + const regex regex3 = regex("%3"); }; diff --git a/cpp/piscsi/piscsi_core.cpp b/cpp/piscsi/piscsi_core.cpp index c5391ce0..e2bf056e 100644 --- a/cpp/piscsi/piscsi_core.cpp +++ b/cpp/piscsi/piscsi_core.cpp @@ -1,57 +1,52 @@ //--------------------------------------------------------------------------- // -// SCSI Target Emulator PiSCSI -// for Raspberry Pi +// SCSI Target Emulator PiSCSI +// for Raspberry Pi // -// Powered by XM6 TypeG Technology. -// Copyright (C) 2016-2020 GIMONS -// Copyright (C) 2020-2023 Contributors to the PiSCSI project +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2020 GIMONS +// Copyright (C) 2020-2023 Contributors to the PiSCSI project +// Copyright (C) 2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "shared/config.h" -#include "shared/log.h" #include "shared/piscsi_util.h" -#include "shared/protobuf_serializer.h" #include "shared/protobuf_util.h" #include "shared/piscsi_exceptions.h" #include "shared/piscsi_version.h" -#include "controllers/controller_manager.h" #include "controllers/scsi_controller.h" +#include "devices/device_logger.h" #include "devices/device_factory.h" #include "devices/storage_device.h" #include "hal/gpiobus_factory.h" #include "hal/gpiobus.h" #include "hal/systimer.h" -#include "piscsi/piscsi_executor.h" #include "piscsi/piscsi_core.h" -#include "spdlog/sinks/stdout_color_sinks.h" +#include #include #include -#include #include #include #include -#include -#include +#include using namespace std; using namespace filesystem; -using namespace spdlog; using namespace piscsi_interface; using namespace piscsi_util; using namespace protobuf_util; using namespace scsi_defs; -void Piscsi::Banner(const vector& args) const +void Piscsi::Banner(span args) const { cout << piscsi_util::Banner("(Backend Service)"); cout << "Connection type: " << CONNECT_DESC << '\n' << flush; if ((args.size() > 1 && strcmp(args[1], "-h") == 0) || (args.size() > 1 && strcmp(args[1], "--help") == 0)){ cout << "\nUsage: " << args[0] << " [-idID[:LUN] FILE] ...\n\n"; - cout << " ID is SCSI device ID (0-7).\n"; - cout << " LUN is the optional logical unit (0-31).\n"; + cout << " ID is SCSI device ID (0-" << (ControllerManager::GetScsiIdMax() - 1) << ").\n"; + cout << " LUN is the optional logical unit (0-" << (ControllerManager::GetScsiLunMax() - 1) <<").\n"; cout << " FILE is a disk image file, \"daynaport\", \"bridge\", \"printer\" or \"services\".\n\n"; cout << " Image type is detected based on file extension if no explicit type is specified.\n"; cout << " hd1 : SCSI-1 HD image (Non-removable generic SCSI-1 HD image)\n"; @@ -69,59 +64,57 @@ void Piscsi::Banner(const vector& args) const } } -bool Piscsi::InitBus() const +bool Piscsi::InitBus() { bus = GPIOBUS_Factory::Create(BUS::mode_e::TARGET); if (bus == nullptr) { return false; } - auto b = bus; - controller_manager = make_shared(*b); - auto c = controller_manager; - executor = make_shared(piscsi_image, *c); + executor = make_unique(piscsi_image, *bus, controller_manager); return true; } -void Piscsi::Cleanup() +void Piscsi::CleanUp() { - executor->DetachAll(); + if (service.IsRunning()) { + service.Stop(); + } - service.Cleanup(); + executor->DetachAll(); bus->Cleanup(); } -void Piscsi::ReadAccessToken(const string& filename) const +void Piscsi::ReadAccessToken(const path& filename) { - struct stat st; - if (stat(filename.c_str(), &st) || !S_ISREG(st.st_mode)) { - throw parser_exception("Can't access token file '" + filename + "'"); + if (error_code error; !is_regular_file(filename, error)) { + throw parser_exception("Access token file '" + filename.string() + "' must be a regular file"); } - if (st.st_uid || st.st_gid) { - throw parser_exception("Access token file '" + filename + "' must be owned by root"); + if (struct stat st; stat(filename.c_str(), &st) || st.st_uid || st.st_gid) { + throw parser_exception("Access token file '" + filename.string() + "' must be owned by root"); } if (const auto perms = filesystem::status(filename).permissions(); (perms & perms::group_read) != perms::none || (perms & perms::others_read) != perms::none || (perms & perms::group_write) != perms::none || (perms & perms::others_write) != perms::none) { - throw parser_exception("Access token file '" + filename + "' must be readable by root only"); + throw parser_exception("Access token file '" + filename.string() + "' must be readable by root only"); } ifstream token_file(filename); if (token_file.fail()) { - throw parser_exception("Can't open access token file '" + filename + "'"); + throw parser_exception("Can't open access token file '" + filename.string() + "'"); } getline(token_file, access_token); if (token_file.fail()) { - throw parser_exception("Can't read access token file '" + filename + "'"); + throw parser_exception("Can't read access token file '" + filename.string() + "'"); } if (access_token.empty()) { - throw parser_exception("Access token file '" + filename + "' must not be empty"); + throw parser_exception("Access token file '" + filename.string() + "' must not be empty"); } } @@ -131,59 +124,74 @@ void Piscsi::LogDevices(string_view devices) const string line; while (getline(ss, line, '\n')) { - LOGINFO("%s", line.c_str()) + spdlog::info(line); } } -PbDeviceType Piscsi::ParseDeviceType(const string& value) const -{ - string t = value; - PbDeviceType type; - transform(t.begin(), t.end(), t.begin(), ::toupper); - if (!PbDeviceType_Parse(t, &type)) { - throw parser_exception("Illegal device type '" + value + "'"); - } - - return type; -} - void Piscsi::TerminationHandler(int) { - Cleanup(); + instance->CleanUp(); // Process will terminate automatically } -Piscsi::optargs_type Piscsi::ParseArguments(const vector& args, int& port) const +string Piscsi::ParseArguments(span args, PbCommand& command, int& port, string& reserved_ids) { - optargs_type optargs; + string log_level = "info"; + PbDeviceType type = UNDEFINED; + int block_size = 0; string name; + string id_and_lun; + + string locale = GetLocale(); + + // Avoid duplicate messages while parsing + set_level(level::off); opterr = 1; int opt; while ((opt = getopt(static_cast(args.size()), args.data(), "-Iib:d:n:p:r:t:z:D:F:L:P:R:C:v")) != -1) { switch (opt) { - // The following options can not be processed until AFTER - // the 'bus' object is created and configured + // The two options below are kind of a compound option with two letters case 'i': case 'I': - case 'b': + continue; + case 'd': case 'D': - case 'R': - case 'n': - case 'r': - case 't': - case 'F': - case 'z': - { - const string optarg_str = optarg == nullptr ? "" : optarg; - optargs.emplace_back(opt, optarg_str); + id_and_lun = optarg; + continue; + + case 'b': + if (!GetAsUnsignedInt(optarg, block_size)) { + throw parser_exception("Invalid block size " + string(optarg)); + } + continue; + + case 'z': + locale = optarg; + continue; + + case 'F': + if (const string error = piscsi_image.SetDefaultFolder(optarg); !error.empty()) { + throw parser_exception(error); + } continue; - } case 'L': - current_log_level = optarg; + log_level = optarg; + continue; + + case 'R': + int depth; + if (!GetAsUnsignedInt(optarg, depth)) { + throw parser_exception("Invalid image file scan depth " + string(optarg)); + } + piscsi_image.SetDepth(depth); + continue; + + case 'n': + name = optarg; continue; case 'p': @@ -196,92 +204,12 @@ Piscsi::optargs_type Piscsi::ParseArguments(const vector& args, int& por ReadAccessToken(optarg); continue; - case 'v': - cout << piscsi_get_version_string() << endl; - exit(0); - - case 1: - { - // Encountered filename - const string optarg_str = (optarg == nullptr) ? "" : string(optarg); - optargs.emplace_back(opt, optarg_str); - continue; - } - - default: - throw parser_exception("Parser error"); - } - - if (optopt) { - throw parser_exception("Parser error"); - } - } - - return optargs; -} - -void Piscsi::CreateInitialDevices(const optargs_type& optargs) const -{ - PbCommand command; - PbDeviceType type = UNDEFINED; - int block_size = 0; - string name; - string log_level; - string id_and_lun; - - const char *locale = setlocale(LC_MESSAGES, ""); - if (locale == nullptr || !strcmp(locale, "C")) { - locale = "en"; - } - - opterr = 1; - for (const auto& [option, value] : optargs) { - switch (option) { - case 'i': - case 'I': - continue; - - case 'd': - case 'D': - id_and_lun = value; - continue; - - case 'b': - if (!GetAsUnsignedInt(value, block_size)) { - throw parser_exception("Invalid block size " + value); - } - continue; - - case 'z': - locale = value.c_str(); - continue; - - case 'F': - if (const string error = piscsi_image.SetDefaultFolder(value); !error.empty()) { - throw parser_exception(error); - } - continue; - - case 'R': - int depth; - if (!GetAsUnsignedInt(value, depth)) { - throw parser_exception("Invalid image file scan depth " + value); - } - piscsi_image.SetDepth(depth); - continue; - - case 'n': - name = value; - continue; - case 'r': - if (const string error = executor->SetReservedIds(value); !error.empty()) { - throw parser_exception(error); - } + reserved_ids = optarg; continue; case 't': - type = ParseDeviceType(value); + type = ParseDeviceType(optarg); continue; case 1: @@ -292,10 +220,16 @@ void Piscsi::CreateInitialDevices(const optargs_type& optargs) const throw parser_exception("Parser error"); } - PbDeviceDefinition *device = command.add_devices(); + if (optopt) { + throw parser_exception("Parser error"); + } + + // Set up the device data + + auto device = command.add_devices(); if (!id_and_lun.empty()) { - if (const string error = SetIdAndLun(*device, id_and_lun, ScsiController::LUN_MAX); !error.empty()) { + if (const string error = SetIdAndLun(*device, id_and_lun); !error.empty()) { throw parser_exception(error); } } @@ -303,7 +237,7 @@ void Piscsi::CreateInitialDevices(const optargs_type& optargs) const device->set_type(type); device->set_block_size(block_size); - ParseParameters(*device, value); + ParseParameters(*device, optarg); SetProductData(*device, name); @@ -313,182 +247,231 @@ void Piscsi::CreateInitialDevices(const optargs_type& optargs) const id_and_lun = ""; } - // Attach all specified devices - command.set_operation(ATTACH); - - if (CommandContext context(locale); !executor->ProcessCmd(context, command)) { - throw parser_exception("Can't execute " + PbOperation_Name(command.operation())); + if (!SetLogLevel(log_level)) { + throw parser_exception("Invalid log level '" + log_level + "'"); } - // Display and log the device list - PbServerInfo server_info; - piscsi_response.GetDevices(controller_manager->GetAllDevices(), server_info, piscsi_image.GetDefaultFolder()); - const list& devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() }; - const string device_list = ListDevices(devices); - LogDevices(device_list); - cout << device_list << flush; + return locale; } -bool Piscsi::ExecuteCommand(const CommandContext& context, const PbCommand& command) +PbDeviceType Piscsi::ParseDeviceType(const string& value) { + string t; + ranges::transform(value, back_inserter(t), ::toupper); + if (PbDeviceType type; PbDeviceType_Parse(t, &type)) { + return type; + } + + throw parser_exception("Illegal device type '" + value + "'"); +} + +bool Piscsi::SetLogLevel(const string& log_level) const +{ + int id = -1; + int lun = -1; + string level = log_level; + + if (const auto& components = Split(log_level, COMPONENT_SEPARATOR, 2); !components.empty()) { + level = components[0]; + + if (components.size() > 1) { + if (const string error = ProcessId(components[1], id, lun); !error.empty()) { + spdlog::warn("Error setting log level: " + error); + return false; + } + } + } + + const level::level_enum l = level::from_str(level); + // Compensate for spdlog using 'off' for unknown levels + if (to_string_view(l) != level) { + spdlog::warn("Invalid log level '" + level + "'"); + return false; + } + + set_level(l); + DeviceLogger::SetLogIdAndLun(id, lun); + + if (id != -1) { + if (lun == -1) { + spdlog::info("Set log level for device " + to_string(id) + " to '" + level + "'"); + } + else { + spdlog::info("Set log level for device " + to_string(id) + ":" + to_string(lun) + " to '" + level + "'"); + } + } + else { + spdlog::info("Set log level to '" + level + "'"); + } + + return true; +} + +bool Piscsi::ExecuteCommand(CommandContext& context) +{ + context.SetDefaultFolder(piscsi_image.GetDefaultFolder()); + + const PbCommand& command = context.GetCommand(); + if (!access_token.empty() && access_token != GetParam(command, "token")) { return context.ReturnLocalizedError(LocalizationKey::ERROR_AUTHENTICATION, UNAUTHORIZED); } if (!PbOperation_IsValid(command.operation())) { - LOGERROR("Received unknown command with operation opcode %d", command.operation()) + spdlog::error("Received unknown command with operation opcode " + to_string(command.operation())); return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION, UNKNOWN_OPERATION); } - LOGTRACE("Received %s command", PbOperation_Name(command.operation()).c_str()) + spdlog::trace("Received " + PbOperation_Name(command.operation()) + " command"); PbResult result; - ProtobufSerializer serializer; switch(command.operation()) { - case LOG_LEVEL: { - const string log_level = GetParam(command, "level"); - if (const bool status = executor->SetLogLevel(log_level); !status) { + case LOG_LEVEL: + if (const string log_level = GetParam(command, "level"); !SetLogLevel(log_level)) { context.ReturnLocalizedError(LocalizationKey::ERROR_LOG_LEVEL, log_level); } else { - current_log_level = log_level; - - context.ReturnStatus(); + context.ReturnSuccessStatus(); } break; - } - case DEFAULT_FOLDER: { - if (const string status = piscsi_image.SetDefaultFolder(GetParam(command, "folder")); !status.empty()) { - context.ReturnStatus(false, status); + case DEFAULT_FOLDER: + if (const string error = piscsi_image.SetDefaultFolder(GetParam(command, "folder")); !error.empty()) { + context.ReturnErrorStatus(error); } else { - context.ReturnStatus(); + context.ReturnSuccessStatus(); } break; - } - case DEVICES_INFO: { - piscsi_response.GetDevicesInfo(controller_manager->GetAllDevices(), result, command, - piscsi_image.GetDefaultFolder()); - serializer.SerializeMessage(context.GetFd(), result); + case DEVICES_INFO: + response.GetDevicesInfo(controller_manager.GetAllDevices(), result, command, piscsi_image.GetDefaultFolder()); + context.WriteResult(result); break; - } - case DEVICE_TYPES_INFO: { - result.set_allocated_device_types_info(piscsi_response.GetDeviceTypesInfo(result).release()); - serializer.SerializeMessage(context.GetFd(), result); + case DEVICE_TYPES_INFO: + response.GetDeviceTypesInfo(*result.mutable_device_types_info()); + context.WriteSuccessResult(result); break; - } - case SERVER_INFO: { - result.set_allocated_server_info(piscsi_response.GetServerInfo(controller_manager->GetAllDevices(), - result, executor->GetReservedIds(), current_log_level, piscsi_image.GetDefaultFolder(), - GetParam(command, "folder_pattern"), GetParam(command, "file_pattern"), - piscsi_image.GetDepth()).release()); - serializer.SerializeMessage(context.GetFd(), result); + case SERVER_INFO: + response.GetServerInfo(*result.mutable_server_info(), controller_manager.GetAllDevices(), + executor->GetReservedIds(), piscsi_image.GetDefaultFolder(), + GetParam(command, "folder_pattern"), GetParam(command, "file_pattern"), piscsi_image.GetDepth()); + context.WriteSuccessResult(result); break; - } - case VERSION_INFO: { - result.set_allocated_version_info(piscsi_response.GetVersionInfo(result).release()); - serializer.SerializeMessage(context.GetFd(), result); + case VERSION_INFO: + response.GetVersionInfo(*result.mutable_version_info()); + context.WriteSuccessResult(result); break; - } - case LOG_LEVEL_INFO: { - result.set_allocated_log_level_info(piscsi_response.GetLogLevelInfo(result, current_log_level).release()); - serializer.SerializeMessage(context.GetFd(), result); + case LOG_LEVEL_INFO: + response.GetLogLevelInfo(*result.mutable_log_level_info()); + context.WriteSuccessResult(result); break; - } - case DEFAULT_IMAGE_FILES_INFO: { - result.set_allocated_image_files_info(piscsi_response.GetAvailableImages(result, - piscsi_image.GetDefaultFolder(), GetParam(command, "folder_pattern"), - GetParam(command, "file_pattern"), piscsi_image.GetDepth()).release()); - serializer.SerializeMessage(context.GetFd(), result); + case DEFAULT_IMAGE_FILES_INFO: + response.GetImageFilesInfo(*result.mutable_image_files_info(), piscsi_image.GetDefaultFolder(), + GetParam(command, "folder_pattern"), GetParam(command, "file_pattern"), piscsi_image.GetDepth()); + context.WriteSuccessResult(result); break; - } - case IMAGE_FILE_INFO: { + case IMAGE_FILE_INFO: if (string filename = GetParam(command, "file"); filename.empty()) { context.ReturnLocalizedError( LocalizationKey::ERROR_MISSING_FILENAME); } else { auto image_file = make_unique(); - const bool status = piscsi_response.GetImageFile(*image_file.get(), piscsi_image.GetDefaultFolder(), filename); + const bool status = response.GetImageFile(*image_file.get(), piscsi_image.GetDefaultFolder(), + filename); if (status) { - result.set_status(true); result.set_allocated_image_file_info(image_file.get()); - serializer.SerializeMessage(context.GetFd(), result); + result.set_status(true); + context.WriteResult(result); } else { context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_FILE_INFO); } } break; - } - case NETWORK_INTERFACES_INFO: { - result.set_allocated_network_interfaces_info(piscsi_response.GetNetworkInterfacesInfo(result).release()); - serializer.SerializeMessage(context.GetFd(), result); + case NETWORK_INTERFACES_INFO: + response.GetNetworkInterfacesInfo(*result.mutable_network_interfaces_info()); + context.WriteSuccessResult(result); break; - } - case MAPPING_INFO: { - result.set_allocated_mapping_info(piscsi_response.GetMappingInfo(result).release()); - serializer.SerializeMessage(context.GetFd(), result); + case MAPPING_INFO: + response.GetMappingInfo(*result.mutable_mapping_info()); + context.WriteSuccessResult(result); break; - } - case OPERATION_INFO: { - result.set_allocated_operation_info(piscsi_response.GetOperationInfo(result, - piscsi_image.GetDepth()).release()); - serializer.SerializeMessage(context.GetFd(), result); + case OPERATION_INFO: + response.GetOperationInfo(*result.mutable_operation_info(), piscsi_image.GetDepth()); + context.WriteSuccessResult(result); break; - } - case RESERVED_IDS_INFO: { - result.set_allocated_reserved_ids_info(piscsi_response.GetReservedIds(result, - executor->GetReservedIds()).release()); - serializer.SerializeMessage(context.GetFd(), result); + case RESERVED_IDS_INFO: + response.GetReservedIds(*result.mutable_reserved_ids_info(), executor->GetReservedIds()); + context.WriteSuccessResult(result); break; - } - case SHUT_DOWN: { + case SHUT_DOWN: if (executor->ShutDown(context, GetParam(command, "mode"))) { TerminationHandler(0); } break; - } - default: { - // Wait until we become idle + case NO_OPERATION: + context.ReturnSuccessStatus(); + break; + + // TODO The image operations below can most likely directly be executed without calling the executor, + // because they do not require the target to be idle + case CREATE_IMAGE: + case DELETE_IMAGE: + case RENAME_IMAGE: + case COPY_IMAGE: + case PROTECT_IMAGE: + case UNPROTECT_IMAGE: + case RESERVE_IDS: + return executor->ProcessCmd(context); + + // The remaining commands can only be executed when the target is idle + // TODO What happens when the target becomes active while the command is still being executed? + // A field 'mutex locker' can probably avoid SCSI commands and ProcessCmd() being executed at the same time + default: + // TODO Find a better way to wait const timespec ts = { .tv_sec = 0, .tv_nsec = 500'000'000}; - while (active) { + while (target_is_active) { nanosleep(&ts, nullptr); } - - executor->ProcessCmd(context, command); - break; - } + return executor->ProcessCmd(context); } return true; } -int Piscsi::run(const vector& args) +int Piscsi::run(span args) { GOOGLE_PROTOBUF_VERIFY_VERSION; Banner(args); + // The -v option shall result in no other action except displaying the version + if (ranges::find_if(args, [] (const char *arg) { return !strcasecmp(arg, "-v"); } ) != args.end()) { + cout << piscsi_get_version_string() << '\n'; + return EXIT_SUCCESS; + } + + PbCommand command; + string locale; + string reserved_ids; int port = DEFAULT_PORT; - optargs_type optargs; try { - optargs = ParseArguments(args, port); + locale = ParseArguments(args, command, port, reserved_ids); } catch(const parser_exception& e) { cerr << "Error: " << e.what() << endl; @@ -496,35 +479,51 @@ int Piscsi::run(const vector& args) return EXIT_FAILURE; } - // current_log_level may have been updated by ParseArguments() - executor->SetLogLevel(current_log_level); - - // Create a thread-safe stdout logger to process the log messages - const auto logger = stdout_color_mt("piscsi stdout logger"); - if (!InitBus()) { cerr << "Error: Can't initialize bus" << endl; return EXIT_FAILURE; } - // We need to wait to create the devices until after the bus/controller/etc objects have been created - // TODO Try to resolve dependencies so that this work-around can be removed - try { - CreateInitialDevices(optargs); - } - catch(const parser_exception& e) { - cerr << "Error: " << e.what() << endl; + if (const string error = service.Init([this] (CommandContext& context) { return ExecuteCommand(context); }, port); + !error.empty()) { + cerr << "Error: " << error << endl; - Cleanup(); + CleanUp(); return EXIT_FAILURE; } - if (!service.Init(&ExecuteCommand, port)) { + if (const string error = executor->SetReservedIds(reserved_ids); !error.empty()) { + cerr << "Error: " << error << endl; + + CleanUp(); + return EXIT_FAILURE; } + if (command.devices_size()) { + // Attach all specified devices + command.set_operation(ATTACH); + + if (const CommandContext context(command, piscsi_image.GetDefaultFolder(), locale); !executor->ProcessCmd(context)) { + cerr << "Error: Can't attach devices" << endl; + + CleanUp(); + + return EXIT_FAILURE; + } + } + + // Display and log the device list + PbServerInfo server_info; + response.GetDevices(controller_manager.GetAllDevices(), server_info, piscsi_image.GetDefaultFolder()); + const vector& devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() }; + const string device_list = ListDevices(devices); + LogDevices(device_list); + cout << device_list << flush; + + instance = this; // Signal handler to detach all devices on a KILL or TERM signal struct sigaction termination_handler; termination_handler.sa_handler = TerminationHandler; @@ -532,22 +531,30 @@ int Piscsi::run(const vector& args) termination_handler.sa_flags = 0; sigaction(SIGINT, &termination_handler, nullptr); sigaction(SIGTERM, &termination_handler, nullptr); + signal(SIGPIPE, SIG_IGN); // Set the affinity to a specific processor core FixCpu(3); - sched_param schparam; + service.Start(); + + Process(); + + return EXIT_SUCCESS; +} + +void Piscsi::Process() +{ #ifdef USE_SEL_EVENT_ENABLE // Scheduling policy setting (highest priority) + // TODO Check whether this results in any performance gain + sched_param schparam; schparam.sched_priority = sched_get_priority_max(SCHED_FIFO); sched_setscheduler(0, SCHED_FIFO, &schparam); #else cout << "Note: No PiSCSI hardware support, only client interface calls are supported" << endl; #endif - // Start execution - service.SetRunning(true); - // Main Loop while (service.IsRunning()) { #ifdef USE_SEL_EVENT_ENABLE @@ -571,81 +578,55 @@ int Piscsi::run(const vector& args) } #endif - // Wait until BSY is released as there is a possibility for the - // initiator to assert it while setting the ID (for up to 3 seconds) - WaitForNotBusy(); + // Only process the SCSI command if the bus is not busy and no other device responded + if (IsNotBusy() && bus->GetSEL()) { + target_is_active = true; - // Stop because the bus is busy or another device responded - if (bus->GetBSY() || !bus->GetSEL()) { - continue; - } - - int initiator_id = AbstractController::UNKNOWN_INITIATOR_ID; - - // The initiator and target ID - const uint8_t id_data = bus->GetDAT(); - - phase_t phase = phase_t::busfree; - - // Identify the responsible controller - auto controller = controller_manager->IdentifyController(id_data); - if (controller != nullptr) { - device_logger.SetIdAndLun(controller->GetTargetId(), -1); - - initiator_id = controller->ExtractInitiatorId(id_data); - - if (initiator_id != AbstractController::UNKNOWN_INITIATOR_ID) { - device_logger.Trace("++++ Starting processing for initiator ID " + to_string(initiator_id)); - } - else { - device_logger.Trace("++++ Starting processing for unknown initiator ID"); + // Process command on the responsible controller based on the current initiator and target ID + if (const auto shutdown_mode = controller_manager.ProcessOnController(bus->GetDAT()); + shutdown_mode != AbstractController::piscsi_shutdown_mode::NONE) { + // When the bus is free PiSCSI or the Pi may be shut down. + ShutDown(shutdown_mode); } - if (controller->Process(initiator_id) == phase_t::selection) { - phase = phase_t::selection; - } + target_is_active = false; } - - // Return to bus monitoring if the selection phase has not started - if (phase != phase_t::selection) { - continue; - } - - // Start target device - active = true; - -#if !defined(USE_SEL_EVENT_ENABLE) && defined(__linux__) - // Scheduling policy setting (highest priority) - schparam.sched_priority = sched_get_priority_max(SCHED_FIFO); - sched_setscheduler(0, SCHED_FIFO, &schparam); -#endif - - // Loop until the bus is free - while (service.IsRunning()) { - // Target drive - phase = controller->Process(initiator_id); - - // End when the bus is free - if (phase == phase_t::busfree) { - break; - } - } - -#if !defined(USE_SEL_EVENT_ENABLE) && defined(__linux__) - // Set the scheduling priority back to normal - schparam.sched_priority = 0; - sched_setscheduler(0, SCHED_OTHER, &schparam); -#endif - - // End the target travel - active = false; } - - return EXIT_SUCCESS; } -void Piscsi::WaitForNotBusy() const +void Piscsi::ShutDown(AbstractController::piscsi_shutdown_mode shutdown_mode) { + CleanUp(); + + switch(shutdown_mode) { + case AbstractController::piscsi_shutdown_mode::STOP_PISCSI: + spdlog::info("PiSCSI shutdown requested"); + break; + + case AbstractController::piscsi_shutdown_mode::STOP_PI: + spdlog::info("Raspberry Pi shutdown requested"); + if (system("init 0") == -1) { + spdlog::error("Raspberry Pi shutdown failed"); + } + break; + + case AbstractController::piscsi_shutdown_mode::RESTART_PI: + spdlog::info("Raspberry Pi restart requested"); + if (system("init 6") == -1) { + spdlog::error("Raspberry Pi restart failed"); + } + break; + + case AbstractController::piscsi_shutdown_mode::NONE: + assert(false); + break; + } +} + +bool Piscsi::IsNotBusy() const +{ + // Wait until BSY is released as there is a possibility for the + // initiator to assert it while setting the ID (for up to 3 seconds) if (bus->GetBSY()) { const uint32_t now = SysTimer::GetTimerLow(); @@ -654,8 +635,12 @@ void Piscsi::WaitForNotBusy() const bus->Acquire(); if (!bus->GetBSY()) { - break; + return true; } } + + return false; } + + return true; } diff --git a/cpp/piscsi/piscsi_core.h b/cpp/piscsi/piscsi_core.h index 22cb158b..fea8dd73 100644 --- a/cpp/piscsi/piscsi_core.h +++ b/cpp/piscsi/piscsi_core.h @@ -3,31 +3,31 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once -#include "devices/device_logger.h" +#include "controllers/controller_manager.h" +#include "controllers/abstract_controller.h" #include "piscsi/command_context.h" #include "piscsi/piscsi_service.h" #include "piscsi/piscsi_image.h" #include "piscsi/piscsi_response.h" +#include "piscsi/piscsi_executor.h" #include "generated/piscsi_interface.pb.h" -#include +#include "spdlog/sinks/stdout_color_sinks.h" +#include #include +#include using namespace std; class BUS; -class ControllerManager; -class PiscsiExecutor; class Piscsi { - using optargs_type = vector>; - static const int DEFAULT_PORT = 6868; public: @@ -35,46 +35,47 @@ public: Piscsi() = default; ~Piscsi() = default; - int run(const vector&); + int run(span); private: - void Banner(const vector&) const; - bool InitBus() const; - static void Cleanup(); - void ReadAccessToken(const string&) const; + void Banner(span) const; + bool InitBus(); + void CleanUp(); + void ReadAccessToken(const path&); void LogDevices(string_view) const; - PbDeviceType ParseDeviceType(const string&) const; static void TerminationHandler(int); - optargs_type ParseArguments(const vector&, int&) const; - void CreateInitialDevices(const optargs_type&) const; - void WaitForNotBusy() const; + string ParseArguments(span, PbCommand&, int&, string&); + void Process(); + bool IsNotBusy() const; - // TODO Should not be static and should be moved to PiscsiService - static bool ExecuteCommand(const CommandContext&, const PbCommand&); + void ShutDown(AbstractController::piscsi_shutdown_mode); - DeviceLogger device_logger; + bool ExecuteCommand(CommandContext&); - // A static instance is needed because of the signal handler - static inline shared_ptr bus; + bool SetLogLevel(const string&) const; - // TODO These fields should not be static + const shared_ptr logger = spdlog::stdout_color_mt("piscsi stdout logger"); - static inline PiscsiService service; - - static inline PiscsiImage piscsi_image; - - const static inline PiscsiResponse piscsi_response; - - static inline shared_ptr controller_manager; - - static inline shared_ptr executor; + static PbDeviceType ParseDeviceType(const string&); // Processing flag - static inline volatile bool active; + atomic_bool target_is_active; - // Some versions of spdlog do not support get_log_level(), so we have to remember the level - static inline string current_log_level = "info"; + string access_token; - static inline string access_token; + PiscsiImage piscsi_image; + + PiscsiResponse response; + + PiscsiService service; + + unique_ptr executor; + + ControllerManager controller_manager; + + unique_ptr bus; + + // Required for the termination handler + static inline Piscsi *instance; }; diff --git a/cpp/piscsi/piscsi_executor.cpp b/cpp/piscsi/piscsi_executor.cpp index 2f984992..c5564e01 100644 --- a/cpp/piscsi/piscsi_executor.cpp +++ b/cpp/piscsi/piscsi_executor.cpp @@ -3,35 +3,31 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "shared/log.h" #include "shared/piscsi_util.h" #include "shared/protobuf_util.h" #include "shared/piscsi_exceptions.h" -#include "controllers/controller_manager.h" #include "controllers/scsi_controller.h" -#include "devices/device_logger.h" #include "devices/device_factory.h" #include "devices/primary_device.h" #include "devices/disk.h" -#include "piscsi_service.h" #include "piscsi_image.h" #include "localizer.h" #include "command_context.h" #include "piscsi_executor.h" +#include #include using namespace spdlog; using namespace protobuf_util; using namespace piscsi_util; -bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDeviceDefinition& pb_device, - const PbCommand& command, bool dryRun) +bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDeviceDefinition& pb_device, bool dryRun) { - PrintCommand(command, pb_device, dryRun); + spdlog::info((dryRun ? "Validating: " : "Executing: ") + PrintCommand(context.GetCommand(), pb_device)); const int id = pb_device.id(); const int lun = pb_device.unit(); @@ -40,14 +36,14 @@ bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDev return false; } - const PbOperation operation = command.operation(); + const PbOperation operation = context.GetCommand().operation(); // For all commands except ATTACH the device and LUN must exist if (operation != ATTACH && !VerifyExistingIdAndLun(context, id, lun)) { return false; } - auto device = controller_manager.GetDeviceByIdAndLun(id, lun); + auto device = controller_manager.GetDeviceForIdAndLun(id, lun); if (!ValidateOperationAgainstDevice(context, *device, operation)) { return false; @@ -64,7 +60,7 @@ bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDev return Attach(context, pb_device, dryRun); case DETACH: - return Detach(context, device, dryRun); + return Detach(context, *device, dryRun); case INSERT: return Insert(context, pb_device, device, dryRun); @@ -82,7 +78,7 @@ bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDev case CHECK_AUTHENTICATION: case NO_OPERATION: // Do nothing, just log - LOGTRACE("Received %s command", PbOperation_Name(operation).c_str()) + spdlog::trace("Received " + PbOperation_Name(operation) + " command"); break; default: @@ -92,136 +88,83 @@ bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDev return true; } -bool PiscsiExecutor::ProcessCmd(const CommandContext& context, const PbCommand& command) +bool PiscsiExecutor::ProcessCmd(const CommandContext& context) { + const PbCommand& command = context.GetCommand(); + switch (command.operation()) { case DETACH_ALL: DetachAll(); - return context.ReturnStatus(); + return context.ReturnSuccessStatus(); case RESERVE_IDS: { const string ids = GetParam(command, "ids"); if (const string error = SetReservedIds(ids); !error.empty()) { - return context.ReturnStatus(false, error); + return context.ReturnErrorStatus(error); } - return context.ReturnStatus(); + return context.ReturnSuccessStatus(); } case CREATE_IMAGE: - return piscsi_image.CreateImage(context, command); + return piscsi_image.CreateImage(context); case DELETE_IMAGE: - return piscsi_image.DeleteImage(context, command); + return piscsi_image.DeleteImage(context); case RENAME_IMAGE: - return piscsi_image.RenameImage(context, command); + return piscsi_image.RenameImage(context); case COPY_IMAGE: - return piscsi_image.CopyImage(context, command); + return piscsi_image.CopyImage(context); case PROTECT_IMAGE: case UNPROTECT_IMAGE: - return piscsi_image.SetImagePermissions(context, command); + return piscsi_image.SetImagePermissions(context); default: // This is a device-specific command handled below break; } - // Remember the list of reserved files, than run the dry run + // Remember the list of reserved files during the dry run const auto& reserved_files = StorageDevice::GetReservedFiles(); - for (const auto& device : command.devices()) { - if (!ProcessDeviceCmd(context, device, command, true)) { - // Dry run failed, restore the file list - StorageDevice::SetReservedFiles(reserved_files); - return false; - } - } - - // Restore the list of reserved files before proceeding + const bool reserved = ranges::find_if_not(context.GetCommand().devices(), [&] (const auto& device) + { return ProcessDeviceCmd(context, device, true); }) != command.devices().end(); StorageDevice::SetReservedFiles(reserved_files); - - if (const string result = ValidateLunSetup(command); !result.empty()) { - return context.ReturnStatus(false, result); - } - - for (const auto& device : command.devices()) { - if (!ProcessDeviceCmd(context, device, command, false)) { - return false; - } - } - - // ATTACH and DETACH return the device list - if (context.IsValid() && (command.operation() == ATTACH || command.operation() == DETACH)) { - // A new command with an empty device list is required here in order to return data for all devices - PbCommand cmd; - PbResult result; - piscsi_response.GetDevicesInfo(controller_manager.GetAllDevices(), result, cmd, - piscsi_image.GetDefaultFolder()); - serializer.SerializeMessage(context.GetFd(), result); - return true; - } - - return context.ReturnStatus(); -} - -bool PiscsiExecutor::SetLogLevel(const string& log_level) const -{ - int id = -1; - int lun = -1; - string level = log_level; - - if (size_t separator_pos = log_level.find(COMPONENT_SEPARATOR); separator_pos != string::npos) { - level = log_level.substr(0, separator_pos); - - const string l = log_level.substr(separator_pos + 1); - separator_pos = l.find(COMPONENT_SEPARATOR); - if (separator_pos != string::npos) { - const string error = ProcessId(l, ScsiController::LUN_MAX, id, lun); - if (!error.empty()) { - LOGWARN("Invalid device ID/LUN specifier '%s'", l.c_str()) - return false; - } - } - else if (!GetAsUnsignedInt(l, id)) { - LOGWARN("Invalid device ID specifier '%s'", l.c_str()) - return false; - } - } - - if (const auto& it = log_level_mapping.find(level); it != log_level_mapping.end()) { - set_level(it->second); - } - else { - LOGWARN("Invalid log level '%s'", log_level.c_str()) + if (reserved) { return false; } - DeviceLogger::SetLogIdAndLun(id, lun); - - if (id != -1) { - if (lun == -1) { - LOGINFO("Set log level for device ID %d to '%s'", id, level.c_str()) - } - else { - LOGINFO("Set log level for device ID %d, LUN %d to '%s'", id, lun, level.c_str()) - } - } - else { - LOGINFO("Set log level to '%s'", level.c_str()) + if (const string error = EnsureLun0(command); !error.empty()) { + return context.ReturnErrorStatus(error); } - return true; + if (ranges::find_if_not(command.devices(), [&] (const auto& device) + { return ProcessDeviceCmd(context, device, false); } ) != command.devices().end()) { + return false; + } + + // ATTACH and DETACH return the device list + if (command.operation() == ATTACH || command.operation() == DETACH) { + // A new command with an empty device list is required here in order to return data for all devices + PbCommand cmd; + PbResult result; + piscsi_response.GetDevicesInfo(controller_manager.GetAllDevices(), result, cmd, context.GetDefaultFolder()); + context.WriteResult(result); + return true; + } + + return context.ReturnSuccessStatus(); } bool PiscsiExecutor::Start(PrimaryDevice& device, bool dryRun) const { if (!dryRun) { - LOGINFO("Start requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(), device.GetLun()) + spdlog::info("Start requested for " + device.GetIdentifier()); if (!device.Start()) { - LOGWARN("Starting %s ID %d, unit %d failed", device.GetTypeString(), device.GetId(), device.GetLun()) + spdlog::warn("Starting " + device.GetIdentifier() + " failed"); } } @@ -231,7 +174,7 @@ bool PiscsiExecutor::Start(PrimaryDevice& device, bool dryRun) const bool PiscsiExecutor::Stop(PrimaryDevice& device, bool dryRun) const { if (!dryRun) { - LOGINFO("Stop requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(), device.GetLun()) + spdlog::info("Stop requested for " + device.GetIdentifier()); device.Stop(); } @@ -242,10 +185,10 @@ bool PiscsiExecutor::Stop(PrimaryDevice& device, bool dryRun) const bool PiscsiExecutor::Eject(PrimaryDevice& device, bool dryRun) const { if (!dryRun) { - LOGINFO("Eject requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(), device.GetLun()) + spdlog::info("Eject requested for " + device.GetIdentifier()); if (!device.Eject(true)) { - LOGWARN("Ejecting %s ID %d, unit %d failed", device.GetTypeString(), device.GetId(), device.GetLun()) + spdlog::warn("Ejecting " + device.GetIdentifier() + " failed"); } } @@ -255,8 +198,7 @@ bool PiscsiExecutor::Eject(PrimaryDevice& device, bool dryRun) const bool PiscsiExecutor::Protect(PrimaryDevice& device, bool dryRun) const { if (!dryRun) { - LOGINFO("Write protection requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(), - device.GetLun()) + spdlog::info("Write protection requested for " + device.GetIdentifier()); device.SetProtected(true); } @@ -267,8 +209,7 @@ bool PiscsiExecutor::Protect(PrimaryDevice& device, bool dryRun) const bool PiscsiExecutor::Unprotect(PrimaryDevice& device, bool dryRun) const { if (!dryRun) { - LOGINFO("Write unprotection requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(), - device.GetLun()) + spdlog::info("Write unprotection requested for " + device.GetIdentifier()); device.SetProtected(false); } @@ -282,15 +223,16 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit const int lun = pb_device.unit(); const PbDeviceType type = pb_device.type(); - if (lun >= ScsiController::LUN_MAX) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun), to_string(ScsiController::LUN_MAX)); + if (lun >= ControllerManager::GetScsiLunMax()) { + return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun), + to_string(ControllerManager::GetScsiLunMax())); } - if (controller_manager.GetDeviceByIdAndLun(id, lun) != nullptr) { + if (controller_manager.HasDeviceForIdAndLun(id, lun)) { return context.ReturnLocalizedError(LocalizationKey::ERROR_DUPLICATE_ID, to_string(id), to_string(lun)); } - if (reserved_ids.find(id) != reserved_ids.end()) { + if (reserved_ids.contains(id)) { return context.ReturnLocalizedError(LocalizationKey::ERROR_RESERVED_ID, to_string(id)); } @@ -302,8 +244,7 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit } // If no filename was provided the medium is considered not inserted - auto storage_device = dynamic_pointer_cast(device); - device->SetRemoved(storage_device != nullptr ? filename.empty() : false); + device->SetRemoved(device->SupportsFile() ? filename.empty() : false); if (!SetProductData(context, pb_device, *device)) { return false; @@ -313,14 +254,14 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit return false; } - string full_path; + const auto storage_device = dynamic_pointer_cast(device); if (device->SupportsFile()) { // Only with removable media drives, CD and MO the medium (=file) may be inserted later if (!device->IsRemovable() && filename.empty()) { return context.ReturnLocalizedError(LocalizationKey::ERROR_MISSING_FILENAME, PbDeviceType_Name(type)); } - if (!ValidateImageFile(context, *storage_device, filename, full_path)) { + if (!ValidateImageFile(context, *storage_device, filename)) { return false; } } @@ -336,23 +277,22 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit return true; } - unordered_map params = { pb_device.params().begin(), pb_device.params().end() }; + param_map params = { pb_device.params().begin(), pb_device.params().end() }; if (!device->SupportsFile()) { // Clients like scsictl might have sent both "file" and "interfaces" params.erase("file"); } if (!device->Init(params)) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_INITIALIZATION, PbDeviceType_Name(device->GetType()), - to_string(id), to_string(lun)); + return context.ReturnLocalizedError(LocalizationKey::ERROR_INITIALIZATION, device->GetIdentifier()); + } + + if (!controller_manager.AttachToController(bus, id, device)) { + return context.ReturnLocalizedError(LocalizationKey::ERROR_SCSI_CONTROLLER); } if (storage_device != nullptr) { - storage_device->ReserveFile(full_path, id, lun); - } - - if (!controller_manager.AttachToScsiController(id, device)) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_SCSI_CONTROLLER); + storage_device->ReserveFile(); } string msg = "Attached "; @@ -362,8 +302,8 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit else if (device->IsProtectable() && device->IsProtected()) { msg += "protected "; } - msg += string(device->GetTypeString()) + " device, ID " + to_string(id) + ", unit " + to_string(lun); - LOGINFO("%s", msg.c_str()) + msg += device->GetIdentifier(); + spdlog::info(msg); return true; } @@ -371,12 +311,11 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit bool PiscsiExecutor::Insert(const CommandContext& context, const PbDeviceDefinition& pb_device, const shared_ptr& device, bool dryRun) const { - auto storage_device = dynamic_pointer_cast(device); - if (storage_device == nullptr) { + if (!device->SupportsFile()) { return false; } - if (!storage_device->IsRemoved()) { + if (!device->IsRemoved()) { return context.ReturnLocalizedError(LocalizationKey::ERROR_EJECT_REQUIRED); } @@ -394,56 +333,52 @@ bool PiscsiExecutor::Insert(const CommandContext& context, const PbDeviceDefinit return true; } - LOGINFO("Insert %sfile '%s' requested into %s ID %d, unit %d", pb_device.protected_() ? "protected " : "", - filename.c_str(), storage_device->GetTypeString(), pb_device.id(), pb_device.unit()) + spdlog::info("Insert " + string(pb_device.protected_() ? "protected " : "") + "file '" + filename + + "' requested into " + device->GetIdentifier()); + // TODO It may be better to add PrimaryDevice::Insert for all device-specific insert operations + auto storage_device = dynamic_pointer_cast(device); if (!SetSectorSize(context, storage_device, pb_device.block_size())) { return false; } - string full_path; - if (!ValidateImageFile(context, *storage_device, filename, full_path)) { + if (!ValidateImageFile(context, *storage_device, filename)) { return false; } storage_device->SetProtected(pb_device.protected_()); - storage_device->ReserveFile(full_path, storage_device->GetId(), storage_device->GetLun()); + storage_device->ReserveFile(); storage_device->SetMediumChanged(true); return true; } -bool PiscsiExecutor::Detach(const CommandContext& context, const shared_ptr& device, bool dryRun) const +bool PiscsiExecutor::Detach(const CommandContext& context, PrimaryDevice& device, bool dryRun) { - auto controller = controller_manager.FindController(device->GetId()); + auto controller = controller_manager.FindController(device.GetId()); if (controller == nullptr) { return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH); } // LUN 0 can only be detached if there is no other LUN anymore - if (!device->GetLun() && controller->GetLunCount() > 1) { + if (!device.GetLun() && controller->GetLunCount() > 1) { return context.ReturnLocalizedError(LocalizationKey::ERROR_LUN0); } if (!dryRun) { - // Remember the ID before it gets invalid when removing the device - const int id = device->GetId(); + // Remember the device identifier for the log message before the device data become invalid on removal + const string identifier = device.GetIdentifier(); if (!controller->RemoveDevice(device)) { return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH); } // If no LUN is left also delete the controller - if (!controller->GetLunCount() && !controller_manager.DeleteController(controller)) { + if (!controller->GetLunCount() && !controller_manager.DeleteController(*controller)) { return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH); } - if (auto storage_device = dynamic_pointer_cast(device); storage_device != nullptr) { - storage_device->UnreserveFile(); - } - - LOGINFO("%s", ("Detached " + string(device->GetTypeString()) + " device with ID " + to_string(id) - + ", unit " + to_string(device->GetLun())).c_str()) + spdlog::info("Detached " + identifier); } return true; @@ -452,9 +387,8 @@ bool PiscsiExecutor::Detach(const CommandContext& context, const shared_ptr ids_to_reserve; + set ids_to_reserve; stringstream ss(ids.data()); string id; while (getline(ss, id, ',')) { - if (!id.empty()) { - ids_to_reserve.push_back(id); - } - } - - set reserved; - for (const string& id_to_reserve : ids_to_reserve) { int res_id; - if (!GetAsUnsignedInt(id_to_reserve, res_id) || res_id > 7) { - return "Invalid ID " + id_to_reserve; - } - - if (controller_manager.FindController(res_id) != nullptr) { - return "ID " + id_to_reserve + " is currently in use"; - } - - reserved.insert(res_id); - } - - reserved_ids = { reserved.begin(), reserved.end() }; - - if (!reserved_ids.empty()) { - string s; - bool isFirst = true; - for (const auto& reserved_id : reserved) { - if (!isFirst) { - s += ", "; - } - isFirst = false; - s += to_string(reserved_id); + if (!GetAsUnsignedInt(id, res_id) || res_id > 7) { + return "Invalid ID " + id; } - LOGINFO("Reserved ID(s) set to %s", s.c_str()) + if (controller_manager.HasController(res_id)) { + return "ID " + id + " is currently in use"; + } + + ids_to_reserve.insert(res_id); + } + + reserved_ids = { ids_to_reserve.begin(), ids_to_reserve.end() }; + + if (!ids_to_reserve.empty()) { + spdlog::info("Reserved ID(s) set to " + Join(ids_to_reserve)); } else { - LOGINFO("Cleared reserved ID(s)") + spdlog::info("Cleared reserved ID(s)"); } return ""; } bool PiscsiExecutor::ValidateImageFile(const CommandContext& context, StorageDevice& storage_device, - const string& filename, string& full_path) const + const string& filename) const { if (filename.empty()) { return true; } - if (const auto [id1, lun1] = StorageDevice::GetIdsForReservedFile(filename); id1 != -1 || lun1 != -1) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_IN_USE, filename, - to_string(id1), to_string(lun1)); + if (!CheckForReservedFile(context, filename)) { + return false; } - string effective_filename = filename; + storage_device.SetFilename(filename); if (!StorageDevice::FileExists(filename)) { // If the file does not exist search for it in the default image folder - effective_filename = piscsi_image.GetDefaultFolder() + "/" + filename; + const string effective_filename = context.GetDefaultFolder() + "/" + filename; - if (const auto [id2, lun2] = StorageDevice::GetIdsForReservedFile(effective_filename); id2 != -1 || lun2 != -1) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_IN_USE, filename, - to_string(id2), to_string(lun2)); + if (!CheckForReservedFile(context, effective_filename)) { + return false; } - if (!StorageDevice::FileExists(effective_filename)) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_FILE_OPEN, effective_filename); - } - } - - storage_device.SetFilename(effective_filename); - - if (storage_device.IsReadOnlyFile()) { - // Permanently write-protected - storage_device.SetReadOnly(true); - storage_device.SetProtectable(false); - } - else { - storage_device.SetReadOnly(false); - storage_device.SetProtectable(true); + storage_device.SetFilename(effective_filename); } try { storage_device.Open(); } catch(const io_exception&) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_FILE_OPEN, effective_filename); + return context.ReturnLocalizedError(LocalizationKey::ERROR_FILE_OPEN, storage_device.GetFilename()); } - full_path = effective_filename; - return true; } -void PiscsiExecutor::PrintCommand(const PbCommand& command, const PbDeviceDefinition& pb_device, bool dryRun) const +bool PiscsiExecutor::CheckForReservedFile(const CommandContext& context, const string& filename) +{ + if (const auto [id, lun] = StorageDevice::GetIdsForReservedFile(filename); id != -1) { + return context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_IN_USE, filename, + to_string(id) + ":" + to_string(lun)); + } + + return true; +} + +string PiscsiExecutor::PrintCommand(const PbCommand& command, const PbDeviceDefinition& pb_device) const { const map> params = { command.params().begin(), command.params().end() }; ostringstream s; - s << (dryRun ? "Validating" : "Executing"); - s << ": operation=" << PbOperation_Name(command.operation()); + s << "operation=" << PbOperation_Name(command.operation()); if (!params.empty()) { s << ", command params="; bool isFirst = true; - for (const auto& [key, value]: params) { + for (const auto& [key, value] : params) { if (!isFirst) { s << ", "; } @@ -632,8 +537,7 @@ void PiscsiExecutor::PrintCommand(const PbCommand& command, const PbDeviceDefini } } - s << ", device id=" << pb_device.id() << ", lun=" << pb_device.unit() << ", type=" - << PbDeviceType_Name(pb_device.type()); + s << ", device=" << pb_device.id() << ":" << pb_device.unit() << ", type=" << PbDeviceType_Name(pb_device.type()); if (pb_device.params_size()) { s << ", device params="; @@ -649,13 +553,14 @@ void PiscsiExecutor::PrintCommand(const PbCommand& command, const PbDeviceDefini s << ", vendor='" << pb_device.vendor() << "', product='" << pb_device.product() << "', revision='" << pb_device.revision() << "', block size=" << pb_device.block_size(); - LOGINFO("%s", s.str().c_str()) + + return s.str(); } -string PiscsiExecutor::ValidateLunSetup(const PbCommand& command) const +string PiscsiExecutor::EnsureLun0(const PbCommand& command) const { // Mapping of available LUNs (bit vector) to devices - unordered_map luns; + unordered_map luns; // Collect LUN bit vectors of new devices for (const auto& device : command.devices()) { @@ -667,23 +572,17 @@ string PiscsiExecutor::ValidateLunSetup(const PbCommand& command) const luns[device->GetId()] |= 1 << device->GetLun(); } - // LUN 0 must exist for all devices - for (const auto& [id, lun]: luns) { - if (!(lun & 0x01)) { - return "LUN 0 is missing for device ID " + to_string(id); - } - } - - return ""; + const auto& it = ranges::find_if_not(luns, [] (const auto& l) { return l.second & 0x01; } ); + return it == luns.end() ? "" : "LUN 0 is missing for device ID " + to_string((*it).first); } bool PiscsiExecutor::VerifyExistingIdAndLun(const CommandContext& context, int id, int lun) const { - if (controller_manager.FindController(id) == nullptr) { + if (!controller_manager.HasController(id)) { return context.ReturnLocalizedError(LocalizationKey::ERROR_NON_EXISTING_DEVICE, to_string(id)); } - if (controller_manager.GetDeviceByIdAndLun(id, lun) == nullptr) { + if (!controller_manager.HasDeviceForIdAndLun(id, lun)) { return context.ReturnLocalizedError(LocalizationKey::ERROR_NON_EXISTING_UNIT, to_string(id), to_string(lun)); } @@ -706,13 +605,13 @@ shared_ptr PiscsiExecutor::CreateDevice(const CommandContext& con return device; } -bool PiscsiExecutor::SetSectorSize(const CommandContext& context, shared_ptr device, int block_size) const +bool PiscsiExecutor::SetSectorSize(const CommandContext& context, shared_ptr device, int size) const { - if (block_size) { - auto disk = dynamic_pointer_cast(device); + if (size) { + const auto disk = dynamic_pointer_cast(device); if (disk != nullptr && disk->IsSectorSizeConfigurable()) { - if (!disk->SetConfiguredSectorSize(device_factory, block_size)) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_BLOCK_SIZE, to_string(block_size)); + if (!disk->SetConfiguredSectorSize(device_factory, size)) { + return context.ReturnLocalizedError(LocalizationKey::ERROR_BLOCK_SIZE, to_string(size)); } } else { @@ -725,22 +624,26 @@ bool PiscsiExecutor::SetSectorSize(const CommandContext& context, shared_ptr= ControllerManager::DEVICE_MAX) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_ID, to_string(id), to_string(ControllerManager::DEVICE_MAX - 1)); + if (id >= ControllerManager::GetScsiIdMax()) { + return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_ID, to_string(id), + to_string(ControllerManager::GetScsiIdMax() - 1)); } - if (lun < 0 || lun >= ScsiController::LUN_MAX) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun), to_string(ScsiController::LUN_MAX - 1)); + if (lun < 0 || lun >= ControllerManager::GetScsiLunMax()) { + return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun), + to_string(ControllerManager::GetScsiLunMax() - 1)); } return true; @@ -777,7 +681,7 @@ bool PiscsiExecutor::SetProductData(const CommandContext& context, const PbDevic } } catch(const invalid_argument& e) { - return context.ReturnStatus(false, e.what()); + return context.ReturnErrorStatus(e.what()); } return true; diff --git a/cpp/piscsi/piscsi_executor.h b/cpp/piscsi/piscsi_executor.h index cf2739a3..efa5916e 100644 --- a/cpp/piscsi/piscsi_executor.h +++ b/cpp/piscsi/piscsi_executor.h @@ -3,22 +3,21 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once -#include "spdlog/spdlog.h" -#include "shared/protobuf_serializer.h" +#include "hal/bus.h" +#include "controllers/controller_manager.h" #include "piscsi/piscsi_response.h" #include -#include class PiscsiImage; class DeviceFactory; -class ControllerManager; class PrimaryDevice; +class StorageDevice; class CommandContext; using namespace spdlog; @@ -27,15 +26,14 @@ class PiscsiExecutor { public: - PiscsiExecutor(PiscsiImage& piscsi_image, ControllerManager& controller_manager) - : piscsi_image(piscsi_image), controller_manager(controller_manager) {} + PiscsiExecutor(PiscsiImage& piscsi_image, BUS& bus, ControllerManager& controller_manager) + : piscsi_image(piscsi_image), bus(bus), controller_manager(controller_manager) {} ~PiscsiExecutor() = default; - unordered_set GetReservedIds() const { return reserved_ids; } + auto GetReservedIds() const { return reserved_ids; } - bool ProcessDeviceCmd(const CommandContext&, const PbDeviceDefinition&, const PbCommand&, bool); - bool ProcessCmd(const CommandContext&, const PbCommand&); - bool SetLogLevel(const string&) const; + bool ProcessDeviceCmd(const CommandContext&, const PbDeviceDefinition&, bool); + bool ProcessCmd(const CommandContext&); bool Start(PrimaryDevice&, bool) const; bool Stop(PrimaryDevice&, bool) const; bool Eject(PrimaryDevice&, bool) const; @@ -43,41 +41,34 @@ public: bool Unprotect(PrimaryDevice&, bool) const; bool Attach(const CommandContext&, const PbDeviceDefinition&, bool); bool Insert(const CommandContext&, const PbDeviceDefinition&, const shared_ptr&, bool) const; - bool Detach(const CommandContext&, const shared_ptr&, bool) const; + bool Detach(const CommandContext&, PrimaryDevice&, bool); void DetachAll(); bool ShutDown(const CommandContext&, const string&); string SetReservedIds(string_view); - bool ValidateImageFile(const CommandContext&, StorageDevice&, const string&, string&) const; - void PrintCommand(const PbCommand&, const PbDeviceDefinition&, bool) const; - string ValidateLunSetup(const PbCommand&) const; + bool ValidateImageFile(const CommandContext&, StorageDevice&, const string&) const; + string PrintCommand(const PbCommand&, const PbDeviceDefinition&) const; + string EnsureLun0(const PbCommand&) const; bool VerifyExistingIdAndLun(const CommandContext&, int, int) const; shared_ptr CreateDevice(const CommandContext&, const PbDeviceType, int, const string&) const; bool SetSectorSize(const CommandContext&, shared_ptr, int) const; - static bool ValidateOperationAgainstDevice(const CommandContext&, const PrimaryDevice&, const PbOperation&); + static bool ValidateOperationAgainstDevice(const CommandContext&, const PrimaryDevice&, PbOperation); static bool ValidateIdAndLun(const CommandContext&, int, int); static bool SetProductData(const CommandContext&, const PbDeviceDefinition&, PrimaryDevice&); private: + static bool CheckForReservedFile(const CommandContext&, const string&); + const PiscsiResponse piscsi_response; PiscsiImage& piscsi_image; + BUS& bus; + ControllerManager& controller_manager; const DeviceFactory device_factory; - const ProtobufSerializer serializer; - unordered_set reserved_ids; - - static inline const unordered_map log_level_mapping = { - { "trace", level::trace }, - { "debug", level::debug }, - { "info", level::info }, - { "warn", level::warn }, - { "err", level::err }, - { "off", level::off } - }; }; diff --git a/cpp/piscsi/piscsi_image.cpp b/cpp/piscsi/piscsi_image.cpp index 6f32c78d..4c7dcc42 100644 --- a/cpp/piscsi/piscsi_image.cpp +++ b/cpp/piscsi/piscsi_image.cpp @@ -3,21 +3,18 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "shared/log.h" -#include "shared/protobuf_util.h" #include "devices/disk.h" -#include "command_context.h" #include "piscsi_image.h" +#include "shared/protobuf_util.h" +#include #include #include #include -#include #include -#include using namespace std; using namespace filesystem; @@ -32,14 +29,12 @@ PiscsiImage::PiscsiImage() bool PiscsiImage::CheckDepth(string_view filename) const { - return count(filename.begin(), filename.end(), '/') <= depth; + return ranges::count(filename, '/') <= depth; } -bool PiscsiImage::CreateImageFolder(const CommandContext& context, const string& filename) const +bool PiscsiImage::CreateImageFolder(const CommandContext& context, string_view filename) const { - if (const size_t filename_start = filename.rfind('/'); filename_start != string::npos) { - const auto folder = path(filename.substr(0, filename_start)); - + if (const auto folder = path(filename).parent_path(); !folder.string().empty()) { // Checking for existence first prevents an error if the top-level folder is a softlink if (error_code error; exists(folder, error)) { return true; @@ -51,67 +46,64 @@ bool PiscsiImage::CreateImageFolder(const CommandContext& context, const string& return ChangeOwner(context, folder, false); } catch(const filesystem_error& e) { - return context.ReturnStatus(false, "Can't create image folder '" + string(folder) + "': " + e.what()); + return context.ReturnErrorStatus("Can't create image folder '" + folder.string() + "': " + e.what()); } } return true; } -string PiscsiImage::SetDefaultFolder(const string& f) +string PiscsiImage::SetDefaultFolder(string_view f) { if (f.empty()) { return "Can't set default image folder: Missing folder name"; } - string folder = f; - // If a relative path is specified, the path is assumed to be relative to the user's home directory - if (folder[0] != '/') { - folder = GetHomeDir() + "/" + folder; + path folder(f); + if (folder.is_relative()) { + folder = path(GetHomeDir() + "/" + folder.string()); } - else { - if (folder.find("/home/") != 0) { - return "Default image folder must be located in '/home/'"; - } + + if (path home_root = path(GetHomeDir()).parent_path(); !folder.string().starts_with(home_root.string())) { + return "Default image folder must be located in '" + home_root.string() + "'"; } // Resolve a potential symlink - auto p = path(folder); - if (error_code error; is_symlink(p, error)) { - p = read_symlink(p); + if (error_code error; is_symlink(folder, error)) { + folder = read_symlink(folder); } - if (error_code error; !is_directory(p, error)) { - return "'" + string(p) + "' is not a valid folder"; + if (error_code error; !is_directory(folder)) { + return string("'") + folder.string() + "' is not a valid image folder"; } - default_folder = string(p); + default_folder = folder.string(); - LOGINFO("Default image folder set to '%s'", default_folder.c_str()) + spdlog::info("Default image folder set to '" + default_folder + "'"); return ""; } -bool PiscsiImage::CreateImage(const CommandContext& context, const PbCommand& command) const +bool PiscsiImage::CreateImage(const CommandContext& context) const { - const string filename = GetParam(command, "file"); + const string filename = GetParam(context.GetCommand(), "file"); if (filename.empty()) { - return context.ReturnStatus(false, "Can't create image file: Missing image filename"); + return context.ReturnErrorStatus("Missing image filename"); } if (!CheckDepth(filename)) { - return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + filename + "'").c_str()); + return context.ReturnErrorStatus(("Invalid folder hierarchy depth '" + filename + "'").c_str()); } const string full_filename = GetFullName(filename); if (!IsValidDstFilename(full_filename)) { - return context.ReturnStatus(false, "Can't create image file: '" + full_filename + "': File already exists"); + return context.ReturnErrorStatus("Can't create image file: '" + full_filename + "': File already exists"); } - const string size = GetParam(command, "size"); + const string size = GetParam(context.GetCommand(), "size"); if (size.empty()) { - return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': Missing file size"); + return context.ReturnErrorStatus("Can't create image file '" + full_filename + "': Missing file size"); } off_t len; @@ -119,20 +111,20 @@ bool PiscsiImage::CreateImage(const CommandContext& context, const PbCommand& co len = stoull(size); } catch(const invalid_argument&) { - return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': Invalid file size " + size); + return context.ReturnErrorStatus("Can't create image file '" + full_filename + "': Invalid file size " + size); } catch(const out_of_range&) { - return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': Invalid file size " + size); + return context.ReturnErrorStatus("Can't create image file '" + full_filename + "': Invalid file size " + size); } if (len < 512 || (len & 0x1ff)) { - return context.ReturnStatus(false, "Invalid image file size " + to_string(len) + " (not a multiple of 512)"); + return context.ReturnErrorStatus("Invalid image file size " + to_string(len) + " (not a multiple of 512)"); } if (!CreateImageFolder(context, full_filename)) { return false; } - const bool read_only = GetParam(command, "read_only") == "true"; + const bool read_only = GetParam(context.GetCommand(), "read_only") == "true"; error_code error; path file(full_filename); @@ -149,40 +141,37 @@ bool PiscsiImage::CreateImage(const CommandContext& context, const PbCommand& co catch(const filesystem_error& e) { remove(file, error); - return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': " + e.what()); + return context.ReturnErrorStatus("Can't create image file '" + full_filename + "': " + e.what()); } - LOGINFO("%s", string("Created " + string(read_only ? "read-only " : "") + "image file '" + full_filename + - "' with a size of " + to_string(len) + " bytes").c_str()) + spdlog::info("Created " + string(read_only ? "read-only " : "") + "image file '" + full_filename + + "' with a size of " + to_string(len) + " bytes"); - return context.ReturnStatus(); + return context.ReturnSuccessStatus(); } -bool PiscsiImage::DeleteImage(const CommandContext& context, const PbCommand& command) const +bool PiscsiImage::DeleteImage(const CommandContext& context) const { - const string filename = GetParam(command, "file"); + const string filename = GetParam(context.GetCommand(), "file"); if (filename.empty()) { - return context.ReturnStatus(false, "Missing image filename"); + return context.ReturnErrorStatus("Missing image filename"); } if (!CheckDepth(filename)) { - return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + filename + "'").c_str()); + return context.ReturnErrorStatus("Invalid folder hierarchy depth '" + filename + "'"); } const auto full_filename = path(GetFullName(filename)); - if (!exists(full_filename)) { - return context.ReturnStatus(false, "Image file '" + string(full_filename) + "' does not exist"); + return context.ReturnErrorStatus("Image file '" + full_filename.string() + "' does not exist"); } - const auto [id, lun] = StorageDevice::GetIdsForReservedFile(full_filename); - if (id != -1 || lun != -1) { - return context.ReturnStatus(false, "Can't delete image file '" + string(full_filename) + - "', it is currently being used by device ID " + to_string(id) + ", LUN " + to_string(lun)); + if (!IsReservedFile(context, full_filename, "delete")) { + return false; } if (error_code error; !remove(full_filename, error)) { - return context.ReturnStatus(false, "Can't delete image file '" + string(full_filename) + "'"); + return context.ReturnErrorStatus("Can't delete image file '" + full_filename.string() + "'"); } // Delete empty subfolders @@ -196,32 +185,22 @@ bool PiscsiImage::DeleteImage(const CommandContext& context, const PbCommand& co } if (error_code error; !remove(full_folder)) { - return context.ReturnStatus(false, "Can't delete empty image folder '" + string(full_folder) + "'"); + return context.ReturnErrorStatus("Can't delete empty image folder '" + full_folder.string() + "'"); } last_slash = folder.rfind('/'); } - LOGINFO("Deleted image file '%s'", full_filename.c_str()) + spdlog::info("Deleted image file '" + full_filename.string() + "'"); - return context.ReturnStatus(); + return context.ReturnSuccessStatus(); } -bool PiscsiImage::RenameImage(const CommandContext& context, const PbCommand& command) const +bool PiscsiImage::RenameImage(const CommandContext& context) const { string from; string to; - if (!ValidateParams(context, command, "rename/move", from, to)) { - return false; - } - - const auto [id, lun] = StorageDevice::GetIdsForReservedFile(from); - if (id != -1 || lun != -1) { - return context.ReturnStatus(false, "Can't rename/move image file '" + from + - "', it is currently being used by device ID " + to_string(id) + ", LUN " + to_string(lun)); - } - - if (!CreateImageFolder(context, to)) { + if (!ValidateParams(context, "rename/move", from, to)) { return false; } @@ -229,33 +208,19 @@ bool PiscsiImage::RenameImage(const CommandContext& context, const PbCommand& co rename(path(from), path(to)); } catch(const filesystem_error& e) { - return context.ReturnStatus(false, "Can't rename/move image file '" + from + "' to '" + to + "': " + e.what()); + return context.ReturnErrorStatus("Can't rename/move image file '" + from + "': " + e.what()); } - LOGINFO("Renamed/Moved image file '%s' to '%s'", from.c_str(), to.c_str()) + spdlog::info("Renamed/Moved image file '" + from + "' to '" + to + "'"); - return context.ReturnStatus(); + return context.ReturnSuccessStatus(); } -bool PiscsiImage::CopyImage(const CommandContext& context, const PbCommand& command) const +bool PiscsiImage::CopyImage(const CommandContext& context) const { string from; string to; - if (!ValidateParams(context, command, "copy", from, to)) { - return false; - } - - if (access(from.c_str(), R_OK)) { - return context.ReturnStatus(false, "Can't read source image file '" + from + "'"); - } - - const auto [id, lun] = StorageDevice::GetIdsForReservedFile(from); - if (id != -1 || lun != -1) { - return context.ReturnStatus(false, "Can't copy image file '" + from + - "', it is currently being used by device ID " + to_string(id) + ", LUN " + to_string(lun)); - } - - if (!CreateImageFolder(context, to)) { + if (!ValidateParams(context, "copy", from, to)) { return false; } @@ -268,119 +233,134 @@ bool PiscsiImage::CopyImage(const CommandContext& context, const PbCommand& comm copy_symlink(f, t); } catch(const filesystem_error& e) { - return context.ReturnStatus(false, "Can't copy image file symlink '" + from + "': " + e.what()); + return context.ReturnErrorStatus("Can't copy image file symlink '" + from + "': " + e.what()); } - LOGINFO("Copied image file symlink '%s' to '%s'", from.c_str(), to.c_str()) + spdlog::info("Copied image file symlink '" + from + "' to '" + to + "'"); - return context.ReturnStatus(); + return context.ReturnSuccessStatus(); } try { copy_file(f, t); - permissions(t, GetParam(command, "read_only") == "true" ? + permissions(t, GetParam(context.GetCommand(), "read_only") == "true" ? perms::owner_read | perms::group_read | perms::others_read : perms::owner_read | perms::group_read | perms::others_read | perms::owner_write | perms::group_write); } catch(const filesystem_error& e) { - return context.ReturnStatus(false, "Can't copy image file '" + from + "' to '" + to + "': " + e.what()); + return context.ReturnErrorStatus("Can't copy image file '" + from + "': " + e.what()); } - LOGINFO("Copied image file '%s' to '%s'", from.c_str(), to.c_str()) + spdlog::info("Copied image file '" + from + "' to '" + to + "'"); - return context.ReturnStatus(); + return context.ReturnSuccessStatus(); } -bool PiscsiImage::SetImagePermissions(const CommandContext& context, const PbCommand& command) const +bool PiscsiImage::SetImagePermissions(const CommandContext& context) const { - string filename = GetParam(command, "file"); + const string filename = GetParam(context.GetCommand(), "file"); if (filename.empty()) { - return context.ReturnStatus(false, "Missing image filename"); + return context.ReturnErrorStatus("Missing image filename"); } if (!CheckDepth(filename)) { - return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + filename + "'").c_str()); + return context.ReturnErrorStatus("Invalid folder hierarchy depth '" + filename + "'"); } - filename = GetFullName(filename); - if (!IsValidSrcFilename(filename)) { - return context.ReturnStatus(false, "Can't modify image file '" + filename + "': Invalid name or type"); + const string full_filename = GetFullName(filename); + if (!IsValidSrcFilename(full_filename)) { + return context.ReturnErrorStatus("Can't modify image file '" + full_filename + "': Invalid name or type"); } - const bool protect = command.operation() == PROTECT_IMAGE; + const bool protect = context.GetCommand().operation() == PROTECT_IMAGE; try { - permissions(path(filename), protect ? + permissions(path(full_filename), protect ? perms::owner_read | perms::group_read | perms::others_read : perms::owner_read | perms::group_read | perms::others_read | perms::owner_write | perms::group_write); } catch(const filesystem_error& e) { - return context.ReturnStatus(false, "Can't " + string(protect ? "protect" : "unprotect") + " image file '" + - filename + "': " + e.what()); + return context.ReturnErrorStatus("Can't " + string(protect ? "protect" : "unprotect") + " image file '" + + full_filename + "': " + e.what()); } - if (protect) { - LOGINFO("Protected image file '%s'", filename.c_str()) - } - else { - LOGINFO("Unprotected image file '%s'", filename.c_str()) - } + spdlog::info((protect ? "Protected" : "Unprotected") + string(" image file '") + full_filename + "'"); - return context.ReturnStatus(); + return context.ReturnSuccessStatus(); } -bool PiscsiImage::ValidateParams(const CommandContext& context, const PbCommand& command, const string& operation, - string& from, string& to) const +bool PiscsiImage::IsReservedFile(const CommandContext& context, const string& file, const string& op) { - from = GetParam(command, "from"); - if (from.empty()) { - return context.ReturnStatus(false, "Can't " + operation + " image file: Missing source filename"); - } - - if (!CheckDepth(from)) { - return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + from + "'").c_str()); - } - - from = GetFullName(from); - if (!IsValidSrcFilename(from)) { - return context.ReturnStatus(false, "Can't " + operation + " image file: '" + from + "': Invalid name or type"); - } - - to = GetParam(command, "to"); - if (to.empty()) { - return context.ReturnStatus(false, "Can't " + operation + " image file '" + from + "': Missing destination filename"); - } - - if (!CheckDepth(to)) { - return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + to + "'").c_str()); - } - - to = GetFullName(to); - if (!IsValidDstFilename(to)) { - return context.ReturnStatus(false, "Can't " + operation + " image file '" + from + "' to '" + to + "': File already exists"); + const auto [id, lun] = StorageDevice::GetIdsForReservedFile(file); + if (id != -1) { + return context.ReturnErrorStatus("Can't " + op + " image file '" + file + + "', it is currently being used by device " + to_string(id) + ":" + to_string(lun)); } return true; } -bool PiscsiImage::IsValidSrcFilename(const string& filename) +bool PiscsiImage::ValidateParams(const CommandContext& context, const string& op, string& from, string& to) const +{ + from = GetParam(context.GetCommand(), "from"); + if (from.empty()) { + return context.ReturnErrorStatus("Can't " + op + " image file: Missing source filename"); + } + + if (!CheckDepth(from)) { + return context.ReturnErrorStatus("Invalid folder hierarchy depth '" + from + "'"); + } + + to = GetParam(context.GetCommand(), "to"); + if (to.empty()) { + return context.ReturnErrorStatus("Can't " + op + " image file '" + from + "': Missing destination filename"); + } + + if (!CheckDepth(to)) { + return context.ReturnErrorStatus("Invalid folder hierarchy depth '" + to + "'"); + } + + from = GetFullName(from); + if (!IsValidSrcFilename(from)) { + return context.ReturnErrorStatus("Can't " + op + " image file: '" + from + "': Invalid name or type"); + } + + to = GetFullName(to); + if (!IsValidDstFilename(to)) { + return context.ReturnErrorStatus("Can't " + op + " image file '" + from + "' to '" + to + "': File already exists"); + } + + if (!IsReservedFile(context, from, op)) { + return false; + } + + if (!CreateImageFolder(context, to)) { + return false; + } + + return true; +} + +bool PiscsiImage::IsValidSrcFilename(string_view filename) { // Source file must exist and must be a regular file or a symlink path file(filename); - return is_regular_file(file) || is_symlink(file); + + error_code error; + return is_regular_file(file, error) || is_symlink(file, error); } -bool PiscsiImage::IsValidDstFilename(const string& filename) +bool PiscsiImage::IsValidDstFilename(string_view filename) { // Destination file must not yet exist try { return !exists(path(filename)); } catch(const filesystem_error&) { - return true; + return false; } } @@ -394,7 +374,7 @@ bool PiscsiImage::ChangeOwner(const CommandContext& context, const path& filenam error_code error; remove(filename, error); - return context.ReturnStatus(false, "Can't change ownership of '" + string(filename) + "': " + strerror(e)); + return context.ReturnErrorStatus("Can't change ownership of '" + filename.string() + "': " + strerror(e)); } permissions(filename, read_only ? @@ -437,5 +417,5 @@ pair PiscsiImage::GetUidAndGid() gid = pwd.pw_gid; } - return make_pair(uid, gid); + return { uid, gid }; } diff --git a/cpp/piscsi/piscsi_image.h b/cpp/piscsi/piscsi_image.h index 4a820835..f53e894d 100644 --- a/cpp/piscsi/piscsi_image.h +++ b/cpp/piscsi/piscsi_image.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -28,22 +28,23 @@ public: void SetDepth(int d) { depth = d; } int GetDepth() const { return depth; } string GetDefaultFolder() const { return default_folder; } - string SetDefaultFolder(const string&); - bool CreateImage(const CommandContext&, const PbCommand&) const; - bool DeleteImage(const CommandContext&, const PbCommand&) const; - bool RenameImage(const CommandContext&, const PbCommand&) const; - bool CopyImage(const CommandContext&, const PbCommand&) const; - bool SetImagePermissions(const CommandContext&, const PbCommand&) const; + string SetDefaultFolder(string_view); + bool CreateImage(const CommandContext&) const; + bool DeleteImage(const CommandContext&) const; + bool RenameImage(const CommandContext&) const; + bool CopyImage(const CommandContext&) const; + bool SetImagePermissions(const CommandContext&) const; private: bool CheckDepth(string_view) const; string GetFullName(const string& filename) const { return default_folder + "/" + filename; } - bool CreateImageFolder(const CommandContext&, const string&) const; - bool ValidateParams(const CommandContext&, const PbCommand&, const string&, string&, string&) const; + bool CreateImageFolder(const CommandContext&, string_view) const; + static bool IsReservedFile(const CommandContext&, const string&, const string&); + bool ValidateParams(const CommandContext&, const string&, string&, string&) const; - static bool IsValidSrcFilename(const string&); - static bool IsValidDstFilename(const string&); + static bool IsValidSrcFilename(string_view); + static bool IsValidDstFilename(string_view); static bool ChangeOwner(const CommandContext&, const path&, bool); static string GetHomeDir(); static pair GetUidAndGid(); diff --git a/cpp/piscsi/piscsi_response.cpp b/cpp/piscsi/piscsi_response.cpp index 35f3ec51..6987f78f 100644 --- a/cpp/piscsi/piscsi_response.cpp +++ b/cpp/piscsi/piscsi_response.cpp @@ -3,50 +3,48 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "shared/log.h" +#include "controllers/controller_manager.h" #include "shared/protobuf_util.h" +#include "shared/network_util.h" +#include "shared/piscsi_util.h" #include "shared/piscsi_version.h" #include "devices/disk.h" -#include "devices/device_factory.h" -#include "generated/piscsi_interface.pb.h" #include "piscsi_response.h" +#include #include using namespace std; using namespace filesystem; using namespace piscsi_interface; +using namespace piscsi_util; +using namespace network_util; using namespace protobuf_util; -unique_ptr PiscsiResponse::GetDeviceProperties(const Device& device) const +void PiscsiResponse::GetDeviceProperties(const Device& device, PbDeviceProperties& properties) const { - auto properties = make_unique(); - - // Currently there is only a SCSI controller, i.e. there can always be 32 LUNs - properties->set_luns(32); - properties->set_read_only(device.IsReadOnly()); - properties->set_protectable(device.IsProtectable()); - properties->set_stoppable(device.IsStoppable()); - properties->set_removable(device.IsRemovable()); - properties->set_lockable(device.IsLockable()); - properties->set_supports_file(device.SupportsFile()); - properties->set_supports_params(device.SupportsParams()); + properties.set_luns(ControllerManager::GetScsiLunMax()); + properties.set_read_only(device.IsReadOnly()); + properties.set_protectable(device.IsProtectable()); + properties.set_stoppable(device.IsStoppable()); + properties.set_removable(device.IsRemovable()); + properties.set_lockable(device.IsLockable()); + properties.set_supports_file(device.SupportsFile()); + properties.set_supports_params(device.SupportsParams()); if (device.SupportsParams()) { for (const auto& [key, value] : device_factory.GetDefaultParams(device.GetType())) { - auto& map = *properties->mutable_default_params(); + auto& map = *properties.mutable_default_params(); map[key] = value; } } for (const auto& block_size : device_factory.GetSectorSizes(device.GetType())) { - properties->add_block_sizes(block_size); + properties.add_block_sizes(block_size); } - - return properties; } void PiscsiResponse::GetDeviceTypeProperties(PbDeviceTypesInfo& device_types_info, PbDeviceType type) const @@ -54,10 +52,10 @@ void PiscsiResponse::GetDeviceTypeProperties(PbDeviceTypesInfo& device_types_inf auto type_properties = device_types_info.add_properties(); type_properties->set_type(type); const auto device = device_factory.CreateDevice(type, 0, ""); - type_properties->set_allocated_properties(GetDeviceProperties(*device).release()); -} //NOSONAR The allocated memory is managed by protobuf + GetDeviceProperties(*device, *type_properties->mutable_properties()); +} -void PiscsiResponse::GetAllDeviceTypeProperties(PbDeviceTypesInfo& device_types_info) const +void PiscsiResponse::GetDeviceTypesInfo(PbDeviceTypesInfo& device_types_info) const { // Start with 2 instead of 1. 1 was the removed SASI drive type. int ordinal = 2; @@ -78,16 +76,15 @@ void PiscsiResponse::GetDevice(const Device& device, PbDevice& pb_device, const pb_device.set_revision(device.GetRevision()); pb_device.set_type(device.GetType()); - pb_device.set_allocated_properties(GetDeviceProperties(device).release()); + GetDeviceProperties(device, *pb_device.mutable_properties()); - auto status = make_unique().release(); //NOSONAR The allocated memory is managed by protobuf - pb_device.set_allocated_status(status); + auto status = pb_device.mutable_status(); status->set_protected_(device.IsProtected()); status->set_stopped(device.IsStopped()); status->set_removed(device.IsRemoved()); status->set_locked(device.IsLocked()); - if (device.SupportsParams()) { //NOSONAR The allocated memory is managed by protobuf + if (device.SupportsParams()) { for (const auto& [key, value] : device.GetParams()) { SetParam(pb_device, key, value); } @@ -100,11 +97,9 @@ void PiscsiResponse::GetDevice(const Device& device, PbDevice& pb_device, const const auto storage_device = dynamic_cast(&device); if (storage_device != nullptr) { - auto image_file = make_unique().release(); - GetImageFile(*image_file, default_folder, device.IsReady() ? storage_device->GetFilename() : ""); - pb_device.set_allocated_file(image_file); + GetImageFile(*pb_device.mutable_file(), default_folder, device.IsReady() ? storage_device->GetFilename() : ""); } -} //NOSONAR The allocated memory is managed by protobuf +} bool PiscsiResponse::GetImageFile(PbImageFile& image_file, const string& default_folder, const string& filename) const { @@ -112,13 +107,13 @@ bool PiscsiResponse::GetImageFile(PbImageFile& image_file, const string& default image_file.set_name(filename); image_file.set_type(device_factory.GetTypeForFile(filename)); - const string f = filename[0] == '/' ? filename : default_folder + "/" + filename; + const path p(filename[0] == '/' ? filename : default_folder + "/" + filename); - image_file.set_read_only(access(f.c_str(), W_OK)); + image_file.set_read_only(access(p.c_str(), W_OK)); - // filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle files of more than 2 GiB - if (struct stat st; !stat(f.c_str(), &st) && !S_ISDIR(st.st_mode)) { - image_file.set_size(st.st_size); + error_code error; + if (is_regular_file(p, error) || (is_symlink(p, error) && !is_block_file(p, error))) { + image_file.set_size(file_size(p)); return true; } } @@ -127,92 +122,68 @@ bool PiscsiResponse::GetImageFile(PbImageFile& image_file, const string& default } void PiscsiResponse::GetAvailableImages(PbImageFilesInfo& image_files_info, const string& default_folder, - const string& folder, const string& folder_pattern, const string& file_pattern, int scan_depth) const + const string& folder_pattern, const string& file_pattern, int scan_depth) const { - if (scan_depth-- < 0) { + const path default_path(default_folder); + if (!is_directory(default_path)) { return; } - string folder_pattern_lower = folder_pattern; - transform(folder_pattern_lower.begin(), folder_pattern_lower.end(), folder_pattern_lower.begin(), ::tolower); + string folder_pattern_lower; + ranges::transform(folder_pattern, back_inserter(folder_pattern_lower), ::tolower); - string file_pattern_lower = file_pattern; - transform(file_pattern_lower.begin(), file_pattern_lower.end(), file_pattern_lower.begin(), ::tolower); + string file_pattern_lower; + ranges::transform(file_pattern, back_inserter(file_pattern_lower), ::tolower); - DIR *d = opendir(folder.c_str()); - if (d == nullptr) { - return; - } - - // C++ filesystem cannot be used here because gcc < 10.3.0 cannot handle files of more than 2 GiB - const dirent *dir; - while ((dir = readdir(d))) { - string filename = GetNextImageFile(dir, folder); - if (filename.empty()) { + for (auto it = recursive_directory_iterator(default_path, directory_options::follow_directory_symlink); + it != recursive_directory_iterator(); it++) { + if (it.depth() > scan_depth) { + it.disable_recursion_pending(); continue; } - string name_lower = dir->d_name; - if (!file_pattern.empty()) { - transform(name_lower.begin(), name_lower.end(), name_lower.begin(), ::tolower); - } + const string parent = it->path().parent_path().string(); - if (dir->d_type == DT_DIR) { - if (folder_pattern_lower.empty() || name_lower.find(folder_pattern_lower) != string::npos) { - GetAvailableImages(image_files_info, default_folder, filename, folder_pattern, - file_pattern, scan_depth); - } + const string folder = parent.size() > default_folder.size() ? parent.substr(default_folder.size() + 1) : ""; + if (!FilterMatches(folder, folder_pattern_lower) || !FilterMatches(it->path().filename().string(), file_pattern_lower)) { continue; } - if (file_pattern_lower.empty() || name_lower.find(file_pattern_lower) != string::npos) { - if (auto image_file = make_unique(); GetImageFile(*image_file.get(), default_folder, filename)) { - GetImageFile(*image_files_info.add_image_files(), default_folder, - filename.substr(default_folder.length() + 1)); - } + if (!ValidateImageFile(it->path())) { + continue; + } + + const string filename = folder.empty() ? + it->path().filename().string() : folder + "/" + it->path().filename().string(); + if (PbImageFile image_file; GetImageFile(image_file, default_folder, filename)) { + GetImageFile(*image_files_info.add_image_files(), default_folder, filename); } } - - closedir(d); } -unique_ptr PiscsiResponse::GetAvailableImages(PbResult& result, const string& default_folder, +void PiscsiResponse::GetImageFilesInfo(PbImageFilesInfo& image_files_info, const string& default_folder, const string& folder_pattern, const string& file_pattern, int scan_depth) const { - auto image_files_info = make_unique(); + image_files_info.set_default_image_folder(default_folder); + image_files_info.set_depth(scan_depth); - image_files_info->set_default_image_folder(default_folder); - image_files_info->set_depth(scan_depth); - - GetAvailableImages(*image_files_info, default_folder, default_folder, folder_pattern, - file_pattern, scan_depth); - - result.set_status(true); - - return image_files_info; + GetAvailableImages(image_files_info, default_folder, folder_pattern, file_pattern, scan_depth); } -void PiscsiResponse::GetAvailableImages(PbResult& result, PbServerInfo& server_info, const string& default_folder, +void PiscsiResponse::GetAvailableImages(PbServerInfo& server_info, const string& default_folder, const string& folder_pattern, const string& file_pattern, int scan_depth) const { - auto image_files_info = GetAvailableImages(result, default_folder, folder_pattern, file_pattern, scan_depth); - image_files_info->set_default_image_folder(default_folder); - server_info.set_allocated_image_files_info(image_files_info.release()); + server_info.mutable_image_files_info()->set_default_image_folder(default_folder); - result.set_status(true); //NOSONAR The allocated memory is managed by protobuf + GetImageFilesInfo(*server_info.mutable_image_files_info(), default_folder, folder_pattern, file_pattern, scan_depth); } -unique_ptr PiscsiResponse::GetReservedIds(PbResult& result, const unordered_set& ids) const +void PiscsiResponse::GetReservedIds(PbReservedIdsInfo& reserved_ids_info, const unordered_set& ids) const { - auto reserved_ids_info = make_unique(); for (const int id : ids) { - reserved_ids_info->add_ids(id); + reserved_ids_info.add_ids(id); } - - result.set_status(true); - - return reserved_ids_info; } void PiscsiResponse::GetDevices(const unordered_set>& devices, PbServerInfo& server_info, @@ -232,7 +203,7 @@ void PiscsiResponse::GetDevicesInfo(const unordered_setGetId(), device->GetLun())); + id_sets.insert({ device->GetId(), device->GetLun() }); } } // Otherwise get information on the devices provided in the command @@ -243,8 +214,7 @@ void PiscsiResponse::GetDevicesInfo(const unordered_set(); - + auto devices_info = result.mutable_devices_info(); for (const auto& [id, lun] : id_sets) { for (const auto& d : devices) { if (d->GetId() == id && d->GetLun() == lun) { @@ -254,244 +224,179 @@ void PiscsiResponse::GetDevicesInfo(const unordered_set PiscsiResponse::GetDeviceTypesInfo(PbResult& result) const +void PiscsiResponse::GetServerInfo(PbServerInfo& server_info, const unordered_set>& devices, + const unordered_set& reserved_ids, const string& default_folder, const string& folder_pattern, + const string& file_pattern, int scan_depth) const { - auto device_types_info = make_unique(); - - GetAllDeviceTypeProperties(*device_types_info); - - result.set_status(true); - - return device_types_info; + GetVersionInfo(*server_info.mutable_version_info()); + GetLogLevelInfo(*server_info.mutable_log_level_info()); + GetDeviceTypesInfo(*server_info.mutable_device_types_info()); + GetAvailableImages(server_info, default_folder, folder_pattern, file_pattern, scan_depth); + GetNetworkInterfacesInfo(*server_info.mutable_network_interfaces_info()); + GetMappingInfo(*server_info.mutable_mapping_info()); + GetDevices(devices, server_info, default_folder); + GetReservedIds(*server_info.mutable_reserved_ids_info(), reserved_ids); + GetOperationInfo(*server_info.mutable_operation_info(), scan_depth); } -unique_ptr PiscsiResponse::GetServerInfo(const unordered_set>& devices, - PbResult& result, const unordered_set& reserved_ids, const string& current_log_level, - const string& default_folder, const string& folder_pattern, const string& file_pattern, int scan_depth) const +void PiscsiResponse::GetVersionInfo(PbVersionInfo& version_info) const { - auto server_info = make_unique(); - - server_info->set_allocated_version_info(GetVersionInfo(result).release()); - server_info->set_allocated_log_level_info(GetLogLevelInfo(result, current_log_level).release()); //NOSONAR The allocated memory is managed by protobuf - GetAllDeviceTypeProperties(*server_info->mutable_device_types_info()); //NOSONAR The allocated memory is managed by protobuf - GetAvailableImages(result, *server_info, default_folder, folder_pattern, file_pattern, scan_depth); - server_info->set_allocated_network_interfaces_info(GetNetworkInterfacesInfo(result).release()); - server_info->set_allocated_mapping_info(GetMappingInfo(result).release()); //NOSONAR The allocated memory is managed by protobuf - GetDevices(devices, *server_info, default_folder); //NOSONAR The allocated memory is managed by protobuf - server_info->set_allocated_reserved_ids_info(GetReservedIds(result, reserved_ids).release()); - server_info->set_allocated_operation_info(GetOperationInfo(result, scan_depth).release()); //NOSONAR The allocated memory is managed by protobuf - - result.set_status(true); - - return server_info; + version_info.set_major_version(piscsi_major_version); + version_info.set_minor_version(piscsi_minor_version); + version_info.set_patch_version(piscsi_patch_version); } -unique_ptr PiscsiResponse::GetVersionInfo(PbResult& result) const +void PiscsiResponse::GetLogLevelInfo(PbLogLevelInfo& log_level_info) const { - auto version_info = make_unique(); - - version_info->set_major_version(piscsi_major_version); - version_info->set_minor_version(piscsi_minor_version); - version_info->set_patch_version(piscsi_patch_version); - - result.set_status(true); - - return version_info; -} - -unique_ptr PiscsiResponse::GetLogLevelInfo(PbResult& result, const string& current_log_level) const -{ - auto log_level_info = make_unique(); - - for (const auto& log_level : log_levels) { - log_level_info->add_log_levels(log_level); + for (const auto& log_level : spdlog::level::level_string_views) { + log_level_info.add_log_levels(log_level.data()); } - log_level_info->set_current_log_level(current_log_level); - - result.set_status(true); - - return log_level_info; + log_level_info.set_current_log_level(spdlog::level::level_string_views[spdlog::get_level()].data()); } -unique_ptr PiscsiResponse::GetNetworkInterfacesInfo(PbResult& result) const +void PiscsiResponse::GetNetworkInterfacesInfo(PbNetworkInterfacesInfo& network_interfaces_info) const { - auto network_interfaces_info = make_unique(); - - for (const auto& network_interface : device_factory.GetNetworkInterfaces()) { - network_interfaces_info->add_name(network_interface); + for (const auto& network_interface : GetNetworkInterfaces()) { + network_interfaces_info.add_name(network_interface); } - - result.set_status(true); - - return network_interfaces_info; } -unique_ptr PiscsiResponse::GetMappingInfo(PbResult& result) const +void PiscsiResponse::GetMappingInfo(PbMappingInfo& mapping_info) const { - auto mapping_info = make_unique(); - for (const auto& [name, type] : device_factory.GetExtensionMapping()) { - (*mapping_info->mutable_mapping())[name] = type; + (*mapping_info.mutable_mapping())[name] = type; } - - result.set_status(true); - - return mapping_info; } -unique_ptr PiscsiResponse::GetOperationInfo(PbResult& result, int depth) const +void PiscsiResponse::GetOperationInfo(PbOperationInfo& operation_info, int depth) const { - auto operation_info = make_unique(); + auto operation = CreateOperation(operation_info, ATTACH, "Attach device, device-specific parameters are required"); + AddOperationParameter(*operation, "name", "Image file name in case of a mass storage device"); + AddOperationParameter(*operation, "interface", "Comma-separated prioritized network interface list"); + AddOperationParameter(*operation, "inet", "IP address and netmask of the network bridge"); + AddOperationParameter(*operation, "cmd", "Print command for the printer device"); - auto operation = CreateOperation(*operation_info, ATTACH, "Attach device, device-specific parameters are required"); - AddOperationParameter(*operation, "name", "Image file name in case of a mass storage device").release(); - AddOperationParameter(*operation, "interface", "Comma-separated prioritized network interface list").release(); - AddOperationParameter(*operation, "inet", "IP address and netmask of the network bridge").release(); - AddOperationParameter(*operation, "cmd", "Print command for the printer device").release(); - operation.release(); + CreateOperation(operation_info, DETACH, "Detach device, device-specific parameters are required"); - CreateOperation(*operation_info, DETACH, "Detach device, device-specific parameters are required").release(); + CreateOperation(operation_info, DETACH_ALL, "Detach all devices"); - CreateOperation(*operation_info, DETACH_ALL, "Detach all devices").release(); + CreateOperation(operation_info, START, "Start device, device-specific parameters are required"); - CreateOperation(*operation_info, START, "Start device, device-specific parameters are required").release(); + CreateOperation(operation_info, STOP, "Stop device, device-specific parameters are required"); - CreateOperation(*operation_info, STOP, "Stop device, device-specific parameters are required").release(); + operation = CreateOperation(operation_info, INSERT, "Insert medium, device-specific parameters are required"); + AddOperationParameter(*operation, "file", "Image file name", "", true); - operation = CreateOperation(*operation_info, INSERT, "Insert medium, device-specific parameters are required"); - AddOperationParameter(*operation, "file", "Image file name", "", true).release(); - operation.release(); + CreateOperation(operation_info, EJECT, "Eject medium, device-specific parameters are required"); - CreateOperation(*operation_info, EJECT, "Eject medium, device-specific parameters are required").release(); + CreateOperation(operation_info, PROTECT, "Protect medium, device-specific parameters are required"); - CreateOperation(*operation_info, PROTECT, "Protect medium, device-specific parameters are required").release(); + CreateOperation(operation_info, UNPROTECT, "Unprotect medium, device-specific parameters are required"); - CreateOperation(*operation_info, UNPROTECT, "Unprotect medium, device-specific parameters are required").release(); - - operation = CreateOperation(*operation_info, SERVER_INFO, "Get piscsi server information"); + operation = CreateOperation(operation_info, SERVER_INFO, "Get piscsi server information"); if (depth) { - AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names").release(); + AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names"); } - AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names").release(); - operation.release(); + AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names"); - CreateOperation(*operation_info, VERSION_INFO, "Get piscsi server version").release(); + CreateOperation(operation_info, VERSION_INFO, "Get piscsi server version"); - CreateOperation(*operation_info, DEVICES_INFO, "Get information on attached devices").release(); + CreateOperation(operation_info, DEVICES_INFO, "Get information on attached devices"); - CreateOperation(*operation_info, DEVICE_TYPES_INFO, "Get device properties by device type").release(); + CreateOperation(operation_info, DEVICE_TYPES_INFO, "Get device properties by device type"); - operation = CreateOperation(*operation_info, DEFAULT_IMAGE_FILES_INFO, "Get information on available image files"); + operation = CreateOperation(operation_info, DEFAULT_IMAGE_FILES_INFO, "Get information on available image files"); if (depth) { - AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names").release(); + AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names"); } - AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names").release(); - operation.release(); + AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names"); - operation = CreateOperation(*operation_info, IMAGE_FILE_INFO, "Get information on image file"); - AddOperationParameter(*operation, "file", "Image file name", "", true).release(); - operation.release(); + operation = CreateOperation(operation_info, IMAGE_FILE_INFO, "Get information on image file"); + AddOperationParameter(*operation, "file", "Image file name", "", true); - CreateOperation(*operation_info, LOG_LEVEL_INFO, "Get log level information").release(); + CreateOperation(operation_info, LOG_LEVEL_INFO, "Get log level information"); - CreateOperation(*operation_info, NETWORK_INTERFACES_INFO, "Get the available network interfaces").release(); + CreateOperation(operation_info, NETWORK_INTERFACES_INFO, "Get the available network interfaces"); - CreateOperation(*operation_info, MAPPING_INFO, "Get mapping of extensions to device types").release(); + CreateOperation(operation_info, MAPPING_INFO, "Get mapping of extensions to device types"); - CreateOperation(*operation_info, RESERVED_IDS_INFO, "Get list of reserved device IDs").release(); + CreateOperation(operation_info, RESERVED_IDS_INFO, "Get list of reserved device IDs"); - operation = CreateOperation(*operation_info, DEFAULT_FOLDER, "Set default image file folder"); - AddOperationParameter(*operation, "folder", "Default image file folder name", "", true).release(); - operation.release(); + operation = CreateOperation(operation_info, DEFAULT_FOLDER, "Set default image file folder"); + AddOperationParameter(*operation, "folder", "Default image file folder name", "", true); - operation = CreateOperation(*operation_info, LOG_LEVEL, "Set log level"); - AddOperationParameter(*operation, "level", "New log level", "", true).release(); - operation.release(); + operation = CreateOperation(operation_info, LOG_LEVEL, "Set log level"); + AddOperationParameter(*operation, "level", "New log level", "", true); - operation = CreateOperation(*operation_info, RESERVE_IDS, "Reserve device IDs"); - AddOperationParameter(*operation, "ids", "Comma-separated device ID list", "", true).release(); - operation.release(); + operation = CreateOperation(operation_info, RESERVE_IDS, "Reserve device IDs"); + AddOperationParameter(*operation, "ids", "Comma-separated device ID list", "", true); - operation = CreateOperation(*operation_info, SHUT_DOWN, "Shut down or reboot"); - auto parameter = AddOperationParameter(*operation, "mode", "Shutdown mode", "", true).release(); - parameter->add_permitted_values("piscsi"); - // System shutdown/reboot requires root permissions - if (!getuid()) { - parameter->add_permitted_values("system"); - parameter->add_permitted_values("reboot"); + operation = CreateOperation(operation_info, SHUT_DOWN, "Shut down or reboot"); + if (getuid()) { + AddOperationParameter(*operation, "mode", "Shutdown mode", "", true, { "rascsi" } ); + } + else { + // System shutdown/reboot requires root permissions + AddOperationParameter(*operation, "mode", "Shutdown mode", "", true, { "rascsi", "system", "reboot" } ); } - operation.release(); - operation = CreateOperation(*operation_info, CREATE_IMAGE, "Create an image file"); - AddOperationParameter(*operation, "file", "Image file name", "", true).release(); - AddOperationParameter(*operation, "size", "Image file size in bytes", "", true).release(); - parameter = AddOperationParameter(*operation, "read_only", "Read-only flag", "false").release(); - parameter->add_permitted_values("true"); - parameter->add_permitted_values("false"); - operation.release(); + operation = CreateOperation(operation_info, CREATE_IMAGE, "Create an image file"); + AddOperationParameter(*operation, "file", "Image file name", "", true); + AddOperationParameter(*operation, "size", "Image file size in bytes", "", true); + AddOperationParameter(*operation, "read_only", "Read-only flag", "false", false, { "true", "false" } ); - operation = CreateOperation(*operation_info, DELETE_IMAGE, "Delete image file"); - AddOperationParameter(*operation, "file", "Image file name", "", true).release(); - operation.release(); + operation = CreateOperation(operation_info, DELETE_IMAGE, "Delete image file"); + AddOperationParameter(*operation, "file", "Image file name", "", true); - operation = CreateOperation(*operation_info, RENAME_IMAGE, "Rename image file"); - AddOperationParameter(*operation, "from", "Source image file name", "", true).release(); - AddOperationParameter(*operation, "to", "Destination image file name", "", true).release(); - operation.release(); + operation = CreateOperation(operation_info, RENAME_IMAGE, "Rename image file"); + AddOperationParameter(*operation, "from", "Source image file name", "", true); + AddOperationParameter(*operation, "to", "Destination image file name", "", true); - operation = CreateOperation(*operation_info, COPY_IMAGE, "Copy image file"); - AddOperationParameter(*operation, "from", "Source image file name", "", true).release(); - AddOperationParameter(*operation, "to", "Destination image file name", "", true).release(); - parameter = AddOperationParameter(*operation, "read_only", "Read-only flag", "false").release(); - parameter->add_permitted_values("true"); - parameter->add_permitted_values("false"); - operation.release(); + operation = CreateOperation(operation_info, COPY_IMAGE, "Copy image file"); + AddOperationParameter(*operation, "from", "Source image file name", "", true); + AddOperationParameter(*operation, "to", "Destination image file name", "", true); + AddOperationParameter(*operation, "read_only", "Read-only flag", "false", false, { "true", "false" } ); - operation = CreateOperation(*operation_info, PROTECT_IMAGE, "Write-protect image file"); - AddOperationParameter(*operation, "file", "Image file name", "", true).release(); - operation.release(); + operation = CreateOperation(operation_info, PROTECT_IMAGE, "Write-protect image file"); + AddOperationParameter(*operation, "file", "Image file name", "", true); - operation = CreateOperation(*operation_info, UNPROTECT_IMAGE, "Make image file writable"); - AddOperationParameter(*operation, "file", "Image file name", "", true).release(); - operation.release(); + operation = CreateOperation(operation_info, UNPROTECT_IMAGE, "Make image file writable"); + AddOperationParameter(*operation, "file", "Image file name", "", true); - operation = CreateOperation(*operation_info, CHECK_AUTHENTICATION, "Check whether an authentication token is valid"); - AddOperationParameter(*operation, "token", "Authentication token to be checked", "", true).release(); - operation.release(); + operation = CreateOperation(operation_info, CHECK_AUTHENTICATION, "Check whether an authentication token is valid"); + AddOperationParameter(*operation, "token", "Authentication token to be checked", "", true); - CreateOperation(*operation_info, OPERATION_INFO, "Get operation meta data").release(); - - result.set_status(true); - - return operation_info; + CreateOperation(operation_info, OPERATION_INFO, "Get operation meta data"); } -unique_ptr PiscsiResponse::CreateOperation(PbOperationInfo& operation_info, const PbOperation& operation, +// This method returns a raw pointer because protobuf does not have support for smart pointers +PbOperationMetaData *PiscsiResponse::CreateOperation(PbOperationInfo& operation_info, const PbOperation& operation, const string& description) const { - auto meta_data = make_unique(); - meta_data->set_server_side_name(PbOperation_Name(operation)); - meta_data->set_description(description); + PbOperationMetaData meta_data; + meta_data.set_server_side_name(PbOperation_Name(operation)); + meta_data.set_description(description); int ordinal = PbOperation_descriptor()->FindValueByName(PbOperation_Name(operation))->index(); - (*operation_info.mutable_operations())[ordinal] = *meta_data.release(); - return unique_ptr(&(*operation_info.mutable_operations())[ordinal]); + (*operation_info.mutable_operations())[ordinal] = meta_data; + return &(*operation_info.mutable_operations())[ordinal]; } -unique_ptr PiscsiResponse::AddOperationParameter(PbOperationMetaData& meta_data, - const string& name, const string& description, const string& default_value, bool is_mandatory) const +void PiscsiResponse::AddOperationParameter(PbOperationMetaData& meta_data, const string& name, + const string& description, const string& default_value, bool is_mandatory, + const vector& permitted_values) const { - auto parameter = unique_ptr(meta_data.add_parameters()); + auto parameter = meta_data.add_parameters(); parameter->set_name(name); parameter->set_description(description); parameter->set_default_value(default_value); parameter->set_is_mandatory(is_mandatory); - - return parameter; + for (const auto& permitted_value : permitted_values) { + parameter->add_permitted_values(permitted_value); + } } set PiscsiResponse::MatchDevices(const unordered_set>& devices, PbResult& result, @@ -503,7 +408,7 @@ set PiscsiResponse::MatchDevices(const unordered_setGetId() == device.id() && d->GetLun() == device.unit()) { - id_sets.insert(make_pair(device.id(), device.unit())); + id_sets.insert({ device.id(), device.unit() }); has_device = true; break; } @@ -513,7 +418,7 @@ set PiscsiResponse::MatchDevices(const unordered_set PiscsiResponse::MatchDevices(const unordered_setd_type != DT_REG && dir->d_type != DT_DIR && dir->d_type != DT_LNK && dir->d_type != DT_BLK) - || dir->d_name[0] == '.') { - return ""; + if (path.filename().string().starts_with(".")) { + return false; } - const string filename = folder + "/" + dir->d_name; + filesystem::path p(path); - const bool file_exists = exists(path(filename)); - - // filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle files of more than 2 GiB - struct stat st; - stat(filename.c_str(), &st); - if (dir->d_type == DT_REG && file_exists && !st.st_size) { - LOGWARN("File '%s' in image folder '%s' is empty", dir->d_name, folder.c_str()) - return ""; + // Follow symlink + if (is_symlink(p)) { + p = read_symlink(p); + if (!exists(p)) { + spdlog::warn("Image file symlink '" + path.string() + "' is broken"); + return false; + } } - if (dir->d_type == DT_LNK && !file_exists) { - LOGWARN("Symlink '%s' in image folder '%s' is broken", dir->d_name, folder.c_str()) - return ""; + if (is_directory(p) || (is_other(p) && !is_block_file(p))) { + return false; } - return filename; + if (!is_block_file(p) && file_size(p) < 256) { + spdlog::warn("Image file '" + p.string() + "' is invalid"); + return false; + } + + return true; +} + +bool PiscsiResponse::FilterMatches(const string& input, string_view pattern_lower) +{ + if (!pattern_lower.empty()) { + string name_lower; + ranges::transform(input, back_inserter(name_lower), ::tolower); + + if (name_lower.find(pattern_lower) == string::npos) { + return false; + } + } + + return true; } diff --git a/cpp/piscsi/piscsi_response.h b/cpp/piscsi/piscsi_response.h index 16a9b95f..549ea905 100644 --- a/cpp/piscsi/piscsi_response.h +++ b/cpp/piscsi/piscsi_response.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -12,52 +12,51 @@ #include "devices/device_factory.h" #include "devices/primary_device.h" #include "generated/piscsi_interface.pb.h" -#include -#include #include +#include using namespace std; +using namespace filesystem; using namespace piscsi_interface; class PiscsiResponse { - using id_set = pair; - public: PiscsiResponse() = default; ~PiscsiResponse() = default; bool GetImageFile(PbImageFile&, const string&, const string&) const; - unique_ptr GetAvailableImages(PbResult&, const string&, const string&, const string&, int) const; - unique_ptr GetReservedIds(PbResult&, const unordered_set&) const; + void GetImageFilesInfo(PbImageFilesInfo&, const string&, const string&, const string&, int) const; + void GetReservedIds(PbReservedIdsInfo&, const unordered_set&) const; void GetDevices(const unordered_set>&, PbServerInfo&, const string&) const; void GetDevicesInfo(const unordered_set>&, PbResult&, const PbCommand&, const string&) const; - unique_ptr GetDeviceTypesInfo(PbResult&) const; - unique_ptr GetVersionInfo(PbResult&) const; - unique_ptr GetServerInfo(const unordered_set>&, PbResult&, const unordered_set&, - const string&, const string&, const string&, const string&, int) const; - unique_ptr GetNetworkInterfacesInfo(PbResult&) const; - unique_ptr GetMappingInfo(PbResult&) const; - unique_ptr GetLogLevelInfo(PbResult&, const string&) const; - unique_ptr GetOperationInfo(PbResult&, int) const; + void GetDeviceTypesInfo(PbDeviceTypesInfo&) const; + void GetVersionInfo(PbVersionInfo&) const; + void GetServerInfo(PbServerInfo&, const unordered_set>&, const unordered_set&, + const string&, const string&, const string&, int) const; + void GetNetworkInterfacesInfo(PbNetworkInterfacesInfo&) const; + void GetMappingInfo(PbMappingInfo&) const; + void GetLogLevelInfo(PbLogLevelInfo&) const; + void GetOperationInfo(PbOperationInfo&, int) const; private: - DeviceFactory device_factory; + inline static const vector EMPTY_VECTOR; - const inline static array log_levels = { "trace", "debug", "info", "warn", "err", "off" }; + const DeviceFactory device_factory; - unique_ptr GetDeviceProperties(const Device&) const; + void GetDeviceProperties(const Device&, PbDeviceProperties&) const; void GetDevice(const Device&, PbDevice&, const string&) const; - void GetAllDeviceTypeProperties(PbDeviceTypesInfo&) const; void GetDeviceTypeProperties(PbDeviceTypesInfo&, PbDeviceType) const; - void GetAvailableImages(PbImageFilesInfo&, const string&, const string&, const string&, const string&, int) const; - void GetAvailableImages(PbResult& result, PbServerInfo&, const string&, const string&, const string&, int) const; - unique_ptr CreateOperation(PbOperationInfo&, const PbOperation&, const string&) const; - unique_ptr AddOperationParameter(PbOperationMetaData&, const string&, const string&, - const string& = "", bool = false) const; + void GetAvailableImages(PbImageFilesInfo&, const string&, const string&, const string&, int) const; + void GetAvailableImages(PbServerInfo&, const string&, const string&, const string&, int) const; + PbOperationMetaData *CreateOperation(PbOperationInfo&, const PbOperation&, const string&) const; + void AddOperationParameter(PbOperationMetaData&, const string&, const string&, + const string& = "", bool = false, const vector& = EMPTY_VECTOR) const; set MatchDevices(const unordered_set>&, PbResult&, const PbCommand&) const; - static string GetNextImageFile(const dirent *, const string&); + static bool ValidateImageFile(const path&); + + static bool FilterMatches(const string&, string_view); }; diff --git a/cpp/piscsi/piscsi_service.cpp b/cpp/piscsi/piscsi_service.cpp index 661b5f92..4f842def 100644 --- a/cpp/piscsi/piscsi_service.cpp +++ b/cpp/piscsi/piscsi_service.cpp @@ -3,140 +3,114 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "shared/log.h" #include "shared/piscsi_util.h" -#include "shared/protobuf_serializer.h" #include "shared/piscsi_exceptions.h" #include "command_context.h" -#include "localizer.h" #include "piscsi_service.h" +#include +#include #include +#include #include +#include -using namespace piscsi_interface; using namespace piscsi_util; -void PiscsiService::Cleanup() const +string PiscsiService::Init(const callback& cb, int port) { - running = false; + assert(service_socket == -1); - if (service_socket != -1) { - close(service_socket); - } -} - -bool PiscsiService::Init(const callback& cb, int port) -{ if (port <= 0 || port > 65535) { - return false; + return "Invalid port number " + to_string(port); } - // Create socket for monitor - sockaddr_in server = {}; service_socket = socket(PF_INET, SOCK_STREAM, 0); if (service_socket == -1) { - LOGERROR("Unable to create socket") - return false; + return "Unable to create service socket: " + string(strerror(errno)); } + if (const int yes = 1; setsockopt(service_socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) { + Stop(); + return "Can't reuse address"; + } + + sockaddr_in server = {}; server.sin_family = PF_INET; server.sin_port = htons((uint16_t)port); - server.sin_addr.s_addr = htonl(INADDR_ANY); - - // Allow address reuse - if (int yes = 1; setsockopt(service_socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) { - return false; + server.sin_addr.s_addr = INADDR_ANY; + if (bind(service_socket, reinterpret_cast(&server), sizeof(sockaddr_in)) < 0) { //NOSONAR bit_cast is not supported by the bullseye compiler + Stop(); + return "Port " + to_string(port) + " is in use, is piscsi already running?"; } - signal(SIGPIPE, SIG_IGN); - - if (bind(service_socket, (sockaddr *)&server, sizeof(sockaddr_in)) < 0) { - cerr << "Error: Port " << port << " is in use, is piscsi or rascsi already running?" << endl; - return false; + if (listen(service_socket, 2) == -1) { + Stop(); + return "Can't listen to service socket: " + string(strerror(errno)); } execute = cb; - monthread = thread(&PiscsiService::Execute, this); - monthread.detach(); + return ""; +} - // Interrupt handler settings - return signal(SIGINT, KillHandler) != SIG_ERR && signal(SIGHUP, KillHandler) != SIG_ERR - && signal(SIGTERM, KillHandler) != SIG_ERR; +void PiscsiService::Start() +{ + assert(service_socket != -1); + + service_thread = jthread([this] () { Execute(); } ); +} + +void PiscsiService::Stop() +{ + assert(service_socket != -1); + + shutdown(service_socket, SHUT_RD); + close(service_socket); + + service_socket = -1; } void PiscsiService::Execute() const { #ifdef __linux__ - // Scheduler Settings - sched_param schedparam; - schedparam.sched_priority = 0; + // Run this thread with very low priority + sched_param schedparam = { .sched_priority = 0 }; sched_setscheduler(0, SCHED_IDLE, &schedparam); #endif - // Set the affinity to a specific processor core - FixCpu(2); - - // Wait for the execution to start - const timespec ts = { .tv_sec = 0, .tv_nsec = 1000}; - while (!running) { - nanosleep(&ts, nullptr); - } - - // Set up the monitor socket to receive commands - listen(service_socket, 1); - - while (true) { - CommandContext context; - - try { - PbCommand command = ReadCommand(context); - if (context.IsValid()) { - execute(context, command); - } + // TODO Accept more than one command instead of closing the socket after a single command + while (service_socket != -1) { + const int fd = accept(service_socket, nullptr, nullptr); + if (fd != -1) { + ExecuteCommand(fd); + close(fd); } - catch(const io_exception& e) { - LOGWARN("%s", e.what()) - - // Fall through - } - - context.Cleanup(); } } -PbCommand PiscsiService::ReadCommand(CommandContext& context) const +void PiscsiService::ExecuteCommand(int fd) const { - // Wait for connection - sockaddr client = {}; - socklen_t socklen = sizeof(client); - const int fd = accept(service_socket, &client, &socklen); - if (fd == -1) { - throw io_exception("accept() failed"); + CommandContext context(fd); + try { + if (context.ReadCommand()) { + execute(context); + } } + catch(const io_exception& e) { + spdlog::warn(e.what()); - PbCommand command; - - // Read magic string - vector magic(6); - const size_t bytes_read = context.GetSerializer().ReadBytes(fd, magic); - if (!bytes_read) { - return command; + // Try to return an error message (this may fail if the exception was caused when returning the actual result) + PbResult result; + result.set_msg(e.what()); + try { + context.WriteResult(result); + } + catch(const io_exception&) { //NOSONAR Not handled on purpose + // Ignore + } } - - if (bytes_read != magic.size() || memcmp(magic.data(), "RASCSI", magic.size())) { - throw io_exception("Invalid magic"); - } - - // Fetch the command - context.GetSerializer().DeserializeMessage(fd, command); - - context.SetFd(fd); - - return command; } - diff --git a/cpp/piscsi/piscsi_service.h b/cpp/piscsi/piscsi_service.h index a880c6c8..03a956d7 100644 --- a/cpp/piscsi/piscsi_service.h +++ b/cpp/piscsi/piscsi_service.h @@ -3,49 +3,42 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once -#include "generated/piscsi_interface.pb.h" #include #include +#include class CommandContext; using namespace std; -using namespace piscsi_interface; class PiscsiService { - using callback = function; - - callback execute; - - int service_socket = -1; - - thread monthread; - - static inline volatile bool running = false; + using callback = function; public: PiscsiService() = default; ~PiscsiService() = default; - bool Init(const callback&, int); - void Cleanup() const; - - bool IsRunning() const { return running; } - void SetRunning(bool b) const { running = b; } + string Init(const callback&, int); + void Start(); + void Stop(); + bool IsRunning() const { return service_socket != -1 && service_thread.joinable(); } private: void Execute() const; + void ExecuteCommand(int) const; - PbCommand ReadCommand(CommandContext&) const; + callback execute; - static void KillHandler(int) { running = false; } + jthread service_thread; + + int service_socket = -1; }; diff --git a/cpp/piscsi_interface.proto b/cpp/piscsi_interface.proto index d99ead12..1a1be45c 100644 --- a/cpp/piscsi_interface.proto +++ b/cpp/piscsi_interface.proto @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -23,7 +23,7 @@ package piscsi_interface; // The available device types enum PbDeviceType { UNDEFINED = 0; - // Non-removable SASI drive, not supported anymore + // Non-removable SASI drive, not supported anymore but must not be removed because of backwards compatibility SAHD = 1 [deprecated = true]; // Non-removable SCSI drive SCHD = 2; diff --git a/cpp/scsictl/scsictl_commands.cpp b/cpp/scsictl/scsictl_commands.cpp index 99adb09e..8cb55f60 100644 --- a/cpp/scsictl/scsictl_commands.cpp +++ b/cpp/scsictl/scsictl_commands.cpp @@ -3,26 +3,31 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- +#include "shared/network_util.h" #include "shared/piscsi_util.h" #include "shared/protobuf_util.h" #include "shared/piscsi_exceptions.h" #include "scsictl_commands.h" +#include +#include +#include #include #include #include -#include +#include using namespace std; using namespace piscsi_interface; +using namespace network_util; using namespace piscsi_util; using namespace protobuf_util; -bool ScsictlCommands::Execute(const string& log_level, const string& default_folder, const string& reserved_ids, - const string& image_params, const string& filename) +bool ScsictlCommands::Execute(string_view log_level, string_view default_folder, string_view reserved_ids, + string_view image_params, string_view filename) { switch(command.operation()) { case LOG_LEVEL: @@ -79,6 +84,9 @@ bool ScsictlCommands::Execute(const string& log_level, const string& default_fol case OPERATION_INFO: return CommandOperationInfo(); + case NO_OPERATION: + return false; + default: return SendCommand(); } @@ -112,8 +120,8 @@ bool ScsictlCommands::SendCommand() throw io_exception("Can't write magic"); } - serializer.SerializeMessage(fd, command); - serializer.DeserializeMessage(fd, result); + SerializeMessage(fd, command); + DeserializeMessage(fd, result); close(fd); @@ -137,27 +145,23 @@ bool ScsictlCommands::CommandDevicesInfo() return true; } -bool ScsictlCommands::CommandLogLevel(const string& log_level) +bool ScsictlCommands::CommandLogLevel(string_view log_level) { SetParam(command, "level", log_level); return SendCommand(); } -bool ScsictlCommands::CommandReserveIds(const string& reserved_ids) +bool ScsictlCommands::CommandReserveIds(string_view reserved_ids) { SetParam(command, "ids", reserved_ids); return SendCommand(); } -bool ScsictlCommands::CommandCreateImage(const string& image_params) +bool ScsictlCommands::CommandCreateImage(string_view image_params) { - if (const size_t separator_pos = image_params.find(COMPONENT_SEPARATOR); separator_pos != string::npos) { - SetParam(command, "file", string_view(image_params).substr(0, separator_pos)); - SetParam(command, "size", string_view(image_params).substr(separator_pos + 1)); - } - else { + if (!EvaluateParams(image_params, "file", "size")) { cerr << "Error: Invalid file descriptor '" << image_params << "', format is NAME:SIZE" << endl; return false; @@ -168,20 +172,16 @@ bool ScsictlCommands::CommandCreateImage(const string& image_params) return SendCommand(); } -bool ScsictlCommands::CommandDeleteImage(const string& filename) +bool ScsictlCommands::CommandDeleteImage(string_view filename) { SetParam(command, "file", filename); return SendCommand(); } -bool ScsictlCommands::CommandRenameImage(const string& image_params) +bool ScsictlCommands::CommandRenameImage(string_view image_params) { - if (const size_t separator_pos = image_params.find(COMPONENT_SEPARATOR); separator_pos != string::npos) { - SetParam(command, "from", string_view(image_params).substr(0, separator_pos)); - SetParam(command, "to", string_view(image_params).substr(separator_pos + 1)); - } - else { + if (!EvaluateParams(image_params, "from", "to")) { cerr << "Error: Invalid file descriptor '" << image_params << "', format is CURRENT_NAME:NEW_NAME" << endl; return false; @@ -190,13 +190,9 @@ bool ScsictlCommands::CommandRenameImage(const string& image_params) return SendCommand(); } -bool ScsictlCommands::CommandCopyImage(const string& image_params) +bool ScsictlCommands::CommandCopyImage(string_view image_params) { - if (const size_t separator_pos = image_params.find(COMPONENT_SEPARATOR); separator_pos != string::npos) { - SetParam(command, "from", string_view(image_params).substr(0, separator_pos)); - SetParam(command, "to", string_view(image_params).substr(separator_pos + 1)); - } - else { + if (!EvaluateParams(image_params, "from", "to")) { cerr << "Error: Invalid file descriptor '" << image_params << "', format is CURRENT_NAME:NEW_NAME" << endl; return false; @@ -205,7 +201,7 @@ bool ScsictlCommands::CommandCopyImage(const string& image_params) return SendCommand(); } -bool ScsictlCommands::CommandDefaultImageFolder(const string& folder) +bool ScsictlCommands::CommandDefaultImageFolder(string_view folder) { SetParam(command, "folder", folder); @@ -259,8 +255,8 @@ bool ScsictlCommands::CommandServerInfo() cout << scsictl_display.DisplayOperationInfo(server_info.operation_info()); if (server_info.devices_info().devices_size()) { - list sorted_devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() }; - sorted_devices.sort([](const auto& a, const auto& b) { return a.id() < b.id() || a.unit() < b.unit(); }); + vector sorted_devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() }; + ranges::sort(sorted_devices, [](const auto& a, const auto& b) { return a.id() < b.id() || a.unit() < b.unit(); }); cout << "Attached devices:\n"; @@ -283,7 +279,7 @@ bool ScsictlCommands::CommandDefaultImageFilesInfo() return true; } -bool ScsictlCommands::CommandImageFileInfo(const string& filename) +bool ScsictlCommands::CommandImageFileInfo(string_view filename) { SetParam(command, "file", filename); @@ -339,15 +335,12 @@ bool ScsictlCommands::CommandOperationInfo() return true; } -bool ScsictlCommands::ResolveHostName(const string& host, sockaddr_in *addr) +bool ScsictlCommands::EvaluateParams(string_view image_params, const string& key1, const string& key2) { - addrinfo hints = {}; - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; + if (const auto& components = Split(string(image_params), COMPONENT_SEPARATOR, 2); components.size() == 2) { + SetParam(command, key1, components[0]); + SetParam(command, key2, components[1]); - if (addrinfo *result; !getaddrinfo(host.c_str(), nullptr, &hints, &result)) { - *addr = *(sockaddr_in *)(result->ai_addr); - freeaddrinfo(result); return true; } diff --git a/cpp/scsictl/scsictl_commands.h b/cpp/scsictl/scsictl_commands.h index b9e80963..11aa51fa 100644 --- a/cpp/scsictl/scsictl_commands.h +++ b/cpp/scsictl/scsictl_commands.h @@ -3,13 +3,12 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once -#include "shared/protobuf_serializer.h" #include "generated/piscsi_interface.pb.h" #include "scsictl_display.h" #include @@ -26,40 +25,38 @@ public: : command(command), hostname(hostname), port(port) {} ~ScsictlCommands() = default; - bool Execute(const string&, const string&, const string&, const string&, const string&); + bool Execute(string_view, string_view, string_view, string_view, string_view); bool CommandDevicesInfo(); private: - bool CommandLogLevel(const string&); - bool CommandReserveIds(const string&); - bool CommandCreateImage(const string&); - bool CommandDeleteImage(const string&); - bool CommandRenameImage(const string&); - bool CommandCopyImage(const string&); - bool CommandDefaultImageFolder(const string&); + bool CommandLogLevel(string_view); + bool CommandReserveIds(string_view); + bool CommandCreateImage(string_view); + bool CommandDeleteImage(string_view); + bool CommandRenameImage(string_view); + bool CommandCopyImage(string_view); + bool CommandDefaultImageFolder(string_view); bool CommandDeviceInfo(); bool CommandDeviceTypesInfo(); bool CommandVersionInfo(); bool CommandServerInfo(); bool CommandDefaultImageFilesInfo(); - bool CommandImageFileInfo(const string&); + bool CommandImageFileInfo(string_view); bool CommandNetworkInterfacesInfo(); bool CommandLogLevelInfo(); bool CommandReservedIdsInfo(); bool CommandMappingInfo(); bool CommandOperationInfo(); bool SendCommand(); + bool EvaluateParams(string_view, const string&, const string&); - static bool ResolveHostName(const string&, sockaddr_in *); - - ProtobufSerializer serializer; PbCommand& command; string hostname; int port; PbResult result; - ScsictlDisplay scsictl_display; + [[no_unique_address]] const ScsictlDisplay scsictl_display; }; diff --git a/cpp/scsictl/scsictl_core.cpp b/cpp/scsictl/scsictl_core.cpp index db2dea62..ff24d455 100644 --- a/cpp/scsictl/scsictl_core.cpp +++ b/cpp/scsictl/scsictl_core.cpp @@ -9,12 +9,12 @@ // //--------------------------------------------------------------------------- +#include "controllers/controller_manager.h" #include "controllers/scsi_controller.h" #include "shared/piscsi_util.h" #include "shared/protobuf_util.h" #include "shared/piscsi_exceptions.h" #include "shared/piscsi_version.h" -#include "generated/piscsi_interface.pb.h" #include "scsictl/scsictl_parser.h" #include "scsictl/scsictl_commands.h" #include "scsictl/scsictl_core.h" @@ -30,27 +30,27 @@ using namespace protobuf_util; void ScsiCtl::Banner(const vector& args) const { if (args.size() < 2) { - cout << piscsi_util::Banner("(Controller App)"); - - cout << "\nUsage: " << args[0] << " -i ID[:LUN] [-c CMD] [-C FILE] [-t TYPE] [-b BLOCK_SIZE] [-n NAME] [-f FILE|PARAM] "; - cout << "[-F IMAGE_FOLDER] [-L LOG_LEVEL] [-h HOST] [-p PORT] [-r RESERVED_IDS] "; - cout << "[-C FILENAME:FILESIZE] [-d FILENAME] [-w FILENAME] [-R CURRENT_NAME:NEW_NAME] "; - cout << "[-x CURRENT_NAME:NEW_NAME] [-z LOCALE] "; - cout << "[-e] [-E FILENAME] [-D] [-I] [-l] [-m] [o] [-O] [-P] [-s] [-v] [-V] [-y] [-X]\n"; - cout << " where ID[:LUN] ID := {0-7}, LUN := {0-31}, default is 0\n"; - cout << " CMD := {attach|detach|insert|eject|protect|unprotect|show}\n"; - cout << " TYPE := {schd|scrm|sccd|scmo|scbr|scdp} or convenience type {hd|rm|mo|cd|bridge|daynaport}\n"; - cout << " BLOCK_SIZE := {512|1024|2048|4096) bytes per hard disk drive block\n"; - cout << " NAME := name of device to attach (VENDOR:PRODUCT:REVISION)\n"; - cout << " FILE|PARAM := image file path or device-specific parameter\n"; - cout << " IMAGE_FOLDER := default location for image files, default is '~/images'\n"; - cout << " HOST := piscsi host to connect to, default is 'localhost'\n"; - cout << " PORT := piscsi port to connect to, default is 6868\n"; - cout << " RESERVED_IDS := comma-separated list of IDs to reserve\n"; - cout << " LOG_LEVEL := log level {trace|debug|info|warn|err|off}, default is 'info'\n"; - cout << " If CMD is 'attach' or 'insert' the FILE parameter is required.\n"; - cout << "Usage: " << args[0] << " -l\n"; - cout << " Print device list.\n" << flush; + cout << piscsi_util::Banner("(Controller App)") + << "\nUsage: " << args[0] << " -i ID[:LUN] [-c CMD] [-C FILE] [-t TYPE] [-b BLOCK_SIZE] [-n NAME] [-f FILE|PARAM] " + << "[-F IMAGE_FOLDER] [-L LOG_LEVEL] [-h HOST] [-p PORT] [-r RESERVED_IDS] " + << "[-C FILENAME:FILESIZE] [-d FILENAME] [-w FILENAME] [-R CURRENT_NAME:NEW_NAME] " + << "[-x CURRENT_NAME:NEW_NAME] [-z LOCALE] " + << "[-e] [-E FILENAME] [-D] [-I] [-l] [-m] [o] [-O] [-P] [-s] [-v] [-V] [-y] [-X]\n" + << " where ID[:LUN] ID := {0-" << (ControllerManager::GetScsiIdMax() - 1) << "}," + << " LUN := {0-" << (ControllerManager::GetScsiLunMax() - 1) << "}, default is 0\n" + << " CMD := {attach|detach|insert|eject|protect|unprotect|show}\n" + << " TYPE := {schd|scrm|sccd|scmo|scbr|scdp} or convenience type {hd|rm|mo|cd|bridge|daynaport}\n" + << " BLOCK_SIZE := {512|1024|2048|4096) bytes per hard disk drive block\n" + << " NAME := name of device to attach (VENDOR:PRODUCT:REVISION)\n" + << " FILE|PARAM := image file path or device-specific parameter\n" + << " IMAGE_FOLDER := default location for image files, default is '~/images'\n" + << " HOST := piscsi host to connect to, default is 'localhost'\n" + << " PORT := piscsi port to connect to, default is 6868\n" + << " RESERVED_IDS := comma-separated list of IDs to reserve\n" + << " LOG_LEVEL := log level {trace|debug|info|warn|err|off}, default is 'info'\n" + << " If CMD is 'attach' or 'insert' the FILE parameter is required.\n" + << "Usage: " << args[0] << " -l\n" + << " Print device list.\n" << flush; exit(EXIT_SUCCESS); } @@ -77,10 +77,7 @@ int ScsiCtl::run(const vector& args) const string token; bool list = false; - const char *locale = setlocale(LC_MESSAGES, ""); - if (locale == nullptr || !strcmp(locale, "C")) { - locale = "en"; - } + string locale = GetLocale(); opterr = 1; int opt; @@ -88,7 +85,7 @@ int ScsiCtl::run(const vector& args) const "e::lmos::vDINOTVXa:b:c:d:f:h:i:n:p:r:t:x:z:C:E:F:L:P::R:")) != -1) { switch (opt) { case 'i': - if (const string error = SetIdAndLun(*device, optarg, ScsiController::LUN_MAX); !error.empty()) { + if (const string error = SetIdAndLun(*device, optarg); !error.empty()) { cerr << "Error: " << error << endl; exit(EXIT_FAILURE); } @@ -216,7 +213,7 @@ int ScsiCtl::run(const vector& args) const break; case 'v': - cout << "scsictl version: " << piscsi_get_version_string() << endl; + cout << "scsictl version: " << piscsi_get_version_string() << '\n'; exit(EXIT_SUCCESS); break; @@ -263,7 +260,7 @@ int ScsiCtl::run(const vector& args) const bool status; try { - // Listing devices is a special case (rasctl backwards compatibility) + // Listing devices is a special case (legacy rasctl backwards compatibility) if (list) { command.clear_devices(); command.set_operation(DEVICES_INFO); diff --git a/cpp/scsictl/scsictl_display.cpp b/cpp/scsictl/scsictl_display.cpp index 705fa6a2..b71782c6 100644 --- a/cpp/scsictl/scsictl_display.cpp +++ b/cpp/scsictl/scsictl_display.cpp @@ -3,26 +3,29 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- +#include "shared/piscsi_util.h" #include "shared/protobuf_util.h" -#include "generated/piscsi_interface.pb.h" #include "scsictl_display.h" #include -#include +#include +#include +#include #include using namespace std; using namespace piscsi_interface; +using namespace piscsi_util; using namespace protobuf_util; string ScsictlDisplay::DisplayDevicesInfo(const PbDevicesInfo& devices_info) const { ostringstream s; - const list& devices = { devices_info.devices().begin(), devices_info.devices().end() }; + const vector devices(devices_info.devices().begin(), devices_info.devices().end()); s << ListDevices(devices); @@ -50,51 +53,33 @@ string ScsictlDisplay::DisplayDeviceInfo(const PbDevice& pb_device) const s << " "; - bool hasProperty = false; + vector properties; if (pb_device.properties().read_only()) { - s << "read-only"; - hasProperty = true; + properties.emplace_back("read-only"); } if (pb_device.properties().protectable() && pb_device.status().protected_()) { - if (hasProperty) { - s << ", "; - } - s << "protected"; - hasProperty = true; + properties.emplace_back("protected"); } if (pb_device.properties().stoppable() && pb_device.status().stopped()) { - if (hasProperty) { - s << ", "; - } - s << "stopped"; - hasProperty = true; + properties.emplace_back("stopped"); } if (pb_device.properties().removable() && pb_device.status().removed()) { - if (hasProperty) { - s << ", "; - } - s << "removed"; - hasProperty = true; + properties.emplace_back("removed"); } if (pb_device.properties().lockable() && pb_device.status().locked()) { - if (hasProperty) { - s << ", "; - } - s << "locked"; + properties.emplace_back("locked"); } - if (hasProperty) { - s << " "; + if (!properties.empty()) { + s << Join(properties) << " "; } - DisplayParams(s, pb_device); - - s << '\n'; + s << DisplayParams(pb_device) << '\n'; return s.str(); } @@ -103,8 +88,8 @@ string ScsictlDisplay::DisplayVersionInfo(const PbVersionInfo& version_info) con { ostringstream s; - s << "piscsi server version: " << setw(2) << setfill('0') << version_info.major_version() << "." - << setw(2) << setfill('0') << version_info.minor_version(); + s << "piscsi server version: " << setfill('0') << setw(2) << version_info.major_version() << "." + << setw(2) << version_info.minor_version(); if (version_info.patch_version() > 0) { s << "." << version_info.patch_version(); @@ -149,7 +134,7 @@ string ScsictlDisplay::DisplayDeviceTypesInfo(const PbDeviceTypesInfo& device_ty const PbDeviceProperties& properties = device_type_info.properties(); - DisplayAttributes(s, properties); + s << DisplayAttributes(properties); if (properties.supports_file()) { s << "Image file support\n "; @@ -159,9 +144,9 @@ string ScsictlDisplay::DisplayDeviceTypesInfo(const PbDeviceTypesInfo& device_ty s << "Parameter support\n "; } - DisplayDefaultParameters(s, properties); + s << DisplayDefaultParameters(properties); - DisplayBlockSizes(s, properties); + s << DisplayBlockSizes(properties); } s << '\n'; @@ -174,20 +159,8 @@ string ScsictlDisplay::DisplayReservedIdsInfo(const PbReservedIdsInfo& reserved_ ostringstream s; if (reserved_ids_info.ids_size()) { - s << "Reserved device IDs: "; - - for (int i = 0; i < reserved_ids_info.ids_size(); i++) { - if(i) { - s << ", "; - } - - s << reserved_ids_info.ids(i); - } - - s << '\n'; - } - else { - s << "No reserved device IDs\n"; + const set sorted_ids(reserved_ids_info.ids().begin(), reserved_ids_info.ids().end()); + s << "Reserved device IDs: " << Join(sorted_ids) << '\n'; } return s.str(); @@ -219,12 +192,9 @@ string ScsictlDisplay::DisplayImageFilesInfo(const PbImageFilesInfo& image_files s << "Default image file folder: " << image_files_info.default_image_folder() << '\n'; s << "Supported folder depth: " << image_files_info.depth() << '\n'; - if (image_files_info.image_files().empty()) { - s << " No image files available\n"; - } - else { - list image_files = { image_files_info.image_files().begin(), image_files_info.image_files().end() }; - image_files.sort([](const auto& a, const auto& b) { return a.name() < b.name(); }); + if (!image_files_info.image_files().empty()) { + vector image_files(image_files_info.image_files().begin(), image_files_info.image_files().end()); + ranges::sort(image_files, [](const auto& a, const auto& b) { return a.name() < b.name(); }); s << "Available image files:\n"; for (const auto& image_file : image_files) { @@ -241,24 +211,8 @@ string ScsictlDisplay::DisplayNetworkInterfaces(const PbNetworkInterfacesInfo& n { ostringstream s; - s << "Available (up) network interfaces:\n"; - - const list sorted_interfaces = { network_interfaces_info.name().begin(), network_interfaces_info.name().end() }; - - bool isFirst = true; - for (const auto& interface : sorted_interfaces) { - if (!isFirst) { - s << ", "; - } - else { - s << " "; - } - - isFirst = false; - s << interface; - } - - s << '\n'; + const set> sorted_interfaces(network_interfaces_info.name().begin(), network_interfaces_info.name().end()); + s << "Available (up) network interfaces: " << Join(sorted_interfaces) << '\n'; return s.str(); } @@ -269,9 +223,8 @@ string ScsictlDisplay::DisplayMappingInfo(const PbMappingInfo& mapping_info) con s << "Supported image file extension to device type mappings:\n"; - const map> sorted_mappings = { mapping_info.mapping().begin(), mapping_info.mapping().end() }; - - for (const auto& [extension, type] : sorted_mappings) { + for (const map> sorted_mappings(mapping_info.mapping().begin(), mapping_info.mapping().end()); + const auto& [extension, type] : sorted_mappings) { s << " " << extension << "->" << PbDeviceType_Name(type) << '\n'; } @@ -282,7 +235,7 @@ string ScsictlDisplay::DisplayOperationInfo(const PbOperationInfo& operation_inf { ostringstream s; - const map> operations = { operation_info.operations().begin(), operation_info.operations().end() }; + const map> operations(operation_info.operations().begin(), operation_info.operations().end()); // Copies result into a map sorted by operation name auto unknown_operation = make_unique(); @@ -308,7 +261,7 @@ string ScsictlDisplay::DisplayOperationInfo(const PbOperationInfo& operation_inf } s << '\n'; - DisplayParameters(s, meta_data); + s << DisplayParameters(meta_data); } else { s << " " << name << " (Unknown server-side operation)\n"; @@ -318,96 +271,82 @@ string ScsictlDisplay::DisplayOperationInfo(const PbOperationInfo& operation_inf return s.str(); } -void ScsictlDisplay::DisplayParams(ostringstream& s, const PbDevice& pb_device) const +string ScsictlDisplay::DisplayParams(const PbDevice& pb_device) const { - const map> sorted_params = { pb_device.params().begin(), pb_device.params().end() }; + ostringstream s; - bool isFirst = true; - for (const auto& [key, value] : sorted_params) { - if (!isFirst) { - s << ":"; - } - - isFirst = false; - s << key << "=" << value; - } -} - -void ScsictlDisplay::DisplayAttributes(ostringstream& s, const PbDeviceProperties& properties) const -{ - if (properties.read_only() || properties.protectable() || properties.stoppable() || properties.lockable()) { - s << "Properties: "; - - bool has_property = false; - - if (properties.read_only()) { - s << "read-only"; - has_property = true; - } - - if (properties.protectable()) { - s << (has_property ? ", " : "") << "protectable"; - has_property = true; - } - if (properties.stoppable()) { - s << (has_property ? ", " : "") << "stoppable"; - has_property = true; - } - if (properties.removable()) { - s << (has_property ? ", " : "") << "removable"; - has_property = true; - } - if (properties.lockable()) { - s << (has_property ? ", " : "") << "lockable"; - } - - s << "\n "; - } -} - -void ScsictlDisplay::DisplayDefaultParameters(ostringstream& s, const PbDeviceProperties& properties) const -{ - if (properties.supports_params() && properties.default_params_size()) { - s << "Default parameters: "; - - const map> sorted_params = { properties.default_params().begin(), properties.default_params().end() }; - - bool isFirst = true; - for (const auto& [key, value] : sorted_params) { - if (!isFirst) { - s << "\n "; - } - s << key << "=" << value; - - isFirst = false; - } + set> params; + for (const auto& [key, value] : pb_device.params()) { + params.insert(key + "=" + value); } + s << Join(params, ":"); + + return s.str(); } -void ScsictlDisplay::DisplayBlockSizes(ostringstream& s, const PbDeviceProperties& properties) const +string ScsictlDisplay::DisplayAttributes(const PbDeviceProperties& props) const { + ostringstream s; + + vector properties; + if (props.read_only()) { + properties.emplace_back("read-only"); + } + if (props.protectable()) { + properties.emplace_back("protectable"); + } + if (props.stoppable()) { + properties.emplace_back("stoppable"); + } + if (props.removable()) { + properties.emplace_back("removable"); + } + if (props.lockable()) { + properties.emplace_back("lockable"); + } + + if (!properties.empty()) { + s << "Properties: " << Join(properties) << "\n "; + } + + return s.str(); +} + +string ScsictlDisplay::DisplayDefaultParameters(const PbDeviceProperties& properties) const +{ + ostringstream s; + + if (!properties.default_params().empty()) { + set> params; + for (const auto& [key, value] : properties.default_params()) { + params.insert(key + "=" + value); + } + + s << "Default parameters: " << Join(params, "\n "); + } + + return s.str(); +} + +string ScsictlDisplay::DisplayBlockSizes(const PbDeviceProperties& properties) const +{ + ostringstream s; + if (properties.block_sizes_size()) { - s << "Configurable block sizes in bytes: "; - - const set sorted_sizes = { properties.block_sizes().begin(), properties.block_sizes().end() }; - - bool isFirst = true; - for (const auto& size : sorted_sizes) { - if (!isFirst) { - s << ", "; - } - s << size; - - isFirst = false; - } + const set sorted_sizes(properties.block_sizes().begin(), properties.block_sizes().end()); + s << "Configurable block sizes in bytes: " << Join(sorted_sizes); } + + return s.str(); } -void ScsictlDisplay::DisplayParameters(ostringstream& s, const PbOperationMetaData& meta_data) const +string ScsictlDisplay::DisplayParameters(const PbOperationMetaData& meta_data) const { - list sorted_parameters = { meta_data.parameters().begin(), meta_data.parameters().end() }; - sorted_parameters.sort([](const auto& a, const auto& b) { return a.name() < b.name(); }); + vector sorted_parameters(meta_data.parameters().begin(), meta_data.parameters().end()); + ranges::sort(sorted_parameters, [](const auto& a, const auto& b) { return a.name() < b.name(); }); + + ostringstream s; for (const auto& parameter : sorted_parameters) { s << " " << parameter.name() << ": " @@ -418,30 +357,23 @@ void ScsictlDisplay::DisplayParameters(ostringstream& s, const PbOperationMetaDa } s << '\n'; - DisplayPermittedValues(s, parameter); + s << DisplayPermittedValues(parameter); if (!parameter.default_value().empty()) { s << " Default value: " << parameter.default_value() << '\n'; } } + + return s.str(); } -void ScsictlDisplay::DisplayPermittedValues(ostringstream& s, const PbOperationParameter& parameter) const +string ScsictlDisplay::DisplayPermittedValues(const PbOperationParameter& parameter) const { + ostringstream s; if (parameter.permitted_values_size()) { - s << " Permitted values: "; - - bool isFirst = true; - - for (const auto& permitted_value : parameter.permitted_values()) { - if (!isFirst) { - s << ", "; - } - - isFirst = false; - s << permitted_value; - } - - s << '\n'; + const set> sorted_values(parameter.permitted_values().begin(), parameter.permitted_values().end()); + s << " Permitted values: " << Join(parameter.permitted_values()) << '\n'; } + + return s.str(); } diff --git a/cpp/scsictl/scsictl_display.h b/cpp/scsictl/scsictl_display.h index 6a7f4c55..1caddf48 100644 --- a/cpp/scsictl/scsictl_display.h +++ b/cpp/scsictl/scsictl_display.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -11,7 +11,6 @@ #include "generated/piscsi_interface.pb.h" #include -#include using namespace std; using namespace piscsi_interface; @@ -37,10 +36,10 @@ public: private: - void DisplayParams(ostringstream&, const PbDevice&) const; - void DisplayAttributes(ostringstream&, const PbDeviceProperties&) const; - void DisplayDefaultParameters(ostringstream&, const PbDeviceProperties&) const; - void DisplayBlockSizes(ostringstream&, const PbDeviceProperties&) const; - void DisplayParameters(ostringstream&, const PbOperationMetaData&) const; - void DisplayPermittedValues(ostringstream&, const PbOperationParameter&) const; + string DisplayParams(const PbDevice&) const; + string DisplayAttributes(const PbDeviceProperties&) const; + string DisplayDefaultParameters(const PbDeviceProperties&) const; + string DisplayBlockSizes(const PbDeviceProperties&) const; + string DisplayParameters(const PbOperationMetaData&) const; + string DisplayPermittedValues(const PbOperationParameter&) const; }; diff --git a/cpp/scsictl/scsictl_parser.cpp b/cpp/scsictl/scsictl_parser.cpp index cd9ef35c..175b5ba8 100644 --- a/cpp/scsictl/scsictl_parser.cpp +++ b/cpp/scsictl/scsictl_parser.cpp @@ -3,13 +3,13 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "scsictl_parser.h" -PbOperation ScsictlParser::ParseOperation(const string& operation) const +PbOperation ScsictlParser::ParseOperation(string_view operation) const { const auto& it = operations.find(tolower(operation[0])); return it != operations.end() ? it->second : NO_OPERATION; @@ -17,8 +17,8 @@ PbOperation ScsictlParser::ParseOperation(const string& operation) const PbDeviceType ScsictlParser::ParseType(const string& type) const { - string t = type; - transform(t.begin(), t.end(), t.begin(), ::toupper); + string t; + ranges::transform(type, back_inserter(t), ::toupper); if (PbDeviceType parsed_type; PbDeviceType_Parse(t, &parsed_type)) { return parsed_type; diff --git a/cpp/scsictl/scsictl_parser.h b/cpp/scsictl/scsictl_parser.h index a09dc11a..ec5f2ee2 100644 --- a/cpp/scsictl/scsictl_parser.h +++ b/cpp/scsictl/scsictl_parser.h @@ -22,12 +22,12 @@ public: ScsictlParser() = default; ~ScsictlParser() = default; - PbOperation ParseOperation(const string&) const; + PbOperation ParseOperation(string_view) const; PbDeviceType ParseType(const string&) const; private: - unordered_map operations = { + const unordered_map operations = { { 'a', ATTACH }, { 'd', DETACH }, { 'e', EJECT }, @@ -37,7 +37,7 @@ private: { 'u', UNPROTECT } }; - unordered_map device_types = { + const unordered_map device_types = { { 'b', SCBR }, { 'c', SCCD }, { 'd', SCDP }, diff --git a/cpp/scsidump.cpp b/cpp/scsidump.cpp index 35d7645c..2f885529 100644 --- a/cpp/scsidump.cpp +++ b/cpp/scsidump.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -13,7 +13,7 @@ using namespace std; int main(int argc, char *argv[]) { - const vector args(argv, argv + argc); + vector args(argv, argv + argc); return ScsiDump().run(args); } diff --git a/cpp/scsidump/scsidump_core.cpp b/cpp/scsidump/scsidump_core.cpp index 7d1e2060..9df536ca 100644 --- a/cpp/scsidump/scsidump_core.cpp +++ b/cpp/scsidump/scsidump_core.cpp @@ -5,8 +5,8 @@ // // Powered by XM6 TypeG Technology. // Copyright (C) 2016-2020 GIMONS -// Copyright (C) 2022 Uwe Seimet // Copyright (C) 2022 akuker +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -17,10 +17,11 @@ #include "hal/gpiobus.h" #include "hal/gpiobus_factory.h" #include "hal/systimer.h" -#include "shared/log.h" +#include "controllers/controller_manager.h" #include "shared/piscsi_exceptions.h" #include "shared/piscsi_util.h" -#include "shared/piscsi_version.h" +#include +#include #include #include #include @@ -28,10 +29,10 @@ #include #include #include -#include #include using namespace std; +using namespace filesystem; using namespace spdlog; using namespace scsi_defs; using namespace piscsi_util; @@ -50,17 +51,18 @@ void ScsiDump::KillHandler(int) exit(EXIT_SUCCESS); } -bool ScsiDump::Banner(const vector& args) const +bool ScsiDump::Banner(span args) const { cout << piscsi_util::Banner("(Hard Disk Dump/Restore Utility)"); if (args.size() < 2 || string(args[1]) == "-h" || string(args[1]) == "--help") { cout << "Usage: " << args[0] << " -t ID[:LUN] [-i BID] -f FILE [-v] [-r] [-s BUFFER_SIZE] [-p]\n" - << " ID is the target device ID (0-7).\n" - << " LUN is the optional target device LUN (0-7). Default is 0.\n" + << " ID is the target device ID (0-" << (ControllerManager::GetScsiIdMax() - 1) << ").\n" + << " LUN is the optional target device LUN (0-" << (ControllerManager::GetScsiLunMax() -1 ) << ")." + << " Default is 0.\n" << " BID is the PiSCSI board ID (0-7). Default is 7.\n" << " FILE is the dump file path.\n" - << " BUFFER_SIZE is the transfer buffer size in bytes, at least " << to_string(MINIMUM_BUFFER_SIZE) + << " BUFFER_SIZE is the transfer buffer size in bytes, at least " << MINIMUM_BUFFER_SIZE << " bytes. Default is 1 MiB.\n" << " -v Enable verbose logging.\n" << " -r Restore instead of dump.\n" @@ -86,7 +88,7 @@ bool ScsiDump::Init() const return bus != nullptr; } -void ScsiDump::ParseArguments(const vector& args) +void ScsiDump::ParseArguments(span args) { int opt; @@ -107,13 +109,13 @@ void ScsiDump::ParseArguments(const vector& args) case 's': if (!GetAsUnsignedInt(optarg, buffer_size) || buffer_size < MINIMUM_BUFFER_SIZE) { - throw parser_exception("Buffer size must be at least " + to_string(MINIMUM_BUFFER_SIZE / 1024) + "KiB"); + throw parser_exception("Buffer size must be at least " + to_string(MINIMUM_BUFFER_SIZE / 1024) + " KiB"); } break; case 't': { - const string error = ProcessId(optarg, 8, target_id, target_lun); + const string error = ProcessId(optarg, target_id, target_lun); if (!error.empty()) { throw parser_exception(error); } @@ -149,7 +151,7 @@ void ScsiDump::ParseArguments(const vector& args) void ScsiDump::WaitPhase(phase_t phase) const { - LOGDEBUG("Waiting for %s phase", BUS::GetPhaseStrRaw(phase)) + spdlog::debug(string("Waiting for ") + BUS::GetPhaseStrRaw(phase) + " phase"); // Timeout (3000ms) const uint32_t now = SysTimer::GetTimerLow(); @@ -180,7 +182,7 @@ void ScsiDump::Selection() const void ScsiDump::Command(scsi_command cmd, vector& cdb) const { - LOGDEBUG("Executing %s", command_mapping.find(cmd)->second.second) + spdlog::debug("Executing " + command_mapping.find(cmd)->second.second); Selection(); @@ -324,7 +326,7 @@ pair ScsiDump::ReadCapacity() (static_cast(buffer[sector_size_offset + 2]) << 8) | static_cast(buffer[sector_size_offset + 3]); - return make_pair(capacity, sector_size); + return { capacity, sector_size }; } void ScsiDump::Read10(uint32_t bstart, uint32_t blength, uint32_t length) @@ -387,7 +389,7 @@ void ScsiDump::WaitForBusy() const } } -int ScsiDump::run(const vector& args) +int ScsiDump::run(span args) { if (!Banner(args)) { return EXIT_SUCCESS; @@ -436,12 +438,12 @@ int ScsiDump::DumpRestore() if (restore) { cout << "Starting restore\n" << flush; - // filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle more than 2 GiB off_t size; - if (struct stat st; !stat(filename.c_str(), &st)) { - size = st.st_size; - } else { - throw parser_exception("Can't determine file size"); + try { + size = file_size(path(filename)); + } + catch (const filesystem_error& e) { + throw parser_exception(string("Can't determine file size: ") + e.what()); } cout << "Restore file size: " << size << " bytes\n"; @@ -455,11 +457,11 @@ int ScsiDump::DumpRestore() } // Dump by buffer size - auto dsiz = static_cast(buffer.size()); + auto dsiz = static_cast(buffer.size()); const int duni = dsiz / inq_info.sector_size; - auto dnum = static_cast((inq_info.capacity * inq_info.sector_size) / dsiz); + auto dnum = static_cast((inq_info.capacity * inq_info.sector_size) / dsiz); - auto start_time = chrono::high_resolution_clock::now(); + const auto start_time = chrono::high_resolution_clock::now(); int i; for (i = 0; i < dnum; i++) { @@ -501,16 +503,16 @@ int ScsiDump::DumpRestore() cout << "100% (" << inq_info.capacity << "/" << inq_info.capacity << ")\n" << flush; } - auto stop_time = chrono::high_resolution_clock::now(); + const auto stop_time = chrono::high_resolution_clock::now(); - auto duration = chrono::duration_cast(stop_time - start_time).count(); + const auto duration = chrono::duration_cast(stop_time - start_time).count(); cout << divider_str << "\n"; - cout << "Transfered : " << to_string(inq_info.capacity * inq_info.sector_size) << " bytes [" - << to_string(inq_info.capacity * inq_info.sector_size / 1024 / 1024) << "MiB]\n"; - cout << "Total time: " << to_string(duration) << " seconds (" << to_string(duration / 60) << " minutes\n"; - cout << "Averate transfer rate: " << to_string((inq_info.capacity * inq_info.sector_size / 8) / duration) - << " bytes per second (" << to_string((inq_info.capacity * inq_info.sector_size / 8) / duration / 1024) + cout << "Transfered : " << inq_info.capacity * inq_info.sector_size << " bytes [" + << inq_info.capacity * inq_info.sector_size / 1024 / 1024 << "MiB]\n"; + cout << "Total time: " << duration << " seconds (" << duration / 60 << " minutes\n"; + cout << "Averate transfer rate: " << (inq_info.capacity * inq_info.sector_size / 8) / duration + << " bytes per second (" << (inq_info.capacity * inq_info.sector_size / 8) / duration / 1024 << " KiB per second)\n"; cout << divider_str << "\n"; @@ -554,8 +556,8 @@ ScsiDump::inquiry_info_t ScsiDump::GetDeviceInfo() cout << "Revision: " << str.data() << "\n" << flush; inq_info.revision = string(str.data()); - if (auto type = static_cast(buffer[0]); - type != device_type::DIRECT_ACCESS && type != device_type::CD_ROM && type != device_type::OPTICAL_MEMORY) { + if (const auto type = static_cast(buffer[0]); + type != device_type::direct_access && type != device_type::cd_rom && type != device_type::optical_memory) { throw parser_exception("Invalid device type, supported types are DIRECT ACCESS, CD-ROM and OPTICAL MEMORY"); } @@ -564,8 +566,8 @@ ScsiDump::inquiry_info_t ScsiDump::GetDeviceInfo() RequestSense(); const auto [capacity, sector_size] = ReadCapacity(); - inq_info.capacity = capacity; - inq_info.sector_size = sector_size; + inq_info.capacity = capacity; + inq_info.sector_size = sector_size; cout << "Sectors: " << capacity << "\n" << "Sector size: " << sector_size << " bytes\n" @@ -579,24 +581,23 @@ ScsiDump::inquiry_info_t ScsiDump::GetDeviceInfo() void ScsiDump::GeneratePropertiesFile(const string& filename, const inquiry_info_t& inq_info) { - string prop_filename = filename + ".properties"; - string prop_str; - stringstream prop_stream(prop_str); + const string prop_filename = filename + ".properties"; + stringstream prop_stream; prop_stream << "{" << endl; prop_stream << " \"vendor\": \"" << inq_info.vendor << "\"," << endl; prop_stream << " \"product\": \"" << inq_info.product << "\"," << endl; prop_stream << " \"revision\": \"" << inq_info.revision << "\"," << endl; - prop_stream << " \"block_size\": \"" << to_string(inq_info.sector_size) << "\"," << endl; + prop_stream << " \"block_size\": \"" << inq_info.sector_size << "\"," << endl; prop_stream << "}" << endl; FILE* fp = fopen(prop_filename.c_str(), "w"); if (fp) { fputs(prop_stream.str().c_str(), fp); } else { - LOGWARN("Unable to open output file %s", prop_filename.c_str()) + spdlog::warn("Unable to open output file '" + prop_filename + "'"); return; } fclose(fp); -} \ No newline at end of file +} diff --git a/cpp/scsidump/scsidump_core.h b/cpp/scsidump/scsidump_core.h index c46a6993..64a82fa8 100644 --- a/cpp/scsidump/scsidump_core.h +++ b/cpp/scsidump/scsidump_core.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -13,6 +13,7 @@ #include "shared/scsi.h" #include #include +#include #include using namespace std; @@ -26,7 +27,7 @@ class ScsiDump ScsiDump() = default; ~ScsiDump() = default; - int run(const vector&); + int run(const span); struct inquiry_info_struct { string vendor; @@ -42,9 +43,9 @@ class ScsiDump static void GeneratePropertiesFile(const string& filename, const inquiry_info_t& inq_info); private: - bool Banner(const vector&) const; + bool Banner(span) const; bool Init() const; - void ParseArguments(const vector&); + void ParseArguments(span); int DumpRestore(); inquiry_info_t GetDeviceInfo(); void WaitPhase(phase_t) const; diff --git a/cpp/scsiloop/scsiloop_core.cpp b/cpp/scsiloop/scsiloop_core.cpp index b6181d64..c5806bf2 100644 --- a/cpp/scsiloop/scsiloop_core.cpp +++ b/cpp/scsiloop/scsiloop_core.cpp @@ -12,7 +12,7 @@ // //--------------------------------------------------------------------------- -#include "shared/log.h" +#include "hal/log.h" #include "shared/piscsi_version.h" #include "shared/piscsi_util.h" #include "spdlog/sinks/stdout_color_sinks.h" diff --git a/cpp/scsiloop/scsiloop_cout.cpp b/cpp/scsiloop/scsiloop_cout.cpp index b681fac6..8ee99332 100644 --- a/cpp/scsiloop/scsiloop_cout.cpp +++ b/cpp/scsiloop/scsiloop_cout.cpp @@ -30,11 +30,11 @@ void ScsiLoop_Cout::FinishTest(const string &test_name, int failures) } } -void ScsiLoop_Cout::PrintErrors(vector &test_errors) +void ScsiLoop_Cout::PrintErrors(const vector &test_errors) { - if (test_errors.size() > 0) { - for (auto err_string : test_errors) { + if (!test_errors.empty()) { + for (auto& err_string : test_errors) { cout << RED << err_string << endl; } } -} \ No newline at end of file +} diff --git a/cpp/scsiloop/scsiloop_cout.h b/cpp/scsiloop/scsiloop_cout.h index 35d0bc4d..6ac5a12a 100644 --- a/cpp/scsiloop/scsiloop_cout.h +++ b/cpp/scsiloop/scsiloop_cout.h @@ -18,7 +18,7 @@ class ScsiLoop_Cout static void StartTest(const string &test_name); static void PrintUpdate(); static void FinishTest(const string &test_name, int failures); - static void PrintErrors(vector &test_errors); + static void PrintErrors(const vector &test_errors); private: const static inline string RESET = "\033[0m"; @@ -30,4 +30,4 @@ class ScsiLoop_Cout const static inline string MAGENTA = "\033[35m"; /* Magenta */ const static inline string CYAN = "\033[36m"; /* Cyan */ const static inline string WHITE = "\033[37m"; /* White */ -}; \ No newline at end of file +}; diff --git a/cpp/scsiloop/scsiloop_gpio.cpp b/cpp/scsiloop/scsiloop_gpio.cpp index c4a86a96..8032eadf 100644 --- a/cpp/scsiloop/scsiloop_gpio.cpp +++ b/cpp/scsiloop/scsiloop_gpio.cpp @@ -11,7 +11,7 @@ #include "hal/gpiobus_factory.h" #include "hal/sbc_version.h" #include "scsiloop/scsiloop_cout.h" -#include "shared/log.h" +#include "hal/log.h" #if defined CONNECT_TYPE_STANDARD #include "hal/connection_type/connection_standard.h" @@ -589,4 +589,4 @@ int ScsiLoop_GPIO::RunDataOutputTest(vector &error_list) ScsiLoop_Cout::FinishTest("DAT Outputs", err_count); return err_count; -} \ No newline at end of file +} diff --git a/cpp/scsiloop/scsiloop_gpio.h b/cpp/scsiloop/scsiloop_gpio.h index d4365682..a61ae19e 100644 --- a/cpp/scsiloop/scsiloop_gpio.h +++ b/cpp/scsiloop/scsiloop_gpio.h @@ -31,7 +31,7 @@ class ScsiLoop_GPIO int connected_pin; int dir_ctrl_pin; }; - typedef loopback_connections_struct loopback_connection; + using loopback_connection = loopback_connections_struct; std::map pin_name_lookup; std::vector loopback_conn_table; @@ -78,4 +78,4 @@ class ScsiLoop_GPIO int local_pin_dp = -1; shared_ptr bus; -}; \ No newline at end of file +}; diff --git a/cpp/scsiloop/scsiloop_timer.cpp b/cpp/scsiloop/scsiloop_timer.cpp index 293c268a..4cfe627a 100644 --- a/cpp/scsiloop/scsiloop_timer.cpp +++ b/cpp/scsiloop/scsiloop_timer.cpp @@ -10,7 +10,7 @@ #include "scsiloop_timer.h" #include "hal/systimer.h" #include "scsiloop/scsiloop_cout.h" -#include "shared/log.h" +#include "hal/log.h" int ScsiLoop_Timer::RunTimerTest(vector &error_list) { @@ -56,7 +56,7 @@ int ScsiLoop_Timer::RunTimerTest(vector &error_list) } after = SysTimer::GetTimerLow(); elapsed_nanosecs = after - before; - LOGDEBUG("SysTimer::SleepUsec() Average %d", (uint32_t)(elapsed_nanosecs / 100)); + LOGDEBUG("SysTimer::SleepUsec() Average %d", elapsed_nanosecs / 100); if ((elapsed_nanosecs > expected_usec_result * (1.0 + timer_tolerance_percent)) || (elapsed_nanosecs < expected_usec_result * (1.0 - timer_tolerance_percent))) { diff --git a/cpp/shared/network_util.cpp b/cpp/shared/network_util.cpp new file mode 100644 index 00000000..31aa1831 --- /dev/null +++ b/cpp/shared/network_util.cpp @@ -0,0 +1,75 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator PiSCSI +// for Raspberry Pi +// +// Copyright (C) 2023 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include "network_util.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +bool network_util::IsInterfaceUp(const string& interface) +{ + ifreq ifr = {}; + strncpy(ifr.ifr_name, interface.c_str(), IFNAMSIZ - 1); //NOSONAR Using strncpy is safe + const int fd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); + + if (!ioctl(fd, SIOCGIFFLAGS, &ifr) && (ifr.ifr_flags & IFF_UP)) { + close(fd); + return true; + } + + close(fd); + return false; +} + +set> network_util::GetNetworkInterfaces() +{ + set> network_interfaces; + +#ifdef __linux__ + ifaddrs *addrs; + getifaddrs(&addrs); + ifaddrs *tmp = addrs; + + while (tmp) { + if (const string name = tmp->ifa_name; tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_PACKET && + name != "lo" && name != "piscsi_bridge" && !name.starts_with("dummy") && IsInterfaceUp(name)) { + // Only list interfaces that are up + network_interfaces.insert(name); + } + + tmp = tmp->ifa_next; + } + + freeifaddrs(addrs); +#endif + + return network_interfaces; +} + +bool network_util::ResolveHostName(const string& host, sockaddr_in *addr) +{ + addrinfo hints = {}; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + if (addrinfo *result; !getaddrinfo(host.c_str(), nullptr, &hints, &result)) { + *addr = *reinterpret_cast(result->ai_addr); //NOSONAR bit_cast is not supported by the bullseye compiler + freeaddrinfo(result); + return true; + } + + return false; +} diff --git a/cpp/shared/network_util.h b/cpp/shared/network_util.h new file mode 100644 index 00000000..86d0adf9 --- /dev/null +++ b/cpp/shared/network_util.h @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator PiSCSI +// for Raspberry Pi +// +// Copyright (C) 2023 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#pragma once + +#include +#include + +using namespace std; + +struct sockaddr_in; + +namespace network_util +{ + bool IsInterfaceUp(const string&); + set> GetNetworkInterfaces(); + bool ResolveHostName(const string&, sockaddr_in *); +} diff --git a/cpp/shared/piscsi_exceptions.h b/cpp/shared/piscsi_exceptions.h index 34c58591..be003822 100644 --- a/cpp/shared/piscsi_exceptions.h +++ b/cpp/shared/piscsi_exceptions.h @@ -36,7 +36,7 @@ class scsi_exception : public exception public: - scsi_exception(scsi_defs::sense_key sense_key, scsi_defs::asc asc = scsi_defs::asc::NO_ADDITIONAL_SENSE_INFORMATION) + scsi_exception(scsi_defs::sense_key sense_key, scsi_defs::asc asc = scsi_defs::asc::no_additional_sense_information) : sense_key(sense_key), asc(asc) {} ~scsi_exception() override = default; diff --git a/cpp/shared/piscsi_util.cpp b/cpp/shared/piscsi_util.cpp index 2808ee6c..0cdccf02 100644 --- a/cpp/shared/piscsi_util.cpp +++ b/cpp/shared/piscsi_util.cpp @@ -7,13 +7,48 @@ // //--------------------------------------------------------------------------- +#include "controllers/controller_manager.h" #include "piscsi_version.h" #include "piscsi_util.h" +#include #include +#include #include +#include #include using namespace std; +using namespace filesystem; + +vector piscsi_util::Split(const string& s, char separator, int limit) +{ + assert(limit >= 0); + + string component; + vector result; + stringstream str(s); + + while (--limit > 0 && getline(str, component, separator)) { + result.push_back(component); + } + + if (!str.eof()) { + getline(str, component); + result.push_back(component); + } + + return result; +} + +string piscsi_util::GetLocale() +{ + const char *locale = setlocale(LC_MESSAGES, ""); + if (locale == nullptr || !strcmp(locale, "C")) { + locale = "en"; + } + + return locale; +} bool piscsi_util::GetAsUnsignedInt(const string& value, int& result) { @@ -35,10 +70,8 @@ bool piscsi_util::GetAsUnsignedInt(const string& value, int& result) return true; } -string piscsi_util::ProcessId(const string& id_spec, int max_luns, int& id, int& lun) +string piscsi_util::ProcessId(const string& id_spec, int& id, int& lun) { - assert(max_luns > 0); - id = -1; lun = -1; @@ -46,27 +79,32 @@ string piscsi_util::ProcessId(const string& id_spec, int max_luns, int& id, int& return "Missing device ID"; } - if (const size_t separator_pos = id_spec.find(COMPONENT_SEPARATOR); separator_pos == string::npos) { - if (!GetAsUnsignedInt(id_spec, id) || id >= 8) { - id = -1; + const int id_max = ControllerManager::GetScsiIdMax(); + const int lun_max = ControllerManager::GetScsiLunMax(); - return "Invalid device ID (0-7)"; + if (const auto& components = Split(id_spec, COMPONENT_SEPARATOR, 2); !components.empty()) { + if (components.size() == 1) { + if (!GetAsUnsignedInt(components[0], id) || id >= id_max) { + id = -1; + + return "Invalid device ID (0-" + to_string(ControllerManager::GetScsiIdMax() - 1) + ")"; + } + + return ""; } - lun = 0; - } - else if (!GetAsUnsignedInt(id_spec.substr(0, separator_pos), id) || id > 7 || - !GetAsUnsignedInt(id_spec.substr(separator_pos + 1), lun) || lun >= max_luns) { - id = -1; - lun = -1; + if (!GetAsUnsignedInt(components[0], id) || id >= id_max || !GetAsUnsignedInt(components[1], lun) || lun >= lun_max) { + id = -1; + lun = -1; - return "Invalid LUN (0-" + to_string(max_luns - 1) + ")"; + return "Invalid LUN (0-" + to_string(lun_max - 1) + ")"; + } } return ""; } -string piscsi_util::Banner(const string& app) +string piscsi_util::Banner(string_view app) { ostringstream s; @@ -79,15 +117,18 @@ string piscsi_util::Banner(const string& app) return s.str(); } -string piscsi_util::GetExtensionLowerCase(const string& filename) +string piscsi_util::GetExtensionLowerCase(string_view filename) { string ext; - if (const size_t separator = filename.rfind('.'); separator != string::npos) { - ext = filename.substr(separator + 1); - } - transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c){ return std::tolower(c); }); + ranges::transform(path(filename).extension().string(), back_inserter(ext), ::tolower); - return ext; + // Remove the leading dot + return ext.empty() ? "" : ext.substr(1); +} + +void piscsi_util::LogErrno(const string& msg) +{ + spdlog::error(errno ? msg + ": " + string(strerror(errno)) : msg); } // Pin the thread to a specific CPU diff --git a/cpp/shared/piscsi_util.h b/cpp/shared/piscsi_util.h index 762db353..477a8cab 100644 --- a/cpp/shared/piscsi_util.h +++ b/cpp/shared/piscsi_util.h @@ -3,13 +3,16 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once +#include #include +#include +#include using namespace std; @@ -18,11 +21,38 @@ namespace piscsi_util // Separator for compound options like ID:LUN static const char COMPONENT_SEPARATOR = ':'; - bool GetAsUnsignedInt(const string&, int&); - string ProcessId(const string&, int, int&, int&); - string Banner(const string&); + struct StringHash { + using is_transparent = void; - string GetExtensionLowerCase(const string&); + size_t operator()(string_view sv) const { + hash hasher; + return hasher(sv); + } + }; + + string Join(const auto& collection, const string_view separator = ", ") { + ostringstream s; + + for (const auto& element : collection) { + if (s.tellp()) { + s << separator; + } + + s << element; + } + + return s.str(); + } + + vector Split(const string&, char, int = INT_MAX); + string GetLocale(); + bool GetAsUnsignedInt(const string&, int&); + string ProcessId(const string&, int&, int&); + string Banner(string_view); + + string GetExtensionLowerCase(string_view); + + void LogErrno(const string&); void FixCpu(int); } diff --git a/cpp/shared/piscsi_version.cpp b/cpp/shared/piscsi_version.cpp index 63dfe058..05c297fd 100644 --- a/cpp/shared/piscsi_version.cpp +++ b/cpp/shared/piscsi_version.cpp @@ -4,7 +4,6 @@ // for Raspberry Pi // // Copyright (C) 2020 akuker -// [ Define the version string ] // //--------------------------------------------------------------------------- @@ -14,8 +13,8 @@ // The following should be updated for each release const int piscsi_major_version = 23; // Last two digits of year -const int piscsi_minor_version = 5; // Month -const int piscsi_patch_version = -1; // Patch number - increment for each update +const int piscsi_minor_version = 10; // Month +const int piscsi_patch_version = -1; // Patch number - increment for each update using namespace std; diff --git a/cpp/shared/protobuf_serializer.cpp b/cpp/shared/protobuf_serializer.cpp deleted file mode 100644 index ffca4e23..00000000 --- a/cpp/shared/protobuf_serializer.cpp +++ /dev/null @@ -1,80 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Copyright (C) 2022 Uwe Seimet -// -//--------------------------------------------------------------------------- - -#include "shared/protobuf_serializer.h" -#include "shared/piscsi_exceptions.h" -#include "generated/piscsi_interface.pb.h" -#include - -using namespace std; -using namespace piscsi_interface; - -//--------------------------------------------------------------------------- -// -// Serialize/Deserialize protobuf message: Length followed by the actual data. -// A little endian platform is assumed. -// -//--------------------------------------------------------------------------- - -void ProtobufSerializer::SerializeMessage(int fd, const google::protobuf::Message& message) const -{ - string data; - message.SerializeToString(&data); - - // Write the size of the protobuf data as a header - auto size = static_cast(data.length()); - if (write(fd, &size, sizeof(size)) != sizeof(size)) { - throw io_exception("Can't write protobuf message header"); - } - - // Write the actual protobuf data - if (write(fd, data.data(), size) != size) { - throw io_exception("Can't write protobuf message data"); - } -} - -void ProtobufSerializer::DeserializeMessage(int fd, google::protobuf::Message& message) const -{ - // Read the header with the size of the protobuf data - vector header_buf(4); - if (ReadBytes(fd, header_buf) < header_buf.size()) { - throw io_exception("Invalid protobuf message header"); - } - - const int size = (static_cast(header_buf[3]) << 24) + (static_cast(header_buf[2]) << 16) - + (static_cast(header_buf[1]) << 8) + static_cast(header_buf[0]); - if (size < 0) { - throw io_exception("Invalid protobuf message header"); - } - - // Read the binary protobuf data - vector data_buf(size); - if (ReadBytes(fd, data_buf) < data_buf.size()) { - throw io_exception("Missing protobuf message data"); - } - - // Create protobuf message - string data((const char *)data_buf.data(), size); - message.ParseFromString(data); -} - -size_t ProtobufSerializer::ReadBytes(int fd, vector& buf) const -{ - size_t offset = 0; - while (offset < buf.size()) { - const ssize_t len = read(fd, &buf.data()[offset], buf.size() - offset); - if (len <= 0) { - return len; - } - - offset += len; - } - - return offset; -} diff --git a/cpp/shared/protobuf_serializer.h b/cpp/shared/protobuf_serializer.h deleted file mode 100644 index b9a67302..00000000 --- a/cpp/shared/protobuf_serializer.h +++ /dev/null @@ -1,29 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Copyright (C) 2022 Uwe Seimet -// -// Helper for serializing/deserializing protobuf messages -// -//--------------------------------------------------------------------------- - -#pragma once - -#include "google/protobuf/message.h" -#include - -using namespace std; - -class ProtobufSerializer -{ -public: - - ProtobufSerializer() = default; - ~ProtobufSerializer() = default; - - void SerializeMessage(int, const google::protobuf::Message&) const; - void DeserializeMessage(int, google::protobuf::Message&) const; - size_t ReadBytes(int, vector&) const; -}; diff --git a/cpp/shared/protobuf_util.cpp b/cpp/shared/protobuf_util.cpp index 9557b5bc..1cdff94b 100644 --- a/cpp/shared/protobuf_util.cpp +++ b/cpp/shared/protobuf_util.cpp @@ -3,15 +3,18 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "log.h" +#include "shared/piscsi_exceptions.h" #include "piscsi_util.h" -#include "protobuf_serializer.h" #include "protobuf_util.h" +#include #include +#include +#include + using namespace std; using namespace piscsi_util; @@ -32,61 +35,21 @@ void protobuf_util::ParseParameters(PbDeviceDefinition& device, const string& pa return; } - stringstream ss(params); - string p; - while (getline(ss, p, COMPONENT_SEPARATOR)) { - if (!p.empty()) { - const size_t separator_pos = p.find(KEY_VALUE_SEPARATOR); - if (separator_pos != string::npos) { - SetParam(device, p.substr(0, separator_pos), string_view(p).substr(separator_pos + 1)); - } + for (const auto& p : Split(params, COMPONENT_SEPARATOR)) { + if (const auto& param = Split(p, KEY_VALUE_SEPARATOR, 2); param.size() == 2) { + SetParam(device, param[0], param[1]); } } } -string protobuf_util::GetParam(const PbCommand& command, const string& key) -{ - const auto& it = command.params().find(key); - return it != command.params().end() ? it->second : ""; -} - -string protobuf_util::GetParam(const PbDeviceDefinition& device, const string& key) -{ - const auto& it = device.params().find(key); - return it != device.params().end() ? it->second : ""; -} - -void protobuf_util::SetParam(PbCommand& command, const string& key, string_view value) -{ - if (!key.empty() && !value.empty()) { - auto& map = *command.mutable_params(); - map[key] = value; - } -} - -void protobuf_util::SetParam(PbDevice& device, const string& key, string_view value) -{ - if (!key.empty() && !value.empty()) { - auto& map = *device.mutable_params(); - map[key] = value; - } -} - -void protobuf_util::SetParam(PbDeviceDefinition& device, const string& key, string_view value) -{ - if (!key.empty() && !value.empty()) { - auto& map = *device.mutable_params(); - map[key] = value; - } -} - -void protobuf_util::SetPatternParams(PbCommand& command, string_view patterns) +void protobuf_util::SetPatternParams(PbCommand& command, const string& patterns) { string folder_pattern; string file_pattern; - if (const size_t separator_pos = patterns.find(COMPONENT_SEPARATOR); separator_pos != string::npos) { - folder_pattern = patterns.substr(0, separator_pos); - file_pattern = patterns.substr(separator_pos + 1); + + if (const auto& components = Split(patterns, ':', 2); components.size() == 2) { + folder_pattern = components[0]; + file_pattern = components[1]; } else { file_pattern = patterns; @@ -98,40 +61,40 @@ void protobuf_util::SetPatternParams(PbCommand& command, string_view patterns) void protobuf_util::SetProductData(PbDeviceDefinition& device, const string& data) { - string name = data; + const auto& components = Split(data, COMPONENT_SEPARATOR, 3); + switch (components.size()) { + case 3: + device.set_revision(components[2]); + [[fallthrough]]; - if (size_t separator_pos = name.find(COMPONENT_SEPARATOR); separator_pos != string::npos) { - device.set_vendor(name.substr(0, separator_pos)); - name = name.substr(separator_pos + 1); - separator_pos = name.find(COMPONENT_SEPARATOR); - if (separator_pos != string::npos) { - device.set_product(name.substr(0, separator_pos)); - device.set_revision(name.substr(separator_pos + 1)); - } - else { - device.set_product(name); - } - } - else { - device.set_vendor(name); + case 2: + device.set_product(components[1]); + [[fallthrough]]; + + case 1: + device.set_vendor(components[0]); + break; + + default: + break; } } -string protobuf_util::SetIdAndLun(PbDeviceDefinition& device, const string& value, int max_luns) +string protobuf_util::SetIdAndLun(PbDeviceDefinition& device, const string& value) { int id; int lun; - if (const string error = ProcessId(value, max_luns, id, lun); !error.empty()) { + if (const string error = ProcessId(value, id, lun); !error.empty()) { return error; } device.set_id(id); - device.set_unit(lun); + device.set_unit(lun != -1 ? lun : 0); return ""; } -string protobuf_util::ListDevices(const list& pb_devices) +string protobuf_util::ListDevices(const vector& pb_devices) { if (pb_devices.empty()) { return "No devices currently attached.\n"; @@ -142,8 +105,8 @@ string protobuf_util::ListDevices(const list& pb_devices) << "| ID | LUN | TYPE | IMAGE FILE\n" << "+----+-----+------+-------------------------------------\n"; - list devices = pb_devices; - devices.sort([](const auto& a, const auto& b) { return a.id() < b.id() || a.unit() < b.unit(); }); + vector devices = pb_devices; + ranges::sort(devices, [](const auto& a, const auto& b) { return a.id() < b.id() || a.unit() < b.unit(); }); for (const auto& device : devices) { string filename; @@ -179,3 +142,68 @@ string protobuf_util::ListDevices(const list& pb_devices) return s.str(); } + +//--------------------------------------------------------------------------- +// +// Serialize/Deserialize protobuf message: Length followed by the actual data. +// A little endian platform is assumed. +// +//--------------------------------------------------------------------------- + +void protobuf_util::SerializeMessage(int fd, const google::protobuf::Message& message) +{ + const string data = message.SerializeAsString(); + + // Write the size of the protobuf data as a header + const auto size = static_cast(data.length()); + if (write(fd, &size, sizeof(size)) != sizeof(size)) { + throw io_exception("Can't write protobuf message size"); + } + + // Write the actual protobuf data + if (write(fd, data.data(), size) != size) { + throw io_exception("Can't write protobuf message data"); + } +} + +void protobuf_util::DeserializeMessage(int fd, google::protobuf::Message& message) +{ + // Read the header with the size of the protobuf data + array header_buf; + if (ReadBytes(fd, header_buf) < header_buf.size()) { + throw io_exception("Can't read protobuf message size"); + } + + const int size = (static_cast(header_buf[3]) << 24) + (static_cast(header_buf[2]) << 16) + + (static_cast(header_buf[1]) << 8) + static_cast(header_buf[0]); + if (size < 0) { + throw io_exception("Invalid protobuf message size"); + } + + // Read the binary protobuf data + vector data_buf(size); + if (ReadBytes(fd, data_buf) != data_buf.size()) { + throw io_exception("Invalid protobuf message data"); + } + + message.ParseFromArray(data_buf.data(), size); +} + +size_t protobuf_util::ReadBytes(int fd, span buf) +{ + size_t offset = 0; + while (offset < buf.size()) { + const auto len = read(fd, &buf.data()[offset], buf.size() - offset); + if (len == -1) { + throw io_exception("Read error: " + string(strerror(errno))); + } + + if (!len) { + break; + } + + offset += len; + } + + return offset; +} diff --git a/cpp/shared/protobuf_util.h b/cpp/shared/protobuf_util.h index a0dcd760..0e5b2a78 100644 --- a/cpp/shared/protobuf_util.h +++ b/cpp/shared/protobuf_util.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // // Helper methods for setting up/evaluating protobuf messages // @@ -11,10 +11,10 @@ #pragma once -#include "google/protobuf/message.h" -#include "generated/piscsi_interface.pb.h" #include -#include +#include +#include +#include "generated/piscsi_interface.pb.h" using namespace std; using namespace piscsi_interface; @@ -23,14 +23,27 @@ namespace protobuf_util { static const char KEY_VALUE_SEPARATOR = '='; + string GetParam(const auto& item, const string& key) + { + const auto& it = item.params().find(key); + return it != item.params().end() ? it->second : ""; + } + + void SetParam(auto& item, const string& key, string_view value) + { + if (!key.empty() && !value.empty()) { + auto& map = *item.mutable_params(); + map[key] = value; + } + } + void ParseParameters(PbDeviceDefinition&, const string&); - string GetParam(const PbCommand&, const string&); - string GetParam(const PbDeviceDefinition&, const string&); - void SetParam(PbCommand&, const string&, string_view); - void SetParam(PbDevice&, const string&, string_view); - void SetParam(PbDeviceDefinition&, const string&, string_view); - void SetPatternParams(PbCommand&, string_view); + void SetPatternParams(PbCommand&, const string&); void SetProductData(PbDeviceDefinition&, const string&); - string SetIdAndLun(PbDeviceDefinition&, const string&, int); - string ListDevices(const list&); + string SetIdAndLun(PbDeviceDefinition&, const string&); + string ListDevices(const vector&); + + void SerializeMessage(int, const google::protobuf::Message&); + void DeserializeMessage(int, google::protobuf::Message&); + size_t ReadBytes(int, span); } diff --git a/cpp/shared/scsi.h b/cpp/shared/scsi.h index c8a4279b..b5611512 100644 --- a/cpp/shared/scsi.h +++ b/cpp/shared/scsi.h @@ -1,33 +1,39 @@ //--------------------------------------------------------------------------- // -// X68000 EMULATOR "XM6" +// X68000 EMULATOR "XM6" // -// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) -// Copyright (C) 2014-2020 GIMONS +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once +#include #include +#include using namespace std; +// Command Descriptor Block +using cdb_t = span; + namespace scsi_defs { -enum class scsi_level : int { - SCSI_1_CCS = 1, - SCSI_2 = 2, - SPC = 3, - SPC_2 = 4, - SPC_3 = 5, - SPC_4 = 6, - SPC_5 = 7, - SPC_6 = 8 +enum class scsi_level { + scsi_1_ccs = 1, + scsi_2 = 2, + spc = 3, + spc_2 = 4, + spc_3 = 5, + spc_4 = 6, + spc_5 = 7, + spc_6 = 8 }; -// Phase definitions -enum class phase_t : int { +// Phase definitions +enum class phase_t { busfree, arbitration, selection, @@ -41,16 +47,16 @@ enum class phase_t : int { reserved }; -enum class device_type : int { - DIRECT_ACCESS = 0, - PRINTER = 2, - PROCESSOR = 3, - CD_ROM = 5, - OPTICAL_MEMORY = 7, - COMMUNICATIONS = 9 +enum class device_type { + direct_access = 0, + printer = 2, + processor = 3, + cd_rom = 5, + optical_memory = 7, + communications = 9 }; -enum class scsi_command : int { +enum class scsi_command { eCmdTestUnitReady = 0x00, eCmdRezero = 0x01, eCmdRequestSense = 0x03, @@ -104,35 +110,39 @@ enum class scsi_command : int { eCmdReportLuns = 0xA0 }; -enum class status : int { GOOD = 0x00, CHECK_CONDITION = 0x02, RESERVATION_CONFLICT = 0x18 }; - -enum class sense_key : int { - NO_SENSE = 0x00, - NOT_READY = 0x02, - MEDIUM_ERROR = 0x03, - ILLEGAL_REQUEST = 0x05, - UNIT_ATTENTION = 0x06, - DATA_PROTECT = 0x07, - ABORTED_COMMAND = 0x0b +enum class status { + good = 0x00, + check_condition = 0x02, + reservation_conflict = 0x18 }; -enum class asc : int { - NO_ADDITIONAL_SENSE_INFORMATION = 0x00, - WRITE_FAULT = 0x03, - READ_FAULT = 0x11, - INVALID_COMMAND_OPERATION_CODE = 0x20, - LBA_OUT_OF_RANGE = 0x21, - INVALID_FIELD_IN_CDB = 0x24, - INVALID_LUN = 0x25, - INVALID_FIELD_IN_PARAMETER_LIST = 0x26, - WRITE_PROTECTED = 0x27, - NOT_READY_TO_READY_CHANGE = 0x28, - POWER_ON_OR_RESET = 0x29, - MEDIUM_NOT_PRESENT = 0x3a, - LOAD_OR_EJECT_FAILED = 0x53 +enum class sense_key { + no_sense = 0x00, + not_ready = 0x02, + medium_error = 0x03, + illegal_request = 0x05, + unit_attention = 0x06, + data_protect = 0x07, + aborted_command = 0x0b }; -static const unordered_map> command_mapping = { +enum class asc { + no_additional_sense_information = 0x00, + write_fault = 0x03, + read_fault = 0x11, + invalid_command_operation_code = 0x20, + lba_out_of_range = 0x21, + invalid_field_in_cdb = 0x24, + invalid_lun = 0x25, + invalid_field_in_parameter_list = 0x26, + write_protected = 0x27, + not_ready_to_ready_change = 0x28, + power_on_or_reset = 0x29, + medium_not_present = 0x3a, + load_or_eject_failed = 0x53 +}; + +static const unordered_map> command_mapping = { {scsi_command::eCmdTestUnitReady, make_pair(6, "TestUnitReady")}, {scsi_command::eCmdRezero, make_pair(6, "Rezero")}, {scsi_command::eCmdRequestSense, make_pair(6, "RequestSense")}, diff --git a/cpp/test/abstract_controller_test.cpp b/cpp/test/abstract_controller_test.cpp index 8e1e5352..41270b4d 100644 --- a/cpp/test/abstract_controller_test.cpp +++ b/cpp/test/abstract_controller_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -15,9 +15,7 @@ using namespace scsi_defs; TEST(AbstractControllerTest, AllocateCmd) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; EXPECT_EQ(16, controller.GetCmd().size()); controller.AllocateCmd(1234); @@ -26,9 +24,7 @@ TEST(AbstractControllerTest, AllocateCmd) TEST(AbstractControllerTest, AllocateBuffer) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; controller.AllocateBuffer(1); EXPECT_LE(1, controller.GetBuffer().size()); @@ -39,25 +35,23 @@ TEST(AbstractControllerTest, AllocateBuffer) TEST(AbstractControllerTest, Reset) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); + auto controller = make_shared(bus, 0); auto device = make_shared(0); controller->AddDevice(device); controller->SetPhase(phase_t::status); EXPECT_EQ(phase_t::status, controller->GetPhase()); + EXPECT_CALL(*bus, Reset()); controller->Reset(); EXPECT_TRUE(controller->IsBusFree()); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); EXPECT_EQ(0, controller->GetLength()); } TEST(AbstractControllerTest, Next) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; controller.SetNext(0x1234); EXPECT_EQ(0x1234, controller.GetNext()); @@ -67,9 +61,7 @@ TEST(AbstractControllerTest, Next) TEST(AbstractControllerTest, Message) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; controller.SetMessage(0x12); EXPECT_EQ(0x12, controller.GetMessage()); @@ -77,9 +69,7 @@ TEST(AbstractControllerTest, Message) TEST(AbstractControllerTest, ByteTransfer) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; controller.SetByteTransfer(false); EXPECT_FALSE(controller.IsByteTransfer()); @@ -89,9 +79,7 @@ TEST(AbstractControllerTest, ByteTransfer) TEST(AbstractControllerTest, BytesToTransfer) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; controller.SetBytesToTransfer(0x1234); EXPECT_EQ(0x1234, controller.GetBytesToTransfer()); @@ -101,70 +89,17 @@ TEST(AbstractControllerTest, BytesToTransfer) TEST(AbstractControllerTest, GetMaxLuns) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; EXPECT_EQ(32, controller.GetMaxLuns()); } TEST(AbstractControllerTest, Status) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; - controller.SetStatus(status::RESERVATION_CONFLICT); - EXPECT_EQ(status::RESERVATION_CONFLICT, controller.GetStatus()); -} - -TEST(AbstractControllerTest, ProcessPhase) -{ - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); - - controller.SetPhase(phase_t::selection); - EXPECT_CALL(controller, Selection); - controller.ProcessPhase(); - - controller.SetPhase(phase_t::busfree); - EXPECT_CALL(controller, BusFree); - controller.ProcessPhase(); - - controller.SetPhase(phase_t::datain); - EXPECT_CALL(controller, DataIn); - controller.ProcessPhase(); - - controller.SetPhase(phase_t::dataout); - EXPECT_CALL(controller, DataOut); - controller.ProcessPhase(); - - controller.SetPhase(phase_t::command); - EXPECT_CALL(controller, Command); - controller.ProcessPhase(); - - controller.SetPhase(phase_t::status); - EXPECT_CALL(controller, Status); - controller.ProcessPhase(); - - controller.SetPhase(phase_t::msgin); - EXPECT_CALL(controller, MsgIn); - controller.ProcessPhase(); - - controller.SetPhase(phase_t::msgout); - EXPECT_CALL(controller, MsgOut); - controller.ProcessPhase(); - - controller.SetPhase(phase_t::reselection); - EXPECT_THAT([&] { controller.ProcessPhase(); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ABORTED_COMMAND), - Property(&scsi_exception::get_asc, asc::NO_ADDITIONAL_SENSE_INFORMATION)))); - - controller.SetPhase(phase_t::reserved); - EXPECT_THAT([&] { controller.ProcessPhase(); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ABORTED_COMMAND), - Property(&scsi_exception::get_asc, asc::NO_ADDITIONAL_SENSE_INFORMATION)))); + controller.SetStatus(status::reservation_conflict); + EXPECT_EQ(status::reservation_conflict, controller.GetStatus()); } TEST(AbstractControllerTest, DeviceLunLifeCycle) @@ -172,9 +107,7 @@ TEST(AbstractControllerTest, DeviceLunLifeCycle) const int ID = 1; const int LUN = 4; - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, ID); + auto controller = make_shared>(ID); auto device1 = make_shared(LUN); auto device2 = make_shared(32); @@ -190,33 +123,16 @@ TEST(AbstractControllerTest, DeviceLunLifeCycle) EXPECT_FALSE(controller->HasDeviceForLun(0)); EXPECT_NE(nullptr, controller->GetDeviceForLun(LUN)); EXPECT_EQ(nullptr, controller->GetDeviceForLun(0)); - EXPECT_TRUE(controller->RemoveDevice(device1)); + EXPECT_TRUE(controller->RemoveDevice(*device1)); EXPECT_EQ(0, controller->GetLunCount()); - EXPECT_FALSE(controller->RemoveDevice(device1)); -} - -TEST(AbstractControllerTest, ExtractInitiatorId) -{ - const int ID = 1; - const int INITIATOR_ID = 7; - - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, ID); - - EXPECT_EQ(INITIATOR_ID, controller.ExtractInitiatorId((1 << INITIATOR_ID) | ( 1 << ID))); - EXPECT_EQ(AbstractController::UNKNOWN_INITIATOR_ID, controller.ExtractInitiatorId(1 << ID)); + EXPECT_FALSE(controller->RemoveDevice(*device1)); } TEST(AbstractControllerTest, GetOpcode) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; - auto& cmd = controller.GetCmd(); - - cmd[0] = static_cast(scsi_command::eCmdInquiry); + controller.SetCmdByte(0, static_cast(scsi_command::eCmdInquiry)); EXPECT_EQ(scsi_command::eCmdInquiry, controller.GetOpcode()); } @@ -224,33 +140,25 @@ TEST(AbstractControllerTest, GetLun) { const int LUN = 3; - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; - auto& cmd = controller.GetCmd(); - - cmd[1] = LUN << 5; + controller.SetCmdByte(1, LUN << 5); EXPECT_EQ(LUN, controller.GetLun()); } TEST(AbstractControllerTest, Blocks) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; controller.SetBlocks(1); - EXPECT_EQ(1, controller.GetBlocks()); + EXPECT_TRUE(controller.HasBlocks()); controller.DecrementBlocks(); - EXPECT_EQ(0, controller.GetBlocks()); + EXPECT_FALSE(controller.HasBlocks()); } TEST(AbstractControllerTest, Length) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; EXPECT_FALSE(controller.HasValidLength()); @@ -261,9 +169,7 @@ TEST(AbstractControllerTest, Length) TEST(AbstractControllerTest, UpdateOffsetAndLength) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; EXPECT_FALSE(controller.HasValidLength()); @@ -273,9 +179,7 @@ TEST(AbstractControllerTest, UpdateOffsetAndLength) TEST(AbstractControllerTest, Offset) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; controller.ResetOffset(); EXPECT_EQ(0, controller.GetOffset()); diff --git a/cpp/test/command_context_test.cpp b/cpp/test/command_context_test.cpp index d99a1d59..f1f9772d 100644 --- a/cpp/test/command_context_test.cpp +++ b/cpp/test/command_context_test.cpp @@ -3,52 +3,149 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include +#include "test/test_shared.h" +#include "shared/piscsi_exceptions.h" +#include "shared/protobuf_util.h" #include "piscsi/command_context.h" +#include +#include -TEST(CommandContext, GetSerializer) +using namespace protobuf_util; + +TEST(CommandContext, SetGetDefaultFolder) { - CommandContext context("", -1); + PbCommand command; + CommandContext context(command, "folder1", ""); - // There is nothing more that can be tested - context.GetSerializer(); + EXPECT_EQ("folder1", context.GetDefaultFolder()); + context.SetDefaultFolder("folder2"); + EXPECT_EQ("folder2", context.GetDefaultFolder()); } -TEST(CommandContext, IsValid) +TEST(CommandContext, ReadCommand) { - CommandContext context("", -1); + int fd = open(CreateTempFile(0).string().c_str(), O_RDONLY); + CommandContext context1(fd); + EXPECT_FALSE(context1.ReadCommand()); + close(fd); - EXPECT_FALSE(context.IsValid()); + // Invalid magic with wrong length + vector data = { byte{'1'}, byte{'2'}, byte{'3'} }; + fd = open(CreateTempFileWithData(data).string().c_str(), O_RDONLY); + CommandContext context2(fd); + EXPECT_THROW(context2.ReadCommand(), io_exception); + close(fd); - context.SetFd(1); - EXPECT_TRUE(context.IsValid()); + // Invalid magic with right length + data = { byte{'1'}, byte{'2'}, byte{'3'}, byte{'4'}, byte{'5'}, byte{'6'} }; + fd = open(CreateTempFileWithData(data).string().c_str(), O_RDONLY); + CommandContext context3(fd); + EXPECT_THROW(context3.ReadCommand(), io_exception); + close(fd); + + data = { byte{'R'}, byte{'A'}, byte{'S'}, byte{'C'}, byte{'S'}, byte{'I'}, byte{'1'} }; + // Valid magic but invalid command + fd = open(CreateTempFileWithData(data).string().c_str(), O_RDONLY); + CommandContext context4(fd); + EXPECT_THROW(context4.ReadCommand(), io_exception); + close(fd); + + data = { byte{'R'}, byte{'A'}, byte{'S'}, byte{'C'}, byte{'S'}, byte{'I'} }; + // Valid magic but missing command + fd = open(CreateTempFileWithData(data).string().c_str(), O_RDONLY); + CommandContext context5(fd); + EXPECT_THROW(context5.ReadCommand(), io_exception); + close(fd); + + const string filename = CreateTempFileWithData(data).string(); + fd = open(filename.c_str(), O_RDWR | O_APPEND); + PbCommand command; + command.set_operation(PbOperation::SERVER_INFO); + SerializeMessage(fd, command); + close(fd); + fd = open(filename.c_str(), O_RDONLY); + CommandContext context6(fd); + EXPECT_TRUE(context6.ReadCommand()); + close(fd); + EXPECT_EQ(PbOperation::SERVER_INFO, context6.GetCommand().operation()); } -TEST(CommandContext, Cleanup) +TEST(CommandContext, GetCommand) { - CommandContext context("", 0); + PbCommand command; + command.set_operation(PbOperation::SERVER_INFO); + CommandContext context(command, "", ""); + EXPECT_EQ(PbOperation::SERVER_INFO, context.GetCommand().operation()); +} - EXPECT_EQ(0, context.GetFd()); - context.Cleanup(); - EXPECT_EQ(-1, context.GetFd()); +TEST(CommandContext, WriteResult) +{ + const string filename = CreateTempFile(0); + int fd = open(filename.c_str(), O_RDWR | O_APPEND); + PbResult result; + result.set_status(false); + result.set_error_code(PbErrorCode::UNAUTHORIZED); + CommandContext context(fd); + context.WriteResult(result); + close(fd); + EXPECT_FALSE(result.status()); + + fd = open(filename.c_str(), O_RDONLY); + result.set_status(true); + DeserializeMessage(fd, result); + close(fd); + EXPECT_FALSE(result.status()); + EXPECT_EQ(PbErrorCode::UNAUTHORIZED, result.error_code()); +} + +TEST(CommandContext, WriteSuccessResult) +{ + const string filename = CreateTempFile(0); + int fd = open(filename.c_str(), O_RDWR | O_APPEND); + PbResult result; + result.set_status(false); + CommandContext context(fd); + context.WriteSuccessResult(result); + close(fd); + EXPECT_TRUE(result.status()); } TEST(CommandContext, ReturnLocalizedError) { - CommandContext context("en_US", -1); + PbCommand command; + CommandContext context(command, "", "en_US"); EXPECT_FALSE(context.ReturnLocalizedError(LocalizationKey::ERROR_LOG_LEVEL)); } -TEST(CommandContext, ReturnStatus) +TEST(CommandContext, ReturnSuccessStatus) { - CommandContext context("", -1); + PbCommand command; - EXPECT_TRUE(context.ReturnStatus(true, "status")); - EXPECT_FALSE(context.ReturnStatus(false, "status")); + CommandContext context1(command, "", ""); + EXPECT_TRUE(context1.ReturnSuccessStatus()); + + const int fd = open("/dev/null", O_RDWR); + CommandContext context2(fd); + EXPECT_TRUE(context2.ReturnSuccessStatus()); + close(fd); +} + +TEST(CommandContext, ReturnErrorStatus) +{ + PbCommand command; + + CommandContext context1(command, "", ""); + EXPECT_FALSE(context1.ReturnErrorStatus("error")); + + const int fd = open("/dev/null", O_RDWR); + CommandContext context2(fd); + EXPECT_FALSE(context2.ReturnErrorStatus("error")); + close(fd); } diff --git a/cpp/test/controller_manager_test.cpp b/cpp/test/controller_manager_test.cpp index 9ae770a4..22a329e8 100644 --- a/cpp/test/controller_manager_test.cpp +++ b/cpp/test/controller_manager_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -13,54 +13,75 @@ TEST(ControllerManagerTest, LifeCycle) { - const int ID = 4; + const int ID1 = 4; + const int ID2 = 5; const int LUN1 = 0; const int LUN2 = 3; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; DeviceFactory device_factory; auto device = device_factory.CreateDevice(SCHS, -1, ""); - EXPECT_FALSE(controller_manager->AttachToScsiController(ID, device)); + EXPECT_FALSE(controller_manager.AttachToController(*bus, ID1, device)); device = device_factory.CreateDevice(SCHS, LUN1, ""); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device)); - auto controller = controller_manager->FindController(ID); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID1, device)); + EXPECT_TRUE(controller_manager.HasController(ID1)); + auto controller = controller_manager.FindController(ID1); EXPECT_NE(nullptr, controller); EXPECT_EQ(1, controller->GetLunCount()); - EXPECT_NE(nullptr, controller_manager->IdentifyController(1 << ID)); - EXPECT_EQ(nullptr, controller_manager->IdentifyController(0)); - EXPECT_EQ(nullptr, controller_manager->FindController(0)); - EXPECT_NE(nullptr, controller_manager->GetDeviceByIdAndLun(ID, LUN1)); - EXPECT_EQ(nullptr, controller_manager->GetDeviceByIdAndLun(0, 0)); + EXPECT_FALSE(controller_manager.HasController(0)); + EXPECT_EQ(nullptr, controller_manager.FindController(0)); + EXPECT_TRUE(controller_manager.HasDeviceForIdAndLun(ID1, LUN1)); + EXPECT_NE(nullptr, controller_manager.GetDeviceForIdAndLun(ID1, LUN1)); + EXPECT_FALSE(controller_manager.HasDeviceForIdAndLun(0, 0)); + EXPECT_EQ(nullptr, controller_manager.GetDeviceForIdAndLun(0, 0)); device = device_factory.CreateDevice(SCHS, LUN2, ""); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device)); - controller = controller_manager->FindController(ID); - EXPECT_TRUE(controller_manager->DeleteController(controller)); - EXPECT_EQ(nullptr, controller_manager->FindController(ID)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID1, device)); + EXPECT_TRUE(controller_manager.HasController(ID1)); + controller = controller_manager.FindController(ID1); + EXPECT_NE(nullptr, controller_manager.FindController(ID1)); + EXPECT_TRUE(controller_manager.DeleteController(*controller)); + EXPECT_EQ(nullptr, controller_manager.FindController(ID1)); - controller_manager->DeleteAllControllers(); - EXPECT_EQ(nullptr, controller_manager->FindController(ID)); - EXPECT_EQ(nullptr, controller_manager->GetDeviceByIdAndLun(ID, LUN1)); + auto disk = make_shared(); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID2, disk)); + EXPECT_CALL(*disk, FlushCache); + controller_manager.DeleteAllControllers(); + EXPECT_FALSE(controller_manager.HasController(ID1)); + EXPECT_EQ(nullptr, controller_manager.FindController(ID1)); + EXPECT_EQ(nullptr, controller_manager.GetDeviceForIdAndLun(ID1, LUN1)); + EXPECT_FALSE(controller_manager.HasDeviceForIdAndLun(ID1, LUN1)); + EXPECT_FALSE(controller_manager.HasController(ID2)); + EXPECT_EQ(nullptr, controller_manager.FindController(ID2)); + EXPECT_EQ(nullptr, controller_manager.GetDeviceForIdAndLun(ID2, LUN1)); + EXPECT_FALSE(controller_manager.HasDeviceForIdAndLun(ID2, LUN1)); } -TEST(ControllerManagerTest, AttachToScsiController) +TEST(ControllerManagerTest, AttachToController) { const int ID = 4; const int LUN1 = 3; const int LUN2 = 0; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; DeviceFactory device_factory; auto device1 = device_factory.CreateDevice(SCHS, LUN1, ""); - EXPECT_FALSE(controller_manager->AttachToScsiController(ID, device1)) << "LUN 0 is missing"; + EXPECT_FALSE(controller_manager.AttachToController(*bus, ID, device1)) << "LUN 0 is missing"; auto device2 = device_factory.CreateDevice(SCLP, LUN2, ""); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device2)); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device1)); - EXPECT_FALSE(controller_manager->AttachToScsiController(ID, device1)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device2)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device1)); + EXPECT_FALSE(controller_manager.AttachToController(*bus, ID, device1)); +} + +TEST(ControllerManager, ProcessOnController) +{ + ControllerManager controller_manager; + + EXPECT_EQ(AbstractController::piscsi_shutdown_mode::NONE, controller_manager.ProcessOnController(0)); } diff --git a/cpp/test/ctapdriver_test.cpp b/cpp/test/ctapdriver_test.cpp index 031e1acc..b587d471 100644 --- a/cpp/test/ctapdriver_test.cpp +++ b/cpp/test/ctapdriver_test.cpp @@ -3,11 +3,12 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "mocks.h" +#include #include "devices/ctapdriver.h" TEST(CTapDriverTest, Crc32) @@ -15,27 +16,27 @@ TEST(CTapDriverTest, Crc32) array buf; buf.fill(0x00); - EXPECT_EQ(0xe3d887bb, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN)); + EXPECT_EQ(0xe3d887bb, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); buf.fill(0xff); - EXPECT_EQ(0x814765f4, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN)); + EXPECT_EQ(0x814765f4, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); buf.fill(0x10); - EXPECT_EQ(0xb7288Cd3, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN)); + EXPECT_EQ(0xb7288Cd3, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); buf.fill(0x7f); - EXPECT_EQ(0x4b543477, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN)); + EXPECT_EQ(0x4b543477, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); buf.fill(0x80); - EXPECT_EQ(0x29cbd638, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN)); + EXPECT_EQ(0x29cbd638, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); for (size_t i = 0; i < buf.size(); i++) { buf[i] = (uint8_t)i; } - EXPECT_EQ(0xe7870705, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN)); + EXPECT_EQ(0xe7870705, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); for (size_t i = buf.size() - 1; i > 0; i--) { buf[i] = (uint8_t)i; } - EXPECT_EQ(0xe7870705, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN)); + EXPECT_EQ(0xe7870705, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); } diff --git a/cpp/test/device_factory_test.cpp b/cpp/test/device_factory_test.cpp index def637b6..b37c9bc3 100644 --- a/cpp/test/device_factory_test.cpp +++ b/cpp/test/device_factory_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -42,44 +42,43 @@ TEST(DeviceFactoryTest, GetTypeForFile) TEST(DeviceFactoryTest, GetSectorSizes) { DeviceFactory device_factory; - unordered_set sector_sizes; - sector_sizes = device_factory.GetSectorSizes(SCHD); + unordered_set 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()); + EXPECT_TRUE(sector_sizes.contains(512)); + EXPECT_TRUE(sector_sizes.contains(1024)); + EXPECT_TRUE(sector_sizes.contains(2048)); + EXPECT_TRUE(sector_sizes.contains(4096)); 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()); + EXPECT_TRUE(sector_sizes.contains(512)); + EXPECT_TRUE(sector_sizes.contains(1024)); + EXPECT_TRUE(sector_sizes.contains(2048)); + EXPECT_TRUE(sector_sizes.contains(4096)); 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()); + EXPECT_TRUE(sector_sizes.contains(512)); + EXPECT_TRUE(sector_sizes.contains(1024)); + EXPECT_TRUE(sector_sizes.contains(2048)); + EXPECT_TRUE(sector_sizes.contains(4096)); 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()); + EXPECT_TRUE(sector_sizes.contains(512)); + EXPECT_TRUE(sector_sizes.contains(2048)); } TEST(DeviceFactoryTest, GetExtensionMapping) { DeviceFactory device_factory; - unordered_map mapping = device_factory.GetExtensionMapping(); + auto mapping = device_factory.GetExtensionMapping(); EXPECT_EQ(10, mapping.size()); EXPECT_EQ(SCHD, mapping["hd1"]); EXPECT_EQ(SCHD, mapping["hds"]); @@ -97,7 +96,7 @@ TEST(DeviceFactoryTest, GetDefaultParams) { DeviceFactory device_factory; - unordered_map params = device_factory.GetDefaultParams(SCHD); + param_map params = device_factory.GetDefaultParams(SCHD); EXPECT_TRUE(params.empty()); params = device_factory.GetDefaultParams(SCRM); diff --git a/cpp/test/device_test.cpp b/cpp/test/device_test.cpp index 740adfbf..0662b67e 100644 --- a/cpp/test/device_test.cpp +++ b/cpp/test/device_test.cpp @@ -3,12 +3,11 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "mocks.h" -#include "shared/piscsi_exceptions.h" #include "devices/device.h" TEST(DeviceTest, Properties) @@ -118,28 +117,36 @@ TEST(DeviceTest, Properties) TEST(DeviceTest, GetTypeString) { MockDevice schd(SCHD); - EXPECT_STREQ("SCHD", schd.GetTypeString()); + EXPECT_EQ("SCHD", schd.GetTypeString()); MockDevice scrm(SCRM); - EXPECT_STREQ("SCRM", scrm.GetTypeString()); + EXPECT_EQ("SCRM", scrm.GetTypeString()); MockDevice scmo(SCMO); - EXPECT_STREQ("SCMO", scmo.GetTypeString()); + EXPECT_EQ("SCMO", scmo.GetTypeString()); MockDevice sccd(SCCD); - EXPECT_STREQ("SCCD", sccd.GetTypeString()); + EXPECT_EQ("SCCD", sccd.GetTypeString()); MockDevice schs(SCHS); - EXPECT_STREQ("SCHS", schs.GetTypeString()); + EXPECT_EQ("SCHS", schs.GetTypeString()); MockDevice scbr(SCBR); - EXPECT_STREQ("SCBR", scbr.GetTypeString()); + EXPECT_EQ("SCBR", scbr.GetTypeString()); MockDevice scdp(SCDP); - EXPECT_STREQ("SCDP", scdp.GetTypeString()); + EXPECT_EQ("SCDP", scdp.GetTypeString()); MockDevice sclp(SCLP); - EXPECT_STREQ("SCLP", sclp.GetTypeString()); + EXPECT_EQ("SCLP", sclp.GetTypeString()); +} + +TEST(DeviceTest, GetIdentifier) +{ + MockDevice device(1); + + EXPECT_CALL(device, GetId()); + EXPECT_EQ("UNDEFINED 0:1", device.GetIdentifier()); } TEST(DeviceTest, Vendor) @@ -188,7 +195,7 @@ TEST(DeviceTest, GetPaddedName) TEST(DeviceTest, Params) { MockDevice device(0); - unordered_map params; + param_map params; params["key"] = "value"; EXPECT_EQ("", device.GetParam("key")); @@ -196,7 +203,7 @@ TEST(DeviceTest, Params) device.SetParams(params); EXPECT_EQ("", device.GetParam("key")); - unordered_map default_params; + param_map default_params; default_params["key"] = "value"; device.SetDefaultParams(default_params); EXPECT_EQ("", device.GetParam("key")); diff --git a/cpp/test/disk_test.cpp b/cpp/test/disk_test.cpp index 82c5a3c5..b0206403 100644 --- a/cpp/test/disk_test.cpp +++ b/cpp/test/disk_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -16,23 +16,26 @@ using namespace scsi_defs; using namespace scsi_command_util; +pair, shared_ptr> CreateDisk() +{ + auto controller = make_shared>(0); + auto disk = make_shared(); + EXPECT_TRUE(disk->Init({})); + EXPECT_TRUE(controller->AddDevice(disk)); + + return { controller, disk }; +} + TEST(DiskTest, Dispatch) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); - - controller->AddDevice(disk); + auto [controller, disk] = CreateDisk(); disk->SetRemovable(true); disk->SetMediumChanged(false); disk->SetReady(true); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdTestUnitReady); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); disk->SetMediumChanged(true); EXPECT_CALL(*controller, Error); @@ -42,167 +45,132 @@ TEST(DiskTest, Dispatch) TEST(DiskTest, Rezero) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdRezero); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdRezero); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "REZERO must fail because drive is not ready"; disk->SetReady(true); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdRezero); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(DiskTest, FormatUnit) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdFormatUnit); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdFormatUnit); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "FORMAT UNIT must fail because drive is not ready"; disk->SetReady(true); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdFormatUnit); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); - cmd[1] = 0x10; - cmd[4] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdFormatUnit); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))); + controller->SetCmdByte(1, 0x10); + controller->SetCmdByte(4, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdFormatUnit); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))); } TEST(DiskTest, ReassignBlocks) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReassignBlocks); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReassignBlocks); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "REASSIGN must fail because drive is not ready"; disk->SetReady(true); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdReassignBlocks); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(DiskTest, Seek6) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdSeek6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSeek6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "SEEK(6) must fail for a medium with 0 blocks"; disk->SetBlockCount(1); // Block count - cmd[4] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdSeek6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + controller->SetCmdByte(4, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSeek6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "SEEK(6) must fail because drive is not ready"; disk->SetReady(true); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdSeek6); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(DiskTest, Seek10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdSeek10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSeek10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "SEEK(10) must fail for a medium with 0 blocks"; disk->SetBlockCount(1); // Block count - cmd[5] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdSeek10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + controller->SetCmdByte(5, 1); + + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSeek10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "SEEK(10) must fail because drive is not ready"; disk->SetReady(true); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdSeek10); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(DiskTest, ReadCapacity10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReadCapacity10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReadCapacity10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "READ CAPACITY(10) must fail because drive is not ready"; disk->SetReady(true); - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReadCapacity10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReadCapacity10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "READ CAPACITY(10) must fail because the medium has no capacity"; disk->SetBlockCount(0x12345678); @@ -222,34 +190,28 @@ TEST(DiskTest, ReadCapacity10) TEST(DiskTest, ReadCapacity16) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); + controller->SetCmdByte(1, 0x00); - auto& cmd = controller->GetCmd(); - - cmd[1] = 0x00; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Neither READ CAPACITY(16) nor READ LONG(16)"; // READ CAPACITY(16), not READ LONG(16) - cmd[1] = 0x10; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + controller->SetCmdByte(1, 0x10); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "READ CAPACITY(16) must fail because drive is not ready"; disk->SetReady(true); - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "READ CAPACITY(16) must fail because the medium has no capacity"; disk->SetBlockCount(0x1234567887654321); @@ -267,18 +229,13 @@ TEST(DiskTest, ReadCapacity16) TEST(DiskTest, Read6) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdRead6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdRead6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "READ(6) must fail for a medium with 0 blocks"; // Further testing requires filesystem access @@ -286,389 +243,328 @@ TEST(DiskTest, Read6) TEST(DiskTest, Read10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdRead10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdRead10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "READ(10) must fail for a medium with 0 blocks"; disk->SetBlockCount(1); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdRead10); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); // Further testing requires filesystem access } TEST(DiskTest, Read16) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdRead16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdRead16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "READ(16) must fail for a medium with 0 blocks"; disk->SetBlockCount(1); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdRead16); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); // Further testing requires filesystem access } TEST(DiskTest, Write6) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "WRITE(6) must fail for a medium with 0 blocks"; disk->SetBlockCount(1); disk->SetReady(true); disk->SetProtectable(true); disk->SetProtected(true); - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::DATA_PROTECT), - Property(&scsi_exception::get_asc, asc::WRITE_PROTECTED)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::data_protect), + Property(&scsi_exception::get_asc, asc::write_protected)))); // Further testing requires filesystem access } TEST(DiskTest, Write10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdWrite10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWrite10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "WRITE(10) must fail for a medium with 0 blocks"; disk->SetBlockCount(1); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdWrite10); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); // Further testing requires filesystem access } TEST(DiskTest, Write16) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdWrite16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWrite16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "WRITE(16) must fail for a medium with 0 blocks"; disk->SetBlockCount(1); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdWrite16); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); // Further testing requires filesystem access } TEST(DiskTest, Verify10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdVerify10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdVerify10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "VERIFY(10) must fail for a medium with 0 blocks"; + disk->SetReady(true); + // Verify 0 sectors disk->SetBlockCount(1); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdVerify10); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); + + // Verify 1 sector with BytChk=0 + controller->SetCmdByte(8, 1); + EXPECT_CALL(*controller, Status); + disk->Dispatch(scsi_command::eCmdVerify10); + EXPECT_EQ(status::good, controller->GetStatus()); // Further testing requires filesystem access } TEST(DiskTest, Verify16) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdVerify16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdVerify16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "VERIFY(16) must fail for a medium with 0 blocks"; + disk->SetReady(true); + // Verify 0 sectors disk->SetBlockCount(1); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdVerify16); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); + + // Verify 1 sector with BytChk=0 + controller->SetCmdByte(13, 1); + EXPECT_CALL(*controller, Status); + disk->Dispatch(scsi_command::eCmdVerify16); + EXPECT_EQ(status::good, controller->GetStatus()); // Further testing requires filesystem access } TEST(DiskTest, ReadLong10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); - - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdReadLong10); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); - cmd[2] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReadLong10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + controller->SetCmdByte(2, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReadLong10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "READ LONG(10) must fail because the capacity is exceeded"; - cmd[2] = 0; + controller->SetCmdByte(2, 0); - cmd[7] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReadLong10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(7, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReadLong10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "READ LONG(10) must fail because it currently only supports 0 bytes transfer length"; } TEST(DiskTest, ReadLong16) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); - - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; // READ LONG(16), not READ CAPACITY(16) - cmd[1] = 0x11; - cmd[2] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + controller->SetCmdByte(1, 0x11); + controller->SetCmdByte(2, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "READ LONG(16) must fail because the capacity is exceeded"; - cmd[2] = 0; + controller->SetCmdByte(2, 0); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); - cmd[13] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(13, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "READ LONG(16) must fail because it currently only supports 0 bytes transfer length"; } TEST(DiskTest, WriteLong10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); - - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdWriteLong10); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); - cmd[2] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdWriteLong10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + controller->SetCmdByte(2, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWriteLong10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "WRITE LONG(10) must fail because the capacity is exceeded"; - cmd[2] = 0; + controller->SetCmdByte(2, 0); - cmd[7] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdWriteLong10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(7, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWriteLong10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "WRITE LONG(10) must fail because it currently only supports 0 bytes transfer length"; } TEST(DiskTest, WriteLong16) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); - - cmd[2] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdWriteLong16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + controller->SetCmdByte(2, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWriteLong16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "WRITE LONG(16) must fail because the capacity is exceeded"; - cmd[2] = 0; + controller->SetCmdByte(2, 0); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdWriteLong16); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); - cmd[13] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdWriteLong16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(13, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWriteLong16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "WRITE LONG(16) must fail because it currently only supports 0 bytes transfer length"; } TEST(DiskTest, StartStopUnit) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; disk->SetRemovable(true); - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); - // Stop/Unload disk->SetReady(true); EXPECT_CALL(*controller, Status); EXPECT_CALL(*disk, FlushCache); disk->Dispatch(scsi_command::eCmdStartStop); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); EXPECT_TRUE(disk->IsStopped()); // Stop/Load - cmd[4] = 0x02; + controller->SetCmdByte(4, 0x02); disk->SetReady(true); disk->SetLocked(false); EXPECT_CALL(*controller, Status); EXPECT_CALL(*disk, FlushCache); disk->Dispatch(scsi_command::eCmdStartStop); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); disk->SetReady(false); EXPECT_CALL(*disk, FlushCache).Times(0); - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdStartStop); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LOAD_OR_EJECT_FAILED)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdStartStop); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::load_or_eject_failed)))); disk->SetReady(true); disk->SetLocked(true); EXPECT_CALL(*disk, FlushCache).Times(0); - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdStartStop); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LOAD_OR_EJECT_FAILED)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdStartStop); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::load_or_eject_failed)))); // Start/Unload - cmd[4] = 0x01; + controller->SetCmdByte(4, 0x01); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdStartStop); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); EXPECT_FALSE(disk->IsStopped()); // Start/Load - cmd[4] = 0x03; + controller->SetCmdByte(4, 0x03); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdStartStop); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(DiskTest, PreventAllowMediumRemoval) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdPreventAllowMediumRemoval); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdPreventAllowMediumRemoval); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "REMOVAL must fail because drive is not ready"; disk->SetReady(true); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdPreventAllowMediumRemoval); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); EXPECT_FALSE(disk->IsLocked()); - cmd[4] = 1; + controller->SetCmdByte(4, 1); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdPreventAllowMediumRemoval); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); EXPECT_TRUE(disk->IsLocked()); } @@ -701,9 +597,9 @@ TEST(DiskTest, Eject) EXPECT_TRUE(disk.Eject(true)); } -void DiskTest_ValidateFormatPage(shared_ptr controller, int offset) +void DiskTest_ValidateFormatPage(AbstractController& controller, int offset) { - const auto& buf = controller->GetBuffer(); + const auto& buf = controller.GetBuffer(); EXPECT_EQ(0x08, buf[offset + 3]) << "Wrong number of trackes in one zone"; EXPECT_EQ(25, GetInt16(buf, offset + 10)) << "Wrong number of sectors per track"; EXPECT_EQ(1024, GetInt16(buf, offset + 12)) << "Wrong number of bytes per sector"; @@ -714,18 +610,18 @@ void DiskTest_ValidateFormatPage(shared_ptr controller, int EXPECT_TRUE(buf[offset + 20] & 0x40) << "Wrong hard-sectored flag"; } -void DiskTest_ValidateDrivePage(shared_ptr controller, int offset) +void DiskTest_ValidateDrivePage(AbstractController& controller, int offset) { - const auto& buf = controller->GetBuffer(); + const auto& buf = controller.GetBuffer(); EXPECT_EQ(0x17, buf[offset + 2]); EXPECT_EQ(0x4d3b, GetInt16(buf, offset + 3)); EXPECT_EQ(8, buf[offset + 5]) << "Wrong number of heads"; EXPECT_EQ(7200, GetInt16(buf, offset + 20)) << "Wrong medium rotation rate"; } -void DiskTest_ValidateCachePage(shared_ptr controller, int offset) +void DiskTest_ValidateCachePage(AbstractController& controller, int offset) { - const auto& buf = controller->GetBuffer(); + const auto& buf = controller.GetBuffer(); EXPECT_EQ(0xffff, GetInt16(buf, offset + 4)) << "Wrong pre-fetch transfer length"; EXPECT_EQ(0xffff, GetInt16(buf, offset + 8)) << "Wrong maximum pre-fetch"; EXPECT_EQ(0xffff, GetInt16(buf, offset + 10)) << "Wrong maximum pre-fetch ceiling"; @@ -733,28 +629,19 @@ void DiskTest_ValidateCachePage(shared_ptr controller, int o TEST(DiskTest, ModeSense6) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); - - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); + auto [controller, disk] = CreateDisk(); // Drive must be ready on order to return all data disk->SetReady(true); - cmd[2] = 0x3f; + controller->SetCmdByte(2, 0x3f); // ALLOCATION LENGTH - cmd[4] = 255; + controller->SetCmdByte(4, 255); disk->Dispatch(scsi_command::eCmdModeSense6); EXPECT_EQ(0x08, controller->GetBuffer()[3]) << "Wrong block descriptor length"; // No block descriptor - cmd[1] = 0x08; + controller->SetCmdByte(1, 0x08); disk->Dispatch(scsi_command::eCmdModeSense6); EXPECT_EQ(0x00, controller->GetBuffer()[2]) << "Wrong device-specific parameter"; @@ -766,50 +653,41 @@ TEST(DiskTest, ModeSense6) EXPECT_EQ(0x80, buf[2]) << "Wrong device-specific parameter"; // Return block descriptor - cmd[1] = 0x00; + controller->SetCmdByte(1, 0x00); // Format page - cmd[2] = 3; + controller->SetCmdByte(2, 3); disk->SetSectorSizeInBytes(1024); disk->Dispatch(scsi_command::eCmdModeSense6); - DiskTest_ValidateFormatPage(controller, 12); + DiskTest_ValidateFormatPage(*controller, 12); // Rigid disk drive page - cmd[2] = 4; + controller->SetCmdByte(2, 4); disk->SetBlockCount(0x12345678); disk->Dispatch(scsi_command::eCmdModeSense6); - DiskTest_ValidateDrivePage(controller, 12); + DiskTest_ValidateDrivePage(*controller, 12); // Cache page - cmd[2] = 8; + controller->SetCmdByte(2, 8); disk->Dispatch(scsi_command::eCmdModeSense6); - DiskTest_ValidateCachePage(controller, 12); + DiskTest_ValidateCachePage(*controller, 12); } TEST(DiskTest, ModeSense10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); - - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); + auto [controller, disk] = CreateDisk(); // Drive must be ready on order to return all data disk->SetReady(true); - cmd[2] = 0x3f; + controller->SetCmdByte(2, 0x3f); // ALLOCATION LENGTH - cmd[8] = 255; + controller->SetCmdByte(8, 255); disk->Dispatch(scsi_command::eCmdModeSense10); EXPECT_EQ(0x08, controller->GetBuffer()[7]) << "Wrong block descriptor length"; // No block descriptor - cmd[1] = 0x08; + controller->SetCmdByte(1, 0x08); disk->Dispatch(scsi_command::eCmdModeSense10); auto& buf = controller->GetBuffer(); EXPECT_EQ(0x00, controller->GetBuffer()[3]) << "Wrong device-specific parameter"; @@ -822,7 +700,7 @@ TEST(DiskTest, ModeSense10) EXPECT_EQ(0x80, buf[3]) << "Wrong device-specific parameter"; // Return short block descriptor - cmd[1] = 0x00; + controller->SetCmdByte(1, 0x00); disk->SetBlockCount(0x1234); disk->SetSectorSizeInBytes(1024); disk->Dispatch(scsi_command::eCmdModeSense10); @@ -835,7 +713,7 @@ TEST(DiskTest, ModeSense10) EXPECT_EQ(1024, GetInt16(buf, 14)); // Return long block descriptor - cmd[1] = 0x10; + controller->SetCmdByte(1, 0x10); disk->SetBlockCount((uint64_t)0xffffffff + 1); disk->Dispatch(scsi_command::eCmdModeSense10); buf = controller->GetBuffer(); @@ -847,62 +725,49 @@ TEST(DiskTest, ModeSense10) EXPECT_EQ(0x00, GetInt16(buf, 14)); EXPECT_EQ(0x00, GetInt16(buf, 20)); EXPECT_EQ(1024, GetInt16(buf, 22)); - cmd[1] = 0x00; + + controller->SetCmdByte(1, 0x00); // Format page - cmd[2] = 3; + controller->SetCmdByte(2, 3); disk->SetSectorSizeInBytes(1024); disk->Dispatch(scsi_command::eCmdModeSense10); - DiskTest_ValidateFormatPage(controller, 16); + DiskTest_ValidateFormatPage(*controller, 16); // Rigid disk drive page - cmd[2] = 4; + controller->SetCmdByte(2, 4); disk->SetBlockCount(0x12345678); disk->Dispatch(scsi_command::eCmdModeSense10); - DiskTest_ValidateDrivePage(controller, 16); + DiskTest_ValidateDrivePage(*controller, 16); // Cache page - cmd[2] = 8; + controller->SetCmdByte(2, 8); disk->Dispatch(scsi_command::eCmdModeSense10); - DiskTest_ValidateCachePage(controller, 16); + DiskTest_ValidateCachePage(*controller, 16); } TEST(DiskTest, SynchronizeCache) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); - - controller->AddDevice(disk); + auto [controller, disk] = CreateDisk(); EXPECT_CALL(*disk, FlushCache); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdSynchronizeCache10); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); EXPECT_CALL(*disk, FlushCache); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdSynchronizeCache16); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(DiskTest, ReadDefectData) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); - - controller->AddDevice(disk); + auto [controller, disk] = CreateDisk(); EXPECT_CALL(*controller, DataIn); disk->Dispatch(scsi_command::eCmdReadDefectData10); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(DiskTest, SectorSize) @@ -951,8 +816,7 @@ TEST(DiskTest, SectorSize) TEST(DiskTest, ConfiguredSectorSize) { DeviceFactory device_factory; - const unordered_set sector_sizes; - MockSCSIHD disk(0, sector_sizes, false); + MockSCSIHD disk(0, {}, false); EXPECT_TRUE(disk.SetConfiguredSectorSize(device_factory, 512)); EXPECT_EQ(512, disk.GetConfiguredSectorSize()); diff --git a/cpp/test/gpiobus_raspberry_test.cpp b/cpp/test/gpiobus_raspberry_test.cpp index a96e4b7c..6b597013 100644 --- a/cpp/test/gpiobus_raspberry_test.cpp +++ b/cpp/test/gpiobus_raspberry_test.cpp @@ -9,7 +9,7 @@ #include "hal/gpiobus_raspberry.h" #include "mocks.h" -#include "stdlib.h" +#include #include "test/test_shared.h" class SetableGpiobusRaspberry : public GPIOBUS_Raspberry @@ -23,9 +23,9 @@ class SetableGpiobusRaspberry : public GPIOBUS_Raspberry { // Level is inverted logic if (!value) { - *level |= (1 << pin); + *level = *level | (1 << pin); } else { - *level &= ~(1 << pin); + *level = *level & ~(1 << pin); } } SetableGpiobusRaspberry() @@ -65,7 +65,7 @@ TEST(GpiobusRaspberry, GetDtRanges) EXPECT_EQ(0x20000000, GPIOBUS_Raspberry::bcm_host_get_peripheral_address()); DeleteTempFile("/proc/device-tree/soc/ranges"); - CleanupAllTempFiles(); + CleanUpAllTempFiles(); } TEST(GpiobusRaspberry, GetDat) diff --git a/cpp/test/host_services_test.cpp b/cpp/test/host_services_test.cpp index bb069a9c..5bcf7ab4 100644 --- a/cpp/test/host_services_test.cpp +++ b/cpp/test/host_services_test.cpp @@ -9,7 +9,6 @@ #include "mocks.h" #include "shared/piscsi_exceptions.h" -#include "controllers/controller_manager.h" #include "devices/host_services.h" using namespace std; @@ -22,82 +21,70 @@ void HostServices_SetUpModePages(map>& pages) TEST(HostServicesTest, TestUnitReady) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto services = CreateDevice(SCHS, *controller); + auto [controller, services] = CreateDevice(SCHS); EXPECT_CALL(*controller, Status()); services->Dispatch(scsi_command::eCmdTestUnitReady); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(HostServicesTest, Inquiry) { - TestInquiry(SCHS, device_type::PROCESSOR, scsi_level::SPC_3, "PiSCSI Host Services ", 0x1f, false); + TestInquiry::Inquiry(SCHS, device_type::processor, scsi_level::spc_3, "PiSCSI Host Services ", 0x1f, false); } TEST(HostServicesTest, StartStopUnit) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto services = CreateDevice(SCHS, *controller); - - auto& cmd = controller->GetCmd(); + auto [controller, services] = CreateDevice(SCHS); + // Required by the bullseye clang++ compiler + auto s = services; // STOP - EXPECT_CALL(*controller, ScheduleShutdown(AbstractController::piscsi_shutdown_mode::STOP_PISCSI)); EXPECT_CALL(*controller, Status()); services->Dispatch(scsi_command::eCmdStartStop); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); // LOAD - cmd[4] = 0x02; - EXPECT_CALL(*controller, ScheduleShutdown(AbstractController::piscsi_shutdown_mode::STOP_PI)); + controller->SetCmdByte(4, 0x02); EXPECT_CALL(*controller, Status()); services->Dispatch(scsi_command::eCmdStartStop); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); // UNLOAD - cmd[4] = 0x03; - EXPECT_CALL(*controller, ScheduleShutdown(AbstractController::piscsi_shutdown_mode::RESTART_PI)); + controller->SetCmdByte(4, 0x03); EXPECT_CALL(*controller, Status()); services->Dispatch(scsi_command::eCmdStartStop); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); // START - cmd[4] = 0x01; - EXPECT_THAT([&] { services->Dispatch(scsi_command::eCmdStartStop); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))); + controller->SetCmdByte(4, 0x01); + EXPECT_THAT([&] { s->Dispatch(scsi_command::eCmdStartStop); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))); } TEST(HostServicesTest, ModeSense6) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto services = CreateDevice(SCHS, *controller); - const unordered_map params; - services->Init(params); + auto [controller, services] = CreateDevice(SCHS); + // Required by the bullseye clang++ compiler + auto s = services; - auto& cmd = controller->GetCmd(); + EXPECT_TRUE(services->Init({})); - EXPECT_THAT([&] { services->Dispatch(scsi_command::eCmdModeSense6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + EXPECT_THAT([&] { s->Dispatch(scsi_command::eCmdModeSense6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Unsupported mode page was returned"; - cmd[2] = 0x20; - EXPECT_THAT([&] { services->Dispatch(scsi_command::eCmdModeSense6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(2, 0x20); + EXPECT_THAT([&] { s->Dispatch(scsi_command::eCmdModeSense6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Block descriptors are not supported"; - cmd[1] = 0x08; + controller->SetCmdByte(1, 0x08); // ALLOCATION LENGTH - cmd[4] = 255; + controller->SetCmdByte(4, 255); EXPECT_CALL(*controller, DataIn()); services->Dispatch(scsi_command::eCmdModeSense6); vector& buffer = controller->GetBuffer(); @@ -111,7 +98,7 @@ TEST(HostServicesTest, ModeSense6) EXPECT_NE(0x00, buffer[10]); // ALLOCATION LENGTH - cmd[4] = 2; + controller->SetCmdByte(4, 2); EXPECT_CALL(*controller, DataIn()); services->Dispatch(scsi_command::eCmdModeSense6); buffer = controller->GetBuffer(); @@ -120,29 +107,26 @@ TEST(HostServicesTest, ModeSense6) TEST(HostServicesTest, ModeSense10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto services = CreateDevice(SCHS, *controller); - const unordered_map params; - services->Init(params); + auto [controller, services] = CreateDevice(SCHS); + // Required by the bullseye clang++ compiler + auto s = services; + + EXPECT_TRUE(services->Init({})); - auto& cmd = controller->GetCmd(); - - EXPECT_THAT([&] { services->Dispatch(scsi_command::eCmdModeSense10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + EXPECT_THAT([&] { s->Dispatch(scsi_command::eCmdModeSense10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Unsupported mode page was returned"; - cmd[2] = 0x20; - EXPECT_THAT([&] { services->Dispatch(scsi_command::eCmdModeSense10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(2, 0x20); + EXPECT_THAT([&] { s->Dispatch(scsi_command::eCmdModeSense10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Block descriptors are not supported"; - cmd[1] = 0x08; + controller->SetCmdByte(1, 0x08); // ALLOCATION LENGTH - cmd[8] = 255; + controller->SetCmdByte(8, 255); EXPECT_CALL(*controller, DataIn()); services->Dispatch(scsi_command::eCmdModeSense10); vector& buffer = controller->GetBuffer(); @@ -156,7 +140,7 @@ TEST(HostServicesTest, ModeSense10) EXPECT_NE(0x00, buffer[14]); // ALLOCATION LENGTH - cmd[8] = 2; + controller->SetCmdByte(8, 2); EXPECT_CALL(*controller, DataIn()); services->Dispatch(scsi_command::eCmdModeSense10); buffer = controller->GetBuffer(); diff --git a/cpp/test/linux_os_stubs.cpp b/cpp/test/linux_os_stubs.cpp index f5885e27..98cdbdb7 100644 --- a/cpp/test/linux_os_stubs.cpp +++ b/cpp/test/linux_os_stubs.cpp @@ -13,11 +13,13 @@ #include #include -#include -#include -#include +#include +#include +#include #include +#ifdef __linux__ #include +#endif #include #include #include @@ -61,4 +63,4 @@ FILE *__wrap_fopen(const char *__restrict __filename, const char *__restrict __m #endif } -} // end extern "C" \ No newline at end of file +} // end extern "C" diff --git a/cpp/test/mocks.h b/cpp/test/mocks.h index f654054d..9b315392 100644 --- a/cpp/test/mocks.h +++ b/cpp/test/mocks.h @@ -81,10 +81,11 @@ public: class MockPhaseHandler : public PhaseHandler { FRIEND_TEST(PhaseHandlerTest, Phases); + FRIEND_TEST(PhaseHandlerTest, ProcessPhase); public: - MOCK_METHOD(phase_t, Process, (int), (override)); + MOCK_METHOD(bool, Process, (int), (override)); MOCK_METHOD(void, Status, (), ()); MOCK_METHOD(void, DataIn, (), ()); MOCK_METHOD(void, DataOut, (), ()); @@ -97,15 +98,16 @@ public: using PhaseHandler::PhaseHandler; }; +inline static const auto mock_bus = make_shared(); + class MockAbstractController : public AbstractController //NOSONAR Having many fields/methods cannot be avoided { + friend class TestInquiry; + friend shared_ptr CreateDevice(piscsi_interface::PbDeviceType, AbstractController&, int); - friend void TestInquiry(piscsi_interface::PbDeviceType, scsi_defs::device_type, scsi_defs::scsi_level, - scsi_defs::scsi_level, const std::string&, int, bool); FRIEND_TEST(AbstractControllerTest, AllocateCmd); FRIEND_TEST(AbstractControllerTest, Reset); - FRIEND_TEST(AbstractControllerTest, ProcessPhase); FRIEND_TEST(AbstractControllerTest, DeviceLunLifeCycle); FRIEND_TEST(AbstractControllerTest, ExtractInitiatorId); FRIEND_TEST(AbstractControllerTest, GetOpcode); @@ -148,10 +150,26 @@ class MockAbstractController : public AbstractController //NOSONAR Having many f FRIEND_TEST(DiskTest, PreventAllowMediumRemoval); FRIEND_TEST(DiskTest, SynchronizeCache); FRIEND_TEST(DiskTest, ReadDefectData); + FRIEND_TEST(DiskTest, StartStopUnit); + FRIEND_TEST(DiskTest, ModeSense6); + FRIEND_TEST(DiskTest, ModeSense10); + FRIEND_TEST(ScsiDaynaportTest, Read); + FRIEND_TEST(ScsiDaynaportTest, Write); + FRIEND_TEST(ScsiDaynaportTest, Read6); + FRIEND_TEST(ScsiDaynaportTest, Write6); + FRIEND_TEST(ScsiDaynaportTest, TestRetrieveStats); + FRIEND_TEST(ScsiDaynaportTest, SetInterfaceMode); + FRIEND_TEST(ScsiDaynaportTest, SetMcastAddr); + FRIEND_TEST(ScsiDaynaportTest, EnableInterface); + FRIEND_TEST(HostServicesTest, StartStopUnit); + FRIEND_TEST(HostServicesTest, ModeSense6); + FRIEND_TEST(HostServicesTest, ModeSense10); + FRIEND_TEST(HostServicesTest, SetUpModePages); + FRIEND_TEST(ScsiPrinterTest, Print); public: - MOCK_METHOD(phase_t, Process, (int), (override)); + MOCK_METHOD(bool, Process, (int), (override)); MOCK_METHOD(int, GetEffectiveLun, (), (const override)); MOCK_METHOD(void, Error, (scsi_defs::sense_key, scsi_defs::asc, scsi_defs::status), (override)); MOCK_METHOD(int, GetInitiatorId, (), (const override)); @@ -163,10 +181,12 @@ public: MOCK_METHOD(void, Command, (), ()); MOCK_METHOD(void, MsgIn, (), ()); MOCK_METHOD(void, MsgOut, (), ()); - MOCK_METHOD(void, ScheduleShutdown, (piscsi_shutdown_mode), (override)); - explicit MockAbstractController(shared_ptr controller_manager, int target_id) - : AbstractController(controller_manager, target_id, 32) { + MockAbstractController() : AbstractController(*mock_bus, 0, 32) {} + explicit MockAbstractController(int target_id) : AbstractController(*mock_bus, target_id, 32) { + AllocateBuffer(512); + } + MockAbstractController(shared_ptr bus, int target_id) : AbstractController(*bus, target_id, 32) { AllocateBuffer(512); } ~MockAbstractController() override = default; @@ -193,10 +213,8 @@ public: MOCK_METHOD(void, Execute, (), ()); using ScsiController::ScsiController; - MockScsiController(shared_ptr controller_manager, int target_id) - : ScsiController(controller_manager, target_id) {} - explicit MockScsiController(shared_ptr controller_manager) - : ScsiController(controller_manager, 0) {} + MockScsiController(shared_ptr bus, int target_id) : ScsiController(*bus, target_id) {} + explicit MockScsiController(shared_ptr bus) : ScsiController(*bus, 0) {} ~MockScsiController() override = default; }; @@ -233,6 +251,7 @@ class MockPrimaryDevice : public PrimaryDevice public: MOCK_METHOD(vector, InquiryInternal, (), (const)); + MOCK_METHOD(void, FlushCache, (), ()); explicit MockPrimaryDevice(int lun) : PrimaryDevice(UNDEFINED, lun) {} ~MockPrimaryDevice() override = default; @@ -247,8 +266,8 @@ class MockModePageDevice : public ModePageDevice public: MOCK_METHOD(vector, InquiryInternal, (), (const)); - MOCK_METHOD(int, ModeSense6, (const vector&, vector&), (const override)); - MOCK_METHOD(int, ModeSense10, (const vector&, vector&), (const override)); + MOCK_METHOD(int, ModeSense6, (span, vector&), (const override)); + MOCK_METHOD(int, ModeSense10, (span, vector&), (const override)); MockModePageDevice() : ModePageDevice(UNDEFINED, 0) {} ~MockModePageDevice() override = default; @@ -290,8 +309,8 @@ public: MOCK_METHOD(vector, InquiryInternal, (), (const)); MOCK_METHOD(void, Open, (), (override)); - MOCK_METHOD(int, ModeSense6, (const vector&, vector&), (const override)); - MOCK_METHOD(int, ModeSense10, (const vector&, vector&), (const override)); + MOCK_METHOD(int, ModeSense6, (span, vector&), (const override)); + MOCK_METHOD(int, ModeSense10, (span, vector&), (const override)); MOCK_METHOD(void, SetUpModePages, ((map>&), int, bool), (const override)); MockStorageDevice() : StorageDevice(UNDEFINED, 0) {} @@ -391,16 +410,6 @@ class MockHostServices : public HostServices using HostServices::HostServices; }; -class MockCommandContext : public CommandContext -{ -public: - - MockCommandContext() { - SetFd(open("/dev/null", O_WRONLY)); - } - ~MockCommandContext() = default; -}; - class MockPiscsiExecutor : public PiscsiExecutor { public: diff --git a/cpp/test/mode_page_device_test.cpp b/cpp/test/mode_page_device_test.cpp index 31d0a2a9..8515ec55 100644 --- a/cpp/test/mode_page_device_test.cpp +++ b/cpp/test/mode_page_device_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -33,8 +33,8 @@ TEST(ModePageDeviceTest, AddModePages) // Page 0 cdb[2] = 0x00; EXPECT_THAT([&] { device.AddModePages(cdb, buf, 0, 12, 255); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Data were returned for non-existing mode page 0"; // All pages, non changeable @@ -42,8 +42,8 @@ TEST(ModePageDeviceTest, AddModePages) EXPECT_EQ(0, device.AddModePages(cdb, buf, 0, 0, 255)); EXPECT_EQ(3, device.AddModePages(cdb, buf, 0, 3, 255)); EXPECT_THAT([&] { device.AddModePages(cdb, buf, 0, 12, -1); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Maximum size was ignored"; // All pages, changeable @@ -51,8 +51,8 @@ TEST(ModePageDeviceTest, AddModePages) EXPECT_EQ(0, device.AddModePages(cdb, buf, 0, 0, 255)); EXPECT_EQ(3, device.AddModePages(cdb, buf, 0, 3, 255)); EXPECT_THAT([&] { device.AddModePages(cdb, buf, 0, 12, -1); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Maximum size was ignored"; } @@ -80,12 +80,9 @@ TEST(ModePageDeviceTest, AddVendorPage) TEST(ModePageDeviceTest, ModeSense6) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); + auto controller = make_shared(0); auto device = make_shared>(); - const unordered_map params; - device->Init(params); + EXPECT_TRUE(device->Init({})); controller->AddDevice(device); @@ -95,12 +92,9 @@ TEST(ModePageDeviceTest, ModeSense6) TEST(ModePageDeviceTest, ModeSense10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); + auto controller = make_shared(0); auto device = make_shared>(); - const unordered_map params; - device->Init(params); + EXPECT_TRUE(device->Init({})); controller->AddDevice(device); @@ -115,57 +109,47 @@ TEST(ModePageDeviceTest, ModeSelect) vector buf; EXPECT_THAT([&] { device.ModeSelect(scsi_command::eCmdModeSelect6, cmd, buf, 0); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_COMMAND_OPERATION_CODE)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_command_operation_code)))) << "Unexpected MODE SELECT(6) default implementation"; EXPECT_THAT([&] { device.ModeSelect(scsi_command::eCmdModeSelect10, cmd, buf, 0); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_COMMAND_OPERATION_CODE)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_command_operation_code)))) << "Unexpected MODE SELECT(10) default implementation"; } TEST(ModePageDeviceTest, ModeSelect6) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); + auto controller = make_shared(0); auto device = make_shared(); - const unordered_map params; - device->Init(params); + EXPECT_TRUE(device->Init({})); controller->AddDevice(device); - auto& cmd = controller->GetCmd(); - EXPECT_CALL(*controller, DataOut()); device->Dispatch(scsi_command::eCmdModeSelect6); - cmd[1] = 0x01; + controller->SetCmdByte(1, 0x01); EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdModeSelect6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Saving parameters is not supported by base class"; } TEST(ModePageDeviceTest, ModeSelect10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); + auto controller = make_shared(0); auto device = make_shared(); - const unordered_map params; - device->Init(params); + EXPECT_TRUE(device->Init({})); controller->AddDevice(device); - auto& cmd = controller->GetCmd(); - EXPECT_CALL(*controller, DataOut()); device->Dispatch(scsi_command::eCmdModeSelect10); - cmd[1] = 0x01; + controller->SetCmdByte(1, 0x01); EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdModeSelect10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Saving parameters is not supported for by base class"; } diff --git a/cpp/test/network_util_test.cpp b/cpp/test/network_util_test.cpp new file mode 100644 index 00000000..d6f49d62 --- /dev/null +++ b/cpp/test/network_util_test.cpp @@ -0,0 +1,33 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator PiSCSI +// for Raspberry Pi +// +// Copyright (C) 2023 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include + +#include +#include +#include "shared/network_util.h" + +using namespace network_util; + +TEST(NetworkUtilTest, IsInterfaceUp) +{ + EXPECT_FALSE(IsInterfaceUp("foo_bar")); +} + +TEST(NetworkUtilTest, GetNetworkInterfaces) +{ + EXPECT_FALSE(GetNetworkInterfaces().empty()); +} + +TEST(NetworkUtilTest, ResolveHostName) +{ + sockaddr_in server_addr = {}; + EXPECT_FALSE(ResolveHostName("foo.foobar", &server_addr)); + EXPECT_TRUE(ResolveHostName("127.0.0.1", &server_addr)); +} diff --git a/cpp/test/phase_handler_test.cpp b/cpp/test/phase_handler_test.cpp index 7af4f6b1..70532b54 100644 --- a/cpp/test/phase_handler_test.cpp +++ b/cpp/test/phase_handler_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -94,3 +94,50 @@ TEST(PhaseHandlerTest, Phases) EXPECT_FALSE(handler.IsDataOut()); EXPECT_FALSE(handler.IsMsgIn()); } + +TEST(PhaseHandlerTest, ProcessPhase) +{ + MockPhaseHandler handler; + + handler.SetPhase(phase_t::selection); + EXPECT_CALL(handler, Selection); + handler.ProcessPhase(); + + handler.SetPhase(phase_t::busfree); + EXPECT_CALL(handler, BusFree); + handler.ProcessPhase(); + + handler.SetPhase(phase_t::datain); + EXPECT_CALL(handler, DataIn); + handler.ProcessPhase(); + + handler.SetPhase(phase_t::dataout); + EXPECT_CALL(handler, DataOut); + handler.ProcessPhase(); + + handler.SetPhase(phase_t::command); + EXPECT_CALL(handler, Command); + handler.ProcessPhase(); + + handler.SetPhase(phase_t::status); + EXPECT_CALL(handler, Status); + handler.ProcessPhase(); + + handler.SetPhase(phase_t::msgin); + EXPECT_CALL(handler, MsgIn); + handler.ProcessPhase(); + + handler.SetPhase(phase_t::msgout); + EXPECT_CALL(handler, MsgOut); + handler.ProcessPhase(); + + handler.SetPhase(phase_t::reselection); + EXPECT_THAT([&] { handler.ProcessPhase(); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::aborted_command), + Property(&scsi_exception::get_asc, asc::no_additional_sense_information)))); + + handler.SetPhase(phase_t::reserved); + EXPECT_THAT([&] { handler.ProcessPhase(); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::aborted_command), + Property(&scsi_exception::get_asc, asc::no_additional_sense_information)))); +} diff --git a/cpp/test/piscsi_exceptions_test.cpp b/cpp/test/piscsi_exceptions_test.cpp index 6def9716..a0b338e5 100644 --- a/cpp/test/piscsi_exceptions_test.cpp +++ b/cpp/test/piscsi_exceptions_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -36,18 +36,18 @@ TEST(PiscsiExceptionsTest, FileNotFoundException) TEST(PiscsiExceptionsTest, ScsiErrorException) { try { - throw scsi_exception(sense_key::UNIT_ATTENTION); + throw scsi_exception(sense_key::unit_attention); } catch(const scsi_exception& e) { - EXPECT_EQ(sense_key::UNIT_ATTENTION, e.get_sense_key()); - EXPECT_EQ(asc::NO_ADDITIONAL_SENSE_INFORMATION, e.get_asc()); + EXPECT_EQ(sense_key::unit_attention, e.get_sense_key()); + EXPECT_EQ(asc::no_additional_sense_information, e.get_asc()); } try { - throw scsi_exception(sense_key::UNIT_ATTENTION, asc::LBA_OUT_OF_RANGE); + throw scsi_exception(sense_key::illegal_request, asc::lba_out_of_range); } catch(const scsi_exception& e) { - EXPECT_EQ(sense_key::UNIT_ATTENTION, e.get_sense_key()); - EXPECT_EQ(asc::LBA_OUT_OF_RANGE, e.get_asc()); + EXPECT_EQ(sense_key::illegal_request, e.get_sense_key()); + EXPECT_EQ(asc::lba_out_of_range, e.get_asc()); } } diff --git a/cpp/test/piscsi_executor_test.cpp b/cpp/test/piscsi_executor_test.cpp index 9b038c49..a4a917af 100644 --- a/cpp/test/piscsi_executor_test.cpp +++ b/cpp/test/piscsi_executor_test.cpp @@ -3,11 +3,10 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "spdlog/spdlog.h" #include "mocks.h" #include "shared/protobuf_util.h" #include "shared/piscsi_exceptions.h" @@ -24,224 +23,194 @@ using namespace filesystem; using namespace piscsi_interface; using namespace protobuf_util; -const extern bool enable_logging; - -// This test fixture is required in order to reset the log level changed by the log level tests -class PiscsiExecutorTest : public Test -{ - void TearDown() override { - spdlog::set_level(enable_logging ? spdlog::level::trace : spdlog::level::off); - } -}; - -TEST_F(PiscsiExecutorTest, ProcessDeviceCmd) +TEST(PiscsiExecutorTest, ProcessDeviceCmd) { const int ID = 3; const int LUN = 0; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, ID); + ControllerManager controller_manager; + MockAbstractController controller(bus, ID); PiscsiImage piscsi_image; - auto executor = make_shared(piscsi_image, *controller_manager); + auto executor = make_shared(piscsi_image, *bus, controller_manager); PbDeviceDefinition definition; PbCommand command; - MockCommandContext context; + CommandContext context(command, "", ""); definition.set_id(8); definition.set_unit(32); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Invalid ID and LUN must fail"; + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, true)) << "Invalid ID and LUN must fail"; definition.set_unit(LUN); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Invalid ID must fail"; + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, true)) << "Invalid ID must fail"; definition.set_id(ID); definition.set_unit(32); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Invalid LUN must fail"; + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, true)) << "Invalid LUN must fail"; definition.set_unit(LUN); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Unknown operation must fail"; + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, true)) << "Unknown operation must fail"; command.set_operation(ATTACH); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Operation for unknown device type must fail"; + CommandContext context_attach(command, "", ""); + EXPECT_FALSE(executor->ProcessDeviceCmd(context_attach, definition, true)) << "Operation for unknown device type must fail"; auto device1 = make_shared(LUN); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device1)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device1)); definition.set_type(SCHS); command.set_operation(INSERT); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Operation unsupported by device must fail"; - controller_manager->DeleteAllControllers(); + CommandContext context_insert1(command, "", ""); + EXPECT_FALSE(executor->ProcessDeviceCmd(context_insert1, definition, true)) << "Operation unsupported by device must fail"; + controller_manager.DeleteAllControllers(); definition.set_type(SCRM); auto device2 = make_shared(LUN); device2->SetRemovable(true); device2->SetProtectable(true); device2->SetReady(true); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device2)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device2)); - command.set_operation(ATTACH); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "ID and LUN already exist"; + EXPECT_FALSE(executor->ProcessDeviceCmd(context_attach, definition, true)) << "ID and LUN already exist"; command.set_operation(START); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); + CommandContext context_start(command, "", ""); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_start, definition, true)); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_start, definition, false)); command.set_operation(PROTECT); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); + CommandContext context_protect(command, "", ""); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_protect, definition, true)); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_protect, definition, false)); command.set_operation(UNPROTECT); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); + CommandContext context_unprotect(command, "", ""); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_unprotect, definition, true)); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_unprotect, definition, false)); command.set_operation(STOP); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); + CommandContext context_stop(command, "", ""); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_stop, definition, true)); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_stop, definition, false)); command.set_operation(EJECT); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); + CommandContext context_eject(command, "", ""); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_eject, definition, true)); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_eject, definition, false)); command.set_operation(INSERT); SetParam(definition, "file", "filename"); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Non-existing file"; - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, false)) << "Non-existing file"; + CommandContext context_insert2(command, "", ""); + EXPECT_FALSE(executor->ProcessDeviceCmd(context_insert2, definition, true)) << "Non-existing file"; + EXPECT_FALSE(executor->ProcessDeviceCmd(context_insert2, definition, false)) << "Non-existing file"; command.set_operation(DETACH); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device2)); - - command.set_operation(CHECK_AUTHENTICATION); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); - - command.set_operation(NO_OPERATION); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); - - // The operations below are not related to a device - - command.set_operation(DETACH_ALL); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); - - command.set_operation(RESERVE_IDS); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); - - command.set_operation(CREATE_IMAGE); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); - - command.set_operation(DELETE_IMAGE); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); - - command.set_operation(RENAME_IMAGE); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); - - command.set_operation(COPY_IMAGE); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); - - command.set_operation(PROTECT_IMAGE); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); - - command.set_operation(UNPROTECT_IMAGE); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); + CommandContext context_detach(command, "", ""); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_detach, definition, true)); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_detach, definition, false)); } -TEST_F(PiscsiExecutorTest, ProcessCmd) +TEST(PiscsiExecutorTest, ProcessCmd) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + ControllerManager controller_manager; + MockAbstractController controller(bus, 0); PiscsiImage piscsi_image; - auto executor = make_shared(piscsi_image, *controller_manager); - PbCommand command1; - PbCommand command2; - MockCommandContext context; + auto executor = make_shared(piscsi_image, *bus, controller_manager); - command1.set_operation(DETACH_ALL); - EXPECT_TRUE(executor->ProcessCmd(context, command1)); + PbCommand command_detach_all; + command_detach_all.set_operation(DETACH_ALL); + CommandContext context_detach_all(command_detach_all, "", ""); + EXPECT_TRUE(executor->ProcessCmd(context_detach_all)); - command1.set_operation(RESERVE_IDS); - SetParam(command1, "ids", "2,3"); - EXPECT_TRUE(executor->ProcessCmd(context, command1)); + PbCommand command_reserve_ids1; + command_reserve_ids1.set_operation(RESERVE_IDS); + SetParam(command_reserve_ids1, "ids", "2,3"); + CommandContext context_reserve_ids1(command_reserve_ids1, "", ""); + EXPECT_TRUE(executor->ProcessCmd(context_reserve_ids1)); const unordered_set ids = executor->GetReservedIds(); EXPECT_EQ(2, ids.size()); - EXPECT_NE(ids.end(), ids.find(2)); - EXPECT_NE(ids.end(), ids.find(3)); - command2.set_operation(RESERVE_IDS); - EXPECT_TRUE(executor->ProcessCmd(context, command2)); + EXPECT_TRUE(ids.contains(2)); + EXPECT_TRUE(ids.contains(3)); + + PbCommand command_reserve_ids2; + command_reserve_ids2.set_operation(RESERVE_IDS); + CommandContext context_reserve_ids2(command_reserve_ids2, "", ""); + EXPECT_TRUE(executor->ProcessCmd(context_reserve_ids2)); EXPECT_TRUE(executor->GetReservedIds().empty()); - SetParam(command2, "ids", "-1"); - EXPECT_FALSE(executor->ProcessCmd(context, command2)); + PbCommand command_reserve_ids3; + command_reserve_ids3.set_operation(RESERVE_IDS); + SetParam(command_reserve_ids3, "ids", "-1"); + CommandContext context_reserve_ids3(command_reserve_ids3, "", ""); + EXPECT_FALSE(executor->ProcessCmd(context_reserve_ids3)); EXPECT_TRUE(executor->GetReservedIds().empty()); - command1.set_operation(NO_OPERATION); - EXPECT_TRUE(executor->ProcessCmd(context, command1)); + PbCommand command_no_operation; + command_no_operation.set_operation(NO_OPERATION); + CommandContext context_no_operation(command_no_operation, "", ""); + EXPECT_TRUE(executor->ProcessCmd(context_no_operation)); - command1.set_operation(ATTACH); - auto device = command1.add_devices(); - device->set_type(SCHS); - device->set_id(-1); - EXPECT_FALSE(executor->ProcessCmd(context, command1)); - device->set_id(0); - device->set_unit(1); - EXPECT_FALSE(executor->ProcessCmd(context, command1)) << "LUN 0 is missing"; - device->set_unit(0); - EXPECT_TRUE(executor->ProcessCmd(context, command1)); + PbCommand command_attach1; + command_attach1.set_operation(ATTACH); + auto device1 = command_attach1.add_devices(); + device1->set_type(SCHS); + device1->set_id(-1); + CommandContext context_attach1(command_attach1, "", ""); + EXPECT_FALSE(executor->ProcessCmd(context_attach1)); + + PbCommand command_attach2; + command_attach2.set_operation(ATTACH); + auto device2 = command_attach2.add_devices(); + device2->set_type(SCHS); + device2->set_id(0); + device2->set_unit(1); + CommandContext context_attach2(command_attach2, "", ""); + EXPECT_FALSE(executor->ProcessCmd(context_attach2)) << "LUN 0 is missing"; // The operations below must fail because of missing parameters. // The respective functionality is tested in piscsi_image_test.cpp. - command1.set_operation(CREATE_IMAGE); - EXPECT_FALSE(executor->ProcessCmd(context, command1)); + PbCommand command; - command1.set_operation(DELETE_IMAGE); - EXPECT_FALSE(executor->ProcessCmd(context, command1)); + command.set_operation(CREATE_IMAGE); + CommandContext context_create_image(command, "", ""); + EXPECT_FALSE(executor->ProcessCmd(context_create_image)); - command1.set_operation(RENAME_IMAGE); - EXPECT_FALSE(executor->ProcessCmd(context, command1)); + command.set_operation(DELETE_IMAGE); + CommandContext context_delete_image(command, "", ""); + EXPECT_FALSE(executor->ProcessCmd(context_delete_image)); - command1.set_operation(COPY_IMAGE); - EXPECT_FALSE(executor->ProcessCmd(context, command1)); + command.set_operation(RENAME_IMAGE); + CommandContext context_rename_image(command, "", ""); + EXPECT_FALSE(executor->ProcessCmd(context_rename_image)); - command1.set_operation(PROTECT_IMAGE); - EXPECT_FALSE(executor->ProcessCmd(context, command1)); + command.set_operation(COPY_IMAGE); + CommandContext context_copy_image(command, "", ""); + EXPECT_FALSE(executor->ProcessCmd(context_copy_image)); - command1.set_operation(UNPROTECT_IMAGE); - EXPECT_FALSE(executor->ProcessCmd(context, command1)); + command.set_operation(PROTECT_IMAGE); + CommandContext context_protect_image(command, "", ""); + EXPECT_FALSE(executor->ProcessCmd(context_protect_image)); + + command.set_operation(UNPROTECT_IMAGE); + CommandContext context_unprotect_image(command, "", ""); + EXPECT_FALSE(executor->ProcessCmd(context_unprotect_image)); } -TEST_F(PiscsiExecutorTest, SetLogLevel) -{ - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - - EXPECT_TRUE(executor.SetLogLevel("trace")); - EXPECT_TRUE(executor.SetLogLevel("debug")); - EXPECT_TRUE(executor.SetLogLevel("info")); - EXPECT_TRUE(executor.SetLogLevel("warn")); - EXPECT_TRUE(executor.SetLogLevel("err")); - EXPECT_TRUE(executor.SetLogLevel("off")); - EXPECT_FALSE(executor.SetLogLevel("xyz")); -} - -TEST_F(PiscsiExecutorTest, Attach) +TEST(PiscsiExecutorTest, Attach) { const int ID = 3; const int LUN = 0; DeviceFactory device_factory; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); PbDeviceDefinition definition; - MockCommandContext context; + PbCommand command; + CommandContext context(command, "", ""); definition.set_unit(32); EXPECT_FALSE(executor.Attach(context, definition, false)); @@ -258,7 +227,7 @@ TEST_F(PiscsiExecutorTest, Attach) definition.set_type(PbDeviceType::SCHS); EXPECT_TRUE(executor.Attach(context, definition, false)); - controller_manager->DeleteAllControllers(); + controller_manager.DeleteAllControllers(); definition.set_type(PbDeviceType::SCHD); EXPECT_FALSE(executor.Attach(context, definition, false)) << "Drive without sectors not rejected"; @@ -277,19 +246,19 @@ TEST_F(PiscsiExecutorTest, Attach) EXPECT_FALSE(executor.Attach(context, definition, false)) << "Drive with non-existing image file not rejected"; path filename = CreateTempFile(1); - SetParam(definition, "file", filename.c_str()); + SetParam(definition, "file", filename.string()); EXPECT_FALSE(executor.Attach(context, definition, false)) << "Too small image file not rejected"; remove(filename); filename = CreateTempFile(512); - SetParam(definition, "file", filename.c_str()); + SetParam(definition, "file", filename.string()); bool result = executor.Attach(context, definition, false); remove(filename); EXPECT_TRUE(result); - controller_manager->DeleteAllControllers(); + controller_manager.DeleteAllControllers(); filename = CreateTempFile(513); - SetParam(definition, "file", filename.c_str()); + SetParam(definition, "file", filename.string()); result = executor.Attach(context, definition, false); remove(filename); EXPECT_TRUE(result); @@ -297,7 +266,7 @@ TEST_F(PiscsiExecutorTest, Attach) definition.set_type(PbDeviceType::SCCD); definition.set_unit(LUN + 1); filename = CreateTempFile(2048); - SetParam(definition, "file", filename.c_str()); + SetParam(definition, "file", filename.string()); result = executor.Attach(context, definition, false); remove(filename); EXPECT_TRUE(result); @@ -306,25 +275,26 @@ TEST_F(PiscsiExecutorTest, Attach) definition.set_unit(LUN + 2); SetParam(definition, "read_only", "true"); filename = CreateTempFile(4096); - SetParam(definition, "file", filename.c_str()); + SetParam(definition, "file", filename.string()); result = executor.Attach(context, definition, false); remove(filename); EXPECT_TRUE(result); - controller_manager->DeleteAllControllers(); + controller_manager.DeleteAllControllers(); } -TEST_F(PiscsiExecutorTest, Insert) +TEST(PiscsiExecutorTest, Insert) { DeviceFactory device_factory; - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - PbDeviceDefinition definition; - MockCommandContext context; - auto device = device_factory.CreateDevice(SCRM, 0, "test"); + auto bus = make_shared(); + ControllerManager controller_manager; + auto [controller, device] = CreateDevice(SCHD); + PiscsiImage piscsi_image; + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PbDeviceDefinition definition; + PbCommand command; + CommandContext context(command, "", ""); device->SetRemoved(false); EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "Medium is not removed"; @@ -358,18 +328,18 @@ TEST_F(PiscsiExecutorTest, Insert) EXPECT_FALSE(executor.Insert(context, definition, device, false)); path filename = CreateTempFile(1); - SetParam(definition, "file", filename.c_str()); + SetParam(definition, "file", filename.string()); EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "Too small image file not rejected"; remove(filename); filename = CreateTempFile(512); - SetParam(definition, "file", filename.c_str()); + SetParam(definition, "file", filename.string()); const bool result = executor.Insert(context, definition, device, false); remove(filename); EXPECT_TRUE(result); } -TEST_F(PiscsiExecutorTest, Detach) +TEST(PiscsiExecutorTest, Detach) { const int ID = 3; const int LUN1 = 0; @@ -377,68 +347,72 @@ TEST_F(PiscsiExecutorTest, Detach) DeviceFactory device_factory; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - MockCommandContext context; + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PbCommand command; + CommandContext context(command, "", ""); auto device1 = device_factory.CreateDevice(SCHS, LUN1, ""); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device1)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device1)); auto device2 = device_factory.CreateDevice(SCHS, LUN2, ""); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device2)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device2)); - auto d1 = controller_manager->GetDeviceByIdAndLun(ID, LUN1); - EXPECT_FALSE(executor.Detach(context, d1, false)) << "LUNs > 0 have to be detached first"; - auto d2 = controller_manager->GetDeviceByIdAndLun(ID, LUN2); - EXPECT_TRUE(executor.Detach(context, d2, false)); - EXPECT_TRUE(executor.Detach(context, d1, false)); - EXPECT_TRUE(controller_manager->GetAllDevices().empty()); + auto d1 = controller_manager.GetDeviceForIdAndLun(ID, LUN1); + EXPECT_FALSE(executor.Detach(context, *d1, false)) << "LUNs > 0 have to be detached first"; + auto d2 = controller_manager.GetDeviceForIdAndLun(ID, LUN2); + EXPECT_TRUE(executor.Detach(context, *d2, false)); + EXPECT_TRUE(executor.Detach(context, *d1, false)); + EXPECT_TRUE(controller_manager.GetAllDevices().empty()); - EXPECT_FALSE(executor.Detach(context, d1, false)); + EXPECT_FALSE(executor.Detach(context, *d1, false)); } -TEST_F(PiscsiExecutorTest, DetachAll) +TEST(PiscsiExecutorTest, DetachAll) { const int ID = 4; DeviceFactory device_factory; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); auto device = device_factory.CreateDevice(SCHS, 0, ""); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device)); - EXPECT_NE(nullptr, controller_manager->FindController(ID)); - EXPECT_FALSE(controller_manager->GetAllDevices().empty()); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device)); + EXPECT_TRUE(controller_manager.HasController(ID)); + EXPECT_FALSE(controller_manager.GetAllDevices().empty()); executor.DetachAll(); - EXPECT_EQ(nullptr, controller_manager->FindController(ID)); - EXPECT_TRUE(controller_manager->GetAllDevices().empty()); + EXPECT_EQ(nullptr, controller_manager.FindController(ID)); + EXPECT_TRUE(controller_manager.GetAllDevices().empty()); } -TEST_F(PiscsiExecutorTest, ShutDown) +TEST(PiscsiExecutorTest, ShutDown) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - MockCommandContext context; + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PbCommand command; + command.set_operation(SHUT_DOWN); + CommandContext context(command, "", ""); EXPECT_FALSE(executor.ShutDown(context, "")); EXPECT_FALSE(executor.ShutDown(context, "xyz")); + + EXPECT_FALSE(executor.ShutDown(context, "system")) << "Only available for the root user"; + EXPECT_FALSE(executor.ShutDown(context, "reboot")) << "Only available for the root user"; EXPECT_TRUE(executor.ShutDown(context, "rascsi")); - EXPECT_FALSE(executor.ShutDown(context, "system")); - EXPECT_FALSE(executor.ShutDown(context, "reboot")); } -TEST_F(PiscsiExecutorTest, SetReservedIds) +TEST(PiscsiExecutorTest, SetReservedIds) { DeviceFactory device_factory; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); string error = executor.SetReservedIds("xyz"); EXPECT_FALSE(error.empty()); @@ -460,61 +434,85 @@ TEST_F(PiscsiExecutorTest, SetReservedIds) EXPECT_TRUE(error.empty()); unordered_set reserved_ids = executor.GetReservedIds(); EXPECT_EQ(5, reserved_ids.size()); - EXPECT_NE(reserved_ids.end(), reserved_ids.find(1)); - EXPECT_NE(reserved_ids.end(), reserved_ids.find(2)); - EXPECT_NE(reserved_ids.end(), reserved_ids.find(3)); - EXPECT_NE(reserved_ids.end(), reserved_ids.find(5)); - EXPECT_NE(reserved_ids.end(), reserved_ids.find(7)); + EXPECT_TRUE(reserved_ids.contains(1)); + EXPECT_TRUE(reserved_ids.contains(2)); + EXPECT_TRUE(reserved_ids.contains(3)); + EXPECT_TRUE(reserved_ids.contains(5)); + EXPECT_TRUE(reserved_ids.contains(7)); auto device = device_factory.CreateDevice(SCHS, 0, ""); - EXPECT_TRUE(controller_manager->AttachToScsiController(5, device)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, 5, device)); error = executor.SetReservedIds("5"); EXPECT_FALSE(error.empty()); } -TEST_F(PiscsiExecutorTest, ValidateImageFile) +TEST(PiscsiExecutorTest, ValidateImageFile) { DeviceFactory device_factory; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - MockCommandContext context; + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PbCommand command; + CommandContext context(command, "", ""); - string full_path; auto device = dynamic_pointer_cast(device_factory.CreateDevice(SCHD, 0, "test")); - EXPECT_TRUE(executor.ValidateImageFile(context, *device, "", full_path)); - EXPECT_TRUE(full_path.empty()); + EXPECT_TRUE(executor.ValidateImageFile(context, *device, "")); - EXPECT_FALSE(executor.ValidateImageFile(context, *device, "/non_existing_file", full_path)); - EXPECT_TRUE(full_path.empty()); + EXPECT_FALSE(executor.ValidateImageFile(context, *device, "/non_existing_file")); } -TEST_F(PiscsiExecutorTest, ValidateLunSetup) +TEST(PiscsiExecutorTest, PrintCommand) +{ + auto bus = make_shared(); + ControllerManager controller_manager; + PiscsiImage piscsi_image; + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PbDeviceDefinition definition; + + PbCommand command; + string s = executor.PrintCommand(command, definition); + EXPECT_NE(s.find("operation="), string::npos); + EXPECT_EQ(s.find("key1=value1"), string::npos); + EXPECT_EQ(s.find("key2=value2"), string::npos); + + SetParam(command, "key1", "value1"); + s = executor.PrintCommand(command, definition); + EXPECT_NE(s.find("operation="), string::npos); + EXPECT_NE(s.find("key1=value1"), string::npos); + + SetParam(command, "key2", "value2"); + s = executor.PrintCommand(command, definition); + EXPECT_NE(s.find("operation="), string::npos); + EXPECT_NE(s.find("key1=value1"), string::npos); + EXPECT_NE(s.find("key2=value2"), string::npos); +} + +TEST(PiscsiExecutorTest, EnsureLun0) { DeviceFactory device_factory; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); PbCommand command; auto device1 = command.add_devices(); device1->set_unit(0); - string error = executor.ValidateLunSetup(command); + string error = executor.EnsureLun0(command); EXPECT_TRUE(error.empty()); device1->set_unit(1); - error = executor.ValidateLunSetup(command); + error = executor.EnsureLun0(command); EXPECT_FALSE(error.empty()); auto device2 = device_factory.CreateDevice(SCHS, 0, ""); - EXPECT_TRUE(controller_manager->AttachToScsiController(0, device2)); - error = executor.ValidateLunSetup(command); + EXPECT_TRUE(controller_manager.AttachToController(*bus, 0, device2)); + error = executor.EnsureLun0(command); EXPECT_TRUE(error.empty()); } -TEST_F(PiscsiExecutorTest, VerifyExistingIdAndLun) +TEST(PiscsiExecutorTest, VerifyExistingIdAndLun) { const int ID = 1; const int LUN1 = 0; @@ -522,25 +520,27 @@ TEST_F(PiscsiExecutorTest, VerifyExistingIdAndLun) DeviceFactory device_factory; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - MockCommandContext context; + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PbCommand command; + CommandContext context(command, "", ""); EXPECT_FALSE(executor.VerifyExistingIdAndLun(context, ID, LUN1)); auto device = device_factory.CreateDevice(SCHS, LUN1, ""); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device)); EXPECT_TRUE(executor.VerifyExistingIdAndLun(context, ID, LUN1)); EXPECT_FALSE(executor.VerifyExistingIdAndLun(context, ID, LUN2)); } -TEST_F(PiscsiExecutorTest, CreateDevice) +TEST(PiscsiExecutorTest, CreateDevice) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - MockCommandContext context; + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PbCommand command; + CommandContext context(command, "", ""); EXPECT_EQ(nullptr, executor.CreateDevice(context, UNDEFINED, 0, "")); #pragma GCC diagnostic push @@ -551,13 +551,14 @@ TEST_F(PiscsiExecutorTest, CreateDevice) EXPECT_NE(nullptr, executor.CreateDevice(context, SCHS, 0, "")); } -TEST_F(PiscsiExecutorTest, SetSectorSize) +TEST(PiscsiExecutorTest, SetSectorSize) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - MockCommandContext context; + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PbCommand command; + CommandContext context(command, "", ""); unordered_set sizes; auto hd = make_shared(0, sizes, false); @@ -570,13 +571,14 @@ TEST_F(PiscsiExecutorTest, SetSectorSize) EXPECT_TRUE(executor.SetSectorSize(context, hd, 512)); } -TEST_F(PiscsiExecutorTest, ValidateOperationAgainstDevice) +TEST(PiscsiExecutorTest, ValidateOperationAgainstDevice) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - MockCommandContext context; + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PbCommand command; + CommandContext context(command, "", ""); auto device = make_shared(0); @@ -623,13 +625,14 @@ TEST_F(PiscsiExecutorTest, ValidateOperationAgainstDevice) EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, *device, UNPROTECT)); } -TEST_F(PiscsiExecutorTest, ValidateIdAndLun) +TEST(PiscsiExecutorTest, ValidateIdAndLun) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - MockCommandContext context; + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PbCommand command; + CommandContext context(command, "", ""); EXPECT_FALSE(executor.ValidateIdAndLun(context, -1, 0)); EXPECT_FALSE(executor.ValidateIdAndLun(context, 8, 0)); @@ -639,13 +642,14 @@ TEST_F(PiscsiExecutorTest, ValidateIdAndLun) EXPECT_TRUE(executor.ValidateIdAndLun(context, 7, 31)); } -TEST_F(PiscsiExecutorTest, SetProductData) +TEST(PiscsiExecutorTest, SetProductData) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - MockCommandContext context; + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PbCommand command; + CommandContext context(command, "", ""); PbDeviceDefinition definition; auto device = make_shared(0); diff --git a/cpp/test/piscsi_image_test.cpp b/cpp/test/piscsi_image_test.cpp index ac48aa2b..aa5bbdab 100644 --- a/cpp/test/piscsi_image_test.cpp +++ b/cpp/test/piscsi_image_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -35,104 +35,129 @@ TEST(PiscsiImageTest, SetGetDefaultFolder) TEST(PiscsiImageTest, CreateImage) { - MockCommandContext context; - PbCommand command; PiscsiImage image; StorageDevice::UnreserveAll(); - EXPECT_FALSE(image.CreateImage(context, command)) << "Filename must be reported as missing"; + PbCommand command1; + CommandContext context1(command1, "", ""); + EXPECT_FALSE(image.CreateImage(context1)) << "Filename must be reported as missing"; - SetParam(command, "file", "/a/b/c/filename"); - EXPECT_FALSE(image.CreateImage(context, command)) << "Depth must be reported as invalid"; + PbCommand command2; + SetParam(command2, "file", "/a/b/c/filename"); + CommandContext context2(command2, "", ""); + EXPECT_FALSE(image.CreateImage(context2)) << "Depth must be reported as invalid"; - SetParam(command, "file", "filename"); - SetParam(command, "size", "-1"); - EXPECT_FALSE(image.CreateImage(context, command)) << "Size must be reported as invalid"; + PbCommand command3; + SetParam(command3, "file", "filename"); + SetParam(command3, "size", "-1"); + CommandContext context3(command3, "", ""); + EXPECT_FALSE(image.CreateImage(context3)) << "Size must be reported as invalid"; - SetParam(command, "size", "1"); - EXPECT_FALSE(image.CreateImage(context, command)) << "Size must be reported as invalid"; + PbCommand command4; + SetParam(command4, "size", "1"); + CommandContext context4(command4, "", ""); + EXPECT_FALSE(image.CreateImage(context4)) << "Size must be reported as invalid"; - SetParam(command, "size", "513"); - EXPECT_FALSE(image.CreateImage(context, command)) << "Size must be reported as not a multiple of 512"; + PbCommand command5; + SetParam(command5, "size", "513"); + CommandContext context5(command5, "", ""); + EXPECT_FALSE(image.CreateImage(context4)) << "Size must be reported as not a multiple of 512"; // Further tests would modify the filesystem } TEST(PiscsiImageTest, DeleteImage) { - MockCommandContext context; - PbCommand command; PiscsiImage image; StorageDevice::UnreserveAll(); - EXPECT_FALSE(image.DeleteImage(context, command)) << "Filename must be reported as missing"; + PbCommand command1; + CommandContext context1(command1, "", ""); + EXPECT_FALSE(image.DeleteImage(context1)) << "Filename must be reported as missing"; - SetParam(command, "file", "/a/b/c/filename"); - EXPECT_FALSE(image.DeleteImage(context, command)) << "Depth must be reported as invalid"; + PbCommand command2; + SetParam(command2, "file", "/a/b/c/filename"); + CommandContext context2(command2, "", ""); + EXPECT_FALSE(image.DeleteImage(context2)) << "Depth must be reported as invalid"; MockStorageDevice device; - device.ReserveFile("filename", 0, 0); - SetParam(command, "file", "filename"); - EXPECT_FALSE(image.DeleteImage(context, command)) << "File must be reported as in use"; + device.SetFilename("filename"); + device.ReserveFile(); + PbCommand command3; + SetParam(command3, "file", "filename"); + CommandContext context3(command3, "", ""); + EXPECT_FALSE(image.DeleteImage(context3)) << "File must be reported as in use"; // Further testing would modify the filesystem } TEST(PiscsiImageTest, RenameImage) { - MockCommandContext context; - PbCommand command; PiscsiImage image; StorageDevice::UnreserveAll(); - EXPECT_FALSE(image.RenameImage(context, command)) << "Source filename must be reported as missing"; + PbCommand command1; + CommandContext context1(command1, "", ""); + EXPECT_FALSE(image.RenameImage(context1)) << "Source filename must be reported as missing"; - SetParam(command, "from", "/a/b/c/filename_from"); - EXPECT_FALSE(image.RenameImage(context, command)) << "Depth must be reported as invalid"; + PbCommand command2; + SetParam(command2, "from", "/a/b/c/filename_from"); + CommandContext context2(command2, "", ""); + EXPECT_FALSE(image.RenameImage(context2)) << "Depth must be reported as invalid"; - SetParam(command, "from", "filename_from"); - EXPECT_FALSE(image.RenameImage(context, command)) << "Source file must be reported as missing"; + PbCommand command3; + SetParam(command3, "from", "filename_from"); + CommandContext context3(command3, "", ""); + EXPECT_FALSE(image.RenameImage(context3)) << "Source file must be reported as missing"; // Further testing would modify the filesystem } TEST(PiscsiImageTest, CopyImage) { - MockCommandContext context; - PbCommand command; PiscsiImage image; StorageDevice::UnreserveAll(); - EXPECT_FALSE(image.CopyImage(context, command)) << "Source filename must be reported as missing"; + PbCommand command1; + CommandContext context1(command1, "", ""); + EXPECT_FALSE(image.CopyImage(context1)) << "Source filename must be reported as missing"; - SetParam(command, "from", "/a/b/c/filename_from"); - EXPECT_FALSE(image.CopyImage(context, command)) << "Depth must be reported as invalid"; + PbCommand command2; + SetParam(command2, "from", "/a/b/c/filename_from"); + CommandContext context2(command2, "", ""); + EXPECT_FALSE(image.CopyImage(context2)) << "Depth must be reported as invalid"; - SetParam(command, "from", "filename_from"); - EXPECT_FALSE(image.CopyImage(context, command)) << "Source file must be reported as missing"; + PbCommand command3; + SetParam(command3, "from", "filename_from"); + CommandContext context3(command3, "", ""); + EXPECT_FALSE(image.CopyImage(context3)) << "Source file must be reported as missing"; // Further testing would modify the filesystem } TEST(PiscsiImageTest, SetImagePermissions) { - MockCommandContext context; - PbCommand command; PiscsiImage image; StorageDevice::UnreserveAll(); - EXPECT_FALSE(image.SetImagePermissions(context, command)) << "Filename must be reported as missing"; + PbCommand command1; + CommandContext context1(command1, "", ""); + EXPECT_FALSE(image.SetImagePermissions(context1)) << "Filename must be reported as missing"; - SetParam(command, "file", "/a/b/c/filename"); - EXPECT_FALSE(image.SetImagePermissions(context, command)) << "Depth must be reported as invalid"; + PbCommand command2; + SetParam(command2, "file", "/a/b/c/filename"); + CommandContext context2(command2, "", ""); + EXPECT_FALSE(image.SetImagePermissions(context2)) << "Depth must be reported as invalid"; - SetParam(command, "file", "filename"); - EXPECT_FALSE(image.CopyImage(context, command)) << "Source file must be reported as missing"; + PbCommand command3; + SetParam(command3, "file", "filename"); + CommandContext context3(command3, "", ""); + EXPECT_FALSE(image.CopyImage(context3)) << "Source file must be reported as missing"; // Further testing would modify the filesystem } diff --git a/cpp/test/piscsi_response_test.cpp b/cpp/test/piscsi_response_test.cpp index 168eb5bc..19ebb49c 100644 --- a/cpp/test/piscsi_response_test.cpp +++ b/cpp/test/piscsi_response_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -13,32 +13,33 @@ #include "devices/device_factory.h" #include "generated/piscsi_interface.pb.h" #include "piscsi/piscsi_response.h" +#include using namespace piscsi_interface; TEST(PiscsiResponseTest, Operation_Count) { PiscsiResponse response; - PbResult result; - const auto info = response.GetOperationInfo(result, 0); - EXPECT_EQ(PbOperation_ARRAYSIZE - 1, info->operations_size()); + PbOperationInfo info; + response.GetOperationInfo(info, 0); + EXPECT_EQ(PbOperation_ARRAYSIZE - 1, info.operations_size()); } void TestNonDiskDevice(PbDeviceType type, int default_param_count) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; DeviceFactory device_factory; PiscsiResponse response; auto d = device_factory.CreateDevice(type, 0, ""); - const unordered_map params; + const param_map params; d->Init(params); - EXPECT_TRUE(controller_manager->AttachToScsiController(0, d)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, 0, d)); PbServerInfo info; - response.GetDevices(controller_manager->GetAllDevices(), info, "image_folder"); + response.GetDevices(controller_manager.GetAllDevices(), info, "image_folder"); EXPECT_EQ(1, info.devices_info().devices().size()); @@ -81,21 +82,29 @@ TEST(PiscsiResponseTest, GetImageFile) EXPECT_EQ(SCHD, image_file.type()); } +TEST(PiscsiResponseTest, GetImageFilesInfo) +{ + PiscsiResponse response; + + PbImageFilesInfo info; + response.GetImageFilesInfo(info, "default_folder", "", "", 1); + EXPECT_TRUE(info.image_files().empty()); +} + TEST(PiscsiResponseTest, GetReservedIds) { PiscsiResponse response; unordered_set ids; - PbResult result; - const auto& info1 = response.GetReservedIds(result, ids); - EXPECT_TRUE(result.status()); - EXPECT_TRUE(info1->ids().empty()); + PbReservedIdsInfo info1; + response.GetReservedIds(info1, ids); + EXPECT_TRUE(info1.ids().empty()); ids.insert(3); - const auto& info2 = response.GetReservedIds(result, ids); - EXPECT_TRUE(result.status()); - EXPECT_EQ(1, info2->ids().size()); - EXPECT_EQ(3, info2->ids()[0]); + PbReservedIdsInfo info2; + response.GetReservedIds(info2, ids); + EXPECT_EQ(1, info2.ids().size()); + EXPECT_EQ(3, info2.ids()[0]); } TEST(PiscsiResponseTest, GetDevicesInfo) @@ -106,40 +115,42 @@ TEST(PiscsiResponseTest, GetDevicesInfo) const int LUN3 = 6; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiResponse response; PbCommand command; - PbResult result; - response.GetDevicesInfo(controller_manager->GetAllDevices(), result, command, ""); - EXPECT_TRUE(result.status()); - EXPECT_TRUE(result.devices_info().devices().empty()); + PbResult result1; + response.GetDevicesInfo(controller_manager.GetAllDevices(), result1, command, ""); + EXPECT_TRUE(result1.status()); + EXPECT_TRUE(result1.devices_info().devices().empty()); auto device1 = make_shared(LUN1); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device1)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device1)); - response.GetDevicesInfo(controller_manager->GetAllDevices(), result, command, ""); - EXPECT_TRUE(result.status()); - auto& devices1 = result.devices_info().devices(); + response.GetDevicesInfo(controller_manager.GetAllDevices(), result1, command, ""); + EXPECT_TRUE(result1.status()); + auto& devices1 = result1.devices_info().devices(); EXPECT_EQ(1, devices1.size()); EXPECT_EQ(SCHS, devices1[0].type()); EXPECT_EQ(ID, devices1[0].id()); EXPECT_EQ(LUN1, devices1[0].unit()); auto device2 = make_shared(LUN2); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device2)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device2)); - response.GetDevicesInfo(controller_manager->GetAllDevices(), result, command, ""); - EXPECT_TRUE(result.status()); - auto& devices2 = result.devices_info().devices(); - EXPECT_EQ(2, devices2.size()) << "Data for all devices must be returned"; + PbResult result2; + response.GetDevicesInfo(controller_manager.GetAllDevices(), result2, command, ""); + EXPECT_TRUE(result2.status()); + auto& devices2 = result2.devices_info().devices(); + EXPECT_EQ(2, devices2.size()) << "Device count mismatch"; auto requested_device = command.add_devices(); requested_device->set_id(ID); requested_device->set_unit(LUN1); - response.GetDevicesInfo(controller_manager->GetAllDevices(), result, command, ""); - EXPECT_TRUE(result.status()); - auto& devices3 = result.devices_info().devices(); + PbResult result3; + response.GetDevicesInfo(controller_manager.GetAllDevices(), result3, command, ""); + EXPECT_TRUE(result3.status()); + auto& devices3 = result3.devices_info().devices(); EXPECT_EQ(1, devices3.size()) << "Only data for the specified ID and LUN must be returned"; EXPECT_EQ(SCHS, devices3[0].type()); EXPECT_EQ(ID, devices3[0].id()); @@ -147,79 +158,73 @@ TEST(PiscsiResponseTest, GetDevicesInfo) requested_device->set_id(ID); requested_device->set_unit(LUN3); - response.GetDevicesInfo(controller_manager->GetAllDevices(), result, command, ""); - EXPECT_FALSE(result.status()) << "Only data for the specified ID and LUN must be returned"; + PbResult result4; + response.GetDevicesInfo(controller_manager.GetAllDevices(), result4, command, ""); + EXPECT_FALSE(result4.status()) << "Only data for the specified ID and LUN must be returned"; } TEST(PiscsiResponseTest, GetDeviceTypesInfo) { PiscsiResponse response; - PbResult result; - const auto& info = response.GetDeviceTypesInfo(result); - EXPECT_TRUE(result.status()); - EXPECT_EQ(8, info->properties().size()); + PbDeviceTypesInfo info; + response.GetDeviceTypesInfo(info); + EXPECT_EQ(8, info.properties().size()); } TEST(PiscsiResponseTest, GetServerInfo) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); PiscsiResponse response; const unordered_set> devices; const unordered_set ids = { 1, 3 }; - PbResult result; - const auto& info = response.GetServerInfo(devices, result, ids, "log_level", "default_folder", "", "", 1234); - EXPECT_TRUE(result.status()); - EXPECT_EQ(piscsi_major_version, info->version_info().major_version()); - EXPECT_EQ(piscsi_minor_version, info->version_info().minor_version()); - EXPECT_EQ(piscsi_patch_version, info->version_info().patch_version()); - EXPECT_EQ("log_level", info->log_level_info().current_log_level()); - EXPECT_EQ("default_folder", info->image_files_info().default_image_folder()); - EXPECT_EQ(1234, info->image_files_info().depth()); - EXPECT_EQ(2, info->reserved_ids_info().ids().size()); + PbServerInfo info; + response.GetServerInfo(info, devices, ids, "default_folder", "", "", 1234); + EXPECT_EQ(piscsi_major_version, info.version_info().major_version()); + EXPECT_EQ(piscsi_minor_version, info.version_info().minor_version()); + EXPECT_EQ(piscsi_patch_version, info.version_info().patch_version()); + EXPECT_EQ(level::level_string_views[get_level()], info.log_level_info().current_log_level()); + EXPECT_EQ("default_folder", info.image_files_info().default_image_folder()); + EXPECT_EQ(1234, info.image_files_info().depth()); + EXPECT_EQ(2, info.reserved_ids_info().ids().size()); } TEST(PiscsiResponseTest, GetVersionInfo) { PiscsiResponse response; - PbResult result; - const auto& info = response.GetVersionInfo(result); - EXPECT_TRUE(result.status()); - EXPECT_EQ(piscsi_major_version, info->major_version()); - EXPECT_EQ(piscsi_minor_version, info->minor_version()); - EXPECT_EQ(piscsi_patch_version, info->patch_version()); + PbVersionInfo info; + response.GetVersionInfo(info); + EXPECT_EQ(piscsi_major_version, info.major_version()); + EXPECT_EQ(piscsi_minor_version, info.minor_version()); + EXPECT_EQ(piscsi_patch_version, info.patch_version()); } TEST(PiscsiResponseTest, GetLogLevelInfo) { PiscsiResponse response; - PbResult result; - const auto& info = response.GetLogLevelInfo(result, "level"); - EXPECT_TRUE(result.status()); - EXPECT_EQ("level", info->current_log_level()); - EXPECT_EQ(6, info->log_levels().size()); + PbLogLevelInfo info; + response.GetLogLevelInfo(info); + EXPECT_EQ(level::level_string_views[get_level()], info.current_log_level()); + EXPECT_EQ(7, info.log_levels().size()); } TEST(PiscsiResponseTest, GetNetworkInterfacesInfo) { PiscsiResponse response; - PbResult result; - const auto& info = response.GetNetworkInterfacesInfo(result); - EXPECT_TRUE(result.status()); - EXPECT_FALSE(info->name().empty()); + PbNetworkInterfacesInfo info; + response.GetNetworkInterfacesInfo(info); + EXPECT_FALSE(info.name().empty()); } TEST(PiscsiResponseTest, GetMappingInfo) { PiscsiResponse response; - PbResult result; - const auto& info = response.GetMappingInfo(result); - EXPECT_TRUE(result.status()); - EXPECT_EQ(10, info->mapping().size()); + PbMappingInfo info; + response.GetMappingInfo(info); + EXPECT_EQ(10, info.mapping().size()); } diff --git a/cpp/test/piscsi_service_test.cpp b/cpp/test/piscsi_service_test.cpp index a29a8172..80a1c8c6 100644 --- a/cpp/test/piscsi_service_test.cpp +++ b/cpp/test/piscsi_service_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // These tests only test up the point where a network connection is required. // @@ -11,16 +11,100 @@ #include +#include "generated/piscsi_interface.pb.h" +#include "shared/protobuf_util.h" +#include "shared/network_util.h" +#include "shared/piscsi_exceptions.h" +#include "piscsi/command_context.h" #include "piscsi/piscsi_service.h" +#include +#include +#include +#include +#include -TEST(PiscsiServiceTest, LifeCycle) +using namespace piscsi_interface; +using namespace protobuf_util; +using namespace network_util; + +void SendCommand(const PbCommand& command, PbResult& result) +{ + sockaddr_in server_addr = {}; + ASSERT_TRUE(ResolveHostName("127.0.0.1", &server_addr)); + server_addr.sin_port = htons(uint16_t(9999)); + + const int fd = socket(AF_INET, SOCK_STREAM, 0); + ASSERT_NE(-1, fd); + EXPECT_TRUE(connect(fd, reinterpret_cast(&server_addr), sizeof(server_addr)) >= 0) << "Service should be running"; //NOSONAR bit_cast is not supported by the bullseye clang++ compiler + ASSERT_EQ(6, write(fd, "RASCSI", 6)); + SerializeMessage(fd, command); + DeserializeMessage(fd, result); + close(fd); +} + +TEST(PiscsiServiceTest, Init) { PiscsiService service; - EXPECT_TRUE(service.Init(nullptr, 65535)); - EXPECT_FALSE(service.Init(nullptr, 65536)); - EXPECT_FALSE(service.Init(nullptr, 0)); - EXPECT_FALSE(service.Init(nullptr, -1)); - - service.Cleanup(); + EXPECT_FALSE(service.Init(nullptr, 65536).empty()) << "Illegal port number"; + EXPECT_FALSE(service.Init(nullptr, 0).empty()) << "Illegal port number"; + EXPECT_FALSE(service.Init(nullptr, -1).empty()) << "Illegal port number"; + EXPECT_FALSE(service.Init(nullptr, 1).empty()) << "Port 1 is only available for the root user"; + EXPECT_TRUE(service.Init(nullptr, 9999).empty()) << "Port 9999 is expected not to be in use for this test"; + service.Stop(); +} + +TEST(PiscsiServiceTest, IsRunning) +{ + PiscsiService service; + EXPECT_FALSE(service.IsRunning()); + EXPECT_TRUE(service.Init(nullptr, 9999).empty()) << "Port 9999 is expected not to be in use for this test"; + EXPECT_FALSE(service.IsRunning()); + + service.Start(); + EXPECT_TRUE(service.IsRunning()); + service.Stop(); + EXPECT_FALSE(service.IsRunning()); +} + +TEST(PiscsiServiceTest, Execute) +{ + sockaddr_in server_addr = {}; + ASSERT_TRUE(ResolveHostName("127.0.0.1", &server_addr)); + + const int fd = socket(AF_INET, SOCK_STREAM, 0); + ASSERT_NE(-1, fd); + + server_addr.sin_port = htons(uint16_t(9999)); + EXPECT_FALSE(connect(fd, reinterpret_cast(&server_addr), sizeof(server_addr)) >= 0) << "Service should not be running"; //NOSONAR bit_cast is not supported by the bullseye clang++ compiler + + close(fd); + + PiscsiService service; + service.Init([] (const CommandContext& context) { + if (context.GetCommand().operation() == PbOperation::NO_OPERATION) { + PbResult result; + result.set_status(true); + context.WriteResult(result); + } + else { + throw io_exception("error"); + } + return true; + }, 9999); + + service.Start(); + + PbCommand command; + PbResult result; + + SendCommand(command, result); + command.set_operation(PbOperation::NO_OPERATION); + EXPECT_TRUE(result.status()) << "Command should have been successful"; + + command.set_operation(PbOperation::EJECT); + SendCommand(command, result); + EXPECT_FALSE(result.status()) << "Exception should have been raised"; + + service.Stop(); } diff --git a/cpp/test/piscsi_util_test.cpp b/cpp/test/piscsi_util_test.cpp index bfe2bdd1..03a5133e 100644 --- a/cpp/test/piscsi_util_test.cpp +++ b/cpp/test/piscsi_util_test.cpp @@ -3,53 +3,109 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include #include "shared/piscsi_util.h" -#include "generated/piscsi_interface.pb.h" #ifdef __linux__ #include #endif using namespace std; -using namespace piscsi_interface; using namespace piscsi_util; +TEST(PiscsiUtilTest, Split) +{ + auto v = Split("this_is_a_test", '_'); + EXPECT_EQ(4, v.size()); + EXPECT_EQ("this", v[0]); + EXPECT_EQ("is", v[1]); + EXPECT_EQ("a", v[2]); + EXPECT_EQ("test", v[3]); + v = Split("test", ':'); + EXPECT_EQ(1, v.size()); + EXPECT_EQ("test", v[0]); + v = Split(":test", ':'); + EXPECT_EQ(2, v.size()); + EXPECT_EQ("", v[0]); + EXPECT_EQ("test", v[1]); + v = Split("test:", ':'); + EXPECT_EQ(1, v.size()); + EXPECT_EQ("test", v[0]); + v = Split(":", ':'); + EXPECT_EQ(1, v.size()); + EXPECT_EQ("", v[0]); + v = Split("", ':'); + EXPECT_EQ(0, v.size()); + + v = Split("this:is:a:test", ':', 1); + EXPECT_EQ(1, v.size()); + EXPECT_EQ("this:is:a:test", v[0]); + v = Split("this:is:a:test", ':', 2); + EXPECT_EQ(2, v.size()); + EXPECT_EQ("this", v[0]); + EXPECT_EQ("is:a:test", v[1]); +} + +TEST(PiscsiUtilTest, GetLocale) +{ + EXPECT_LE(2, GetLocale().size()); +} + TEST(PiscsiUtilTest, ProcessId) { int id = -1; int lun = -1; - string error = ProcessId("", 32, id, lun); + string error = ProcessId("", id, lun); EXPECT_FALSE(error.empty()); EXPECT_EQ(-1, id); EXPECT_EQ(-1, lun); - error = ProcessId("0:32", 32, id, lun); + error = ProcessId("8", id, lun); EXPECT_FALSE(error.empty()); EXPECT_EQ(-1, id); EXPECT_EQ(-1, lun); - error = ProcessId("-1:", 32, id, lun); + error = ProcessId("0:32", id, lun); EXPECT_FALSE(error.empty()); EXPECT_EQ(-1, id); EXPECT_EQ(-1, lun); - error = ProcessId("0:-1", 32, id, lun); + error = ProcessId("-1:", id, lun); EXPECT_FALSE(error.empty()); EXPECT_EQ(-1, id); EXPECT_EQ(-1, lun); - error = ProcessId("0", 32, id, lun); + error = ProcessId("0:-1", id, lun); + EXPECT_FALSE(error.empty()); + EXPECT_EQ(-1, id); + EXPECT_EQ(-1, lun); + + error = ProcessId("a", id, lun); + EXPECT_FALSE(error.empty()); + EXPECT_EQ(-1, id); + EXPECT_EQ(-1, lun); + + error = ProcessId("a:0", id, lun); + EXPECT_FALSE(error.empty()); + EXPECT_EQ(-1, id); + EXPECT_EQ(-1, lun); + + error = ProcessId("0:a", id, lun); + EXPECT_FALSE(error.empty()); + EXPECT_EQ(-1, id); + EXPECT_EQ(-1, lun); + + error = ProcessId("0", id, lun); EXPECT_TRUE(error.empty()); EXPECT_EQ(0, id); - EXPECT_EQ(0, lun); + EXPECT_EQ(-1, lun); - error = ProcessId("7:31", 32, id, lun); + error = ProcessId("7:31", id, lun); EXPECT_TRUE(error.empty()); EXPECT_EQ(7, id); EXPECT_EQ(31, lun); @@ -78,10 +134,10 @@ TEST(PiscsiUtilTest, GetExtensionLowerCase) { EXPECT_EQ("", GetExtensionLowerCase("")); EXPECT_EQ("", GetExtensionLowerCase(".")); + EXPECT_EQ("", GetExtensionLowerCase(".ext")); + EXPECT_EQ("", GetExtensionLowerCase(".ext_long")); EXPECT_EQ("ext", GetExtensionLowerCase("file.ext")); EXPECT_EQ("ext", GetExtensionLowerCase("FILE.EXT")); - EXPECT_EQ("ext", GetExtensionLowerCase(".ext")); - EXPECT_EQ("ext_long", GetExtensionLowerCase(".ext_long")); EXPECT_EQ("ext", GetExtensionLowerCase(".XYZ.EXT")); } diff --git a/cpp/test/primary_device_test.cpp b/cpp/test/primary_device_test.cpp index 233e5f38..0a3739a0 100644 --- a/cpp/test/primary_device_test.cpp +++ b/cpp/test/primary_device_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -17,33 +17,28 @@ using namespace scsi_defs; using namespace scsi_command_util; +pair, shared_ptr> CreatePrimaryDevice(int id = 0) +{ + auto controller = make_shared>(id); + auto device = make_shared(0); + EXPECT_TRUE(device->Init({})); + EXPECT_TRUE(controller->AddDevice(device)); + + return { controller, device }; +} + TEST(PrimaryDeviceTest, GetId) { const int ID = 5; - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, ID); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); + auto [controller, device] = CreatePrimaryDevice(ID); - EXPECT_EQ(-1, device->GetId()) << "Device ID cannot be known without assignment to a controller"; - - controller->AddDevice(device); EXPECT_EQ(ID, device->GetId()); } TEST(PrimaryDeviceTest, PhaseChange) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); - - controller->AddDevice(device); + auto [controller, device] = CreatePrimaryDevice(); EXPECT_CALL(*controller, Status); device->EnterStatusPhase(); @@ -57,14 +52,7 @@ TEST(PrimaryDeviceTest, PhaseChange) TEST(PrimaryDeviceTest, Reset) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); - - controller->AddDevice(device); + auto [controller, device] = CreatePrimaryDevice(); device->Dispatch(scsi_command::eCmdReserve6); EXPECT_FALSE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false)) @@ -76,14 +64,7 @@ TEST(PrimaryDeviceTest, Reset) TEST(PrimaryDeviceTest, CheckReservation) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); - - controller->AddDevice(device); + auto [controller, device] = CreatePrimaryDevice(); EXPECT_TRUE(device->CheckReservation(0, scsi_command::eCmdTestUnitReady, false)) << "Device must not be reserved for initiator ID 0"; @@ -110,14 +91,7 @@ TEST(PrimaryDeviceTest, CheckReservation) TEST(PrimaryDeviceTest, ReserveReleaseUnit) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); - - controller->AddDevice(device); + auto [controller, device] = CreatePrimaryDevice(); device->Dispatch(scsi_command::eCmdReserve6); EXPECT_FALSE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false)) @@ -139,14 +113,7 @@ TEST(PrimaryDeviceTest, ReserveReleaseUnit) TEST(PrimaryDeviceTest, DiscardReservation) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); - - controller->AddDevice(device); + auto [controller, device] = CreatePrimaryDevice(); device->Dispatch(scsi_command::eCmdReserve6); EXPECT_FALSE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false)) @@ -158,73 +125,61 @@ TEST(PrimaryDeviceTest, DiscardReservation) TEST(PrimaryDeviceTest, TestUnitReady) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); - - controller->AddDevice(device); + auto [controller, device] = CreatePrimaryDevice(); + // Required by the bullseye clang++ compiler + auto d = device; device->SetReset(true); device->SetAttn(true); device->SetReady(false); EXPECT_CALL(*controller, DataIn).Times(0); - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::UNIT_ATTENTION), - Property(&scsi_exception::get_asc, asc::POWER_ON_OR_RESET)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::unit_attention), + Property(&scsi_exception::get_asc, asc::power_on_or_reset)))); device->SetReset(false); EXPECT_CALL(*controller, DataIn).Times(0); - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::UNIT_ATTENTION), - Property(&scsi_exception::get_asc, asc::NOT_READY_TO_READY_CHANGE)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::unit_attention), + Property(&scsi_exception::get_asc, asc::not_ready_to_ready_change)))); device->SetReset(true); device->SetAttn(false); EXPECT_CALL(*controller, DataIn).Times(0); - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::UNIT_ATTENTION), - Property(&scsi_exception::get_asc, asc::POWER_ON_OR_RESET)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::unit_attention), + Property(&scsi_exception::get_asc, asc::power_on_or_reset)))); device->SetReset(false); device->SetAttn(true); EXPECT_CALL(*controller, DataIn).Times(0); - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::UNIT_ATTENTION), - Property(&scsi_exception::get_asc, asc::NOT_READY_TO_READY_CHANGE)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::unit_attention), + Property(&scsi_exception::get_asc, asc::not_ready_to_ready_change)))); device->SetAttn(false); EXPECT_CALL(*controller, DataIn).Times(0); - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))); device->SetReady(true); EXPECT_CALL(*controller, Status); device->Dispatch(scsi_command::eCmdTestUnitReady); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(PrimaryDeviceTest, Inquiry) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); - - controller->AddDevice(device); - - auto& cmd = controller->GetCmd(); + auto [controller, device] = CreatePrimaryDevice(); + // Required by the bullseye clang++ compiler + auto d = device; // ALLOCATION LENGTH - cmd[4] = 255; + controller->SetCmdByte(4, 255); - ON_CALL(*device, InquiryInternal()).WillByDefault([&device]() { - return device->HandleInquiry(device_type::PROCESSOR, scsi_level::SPC_3, false); + ON_CALL(*d, InquiryInternal()).WillByDefault([&d]() { + return d->HandleInquiry(device_type::processor, scsi_level::spc_3, false); }); EXPECT_CALL(*device, InquiryInternal); EXPECT_CALL(*controller, DataIn); @@ -237,42 +192,42 @@ TEST(PrimaryDeviceTest, Inquiry) EXPECT_CALL(*device, InquiryInternal); EXPECT_CALL(*controller, DataIn); device->Dispatch(scsi_command::eCmdInquiry); - EXPECT_EQ(device_type::PROCESSOR, (device_type)controller->GetBuffer()[0]); + EXPECT_EQ(device_type::processor, (device_type)controller->GetBuffer()[0]); EXPECT_EQ(0x00, controller->GetBuffer()[1]) << "Device was not reported as non-removable"; - EXPECT_EQ(scsi_level::SPC_3, (scsi_level)controller->GetBuffer()[2]) << "Wrong SCSI level"; - EXPECT_EQ(scsi_level::SCSI_2, (scsi_level)controller->GetBuffer()[3]) << "Wrong response level"; + EXPECT_EQ(scsi_level::spc_3, (scsi_level)controller->GetBuffer()[2]) << "Wrong SCSI level"; + EXPECT_EQ(scsi_level::scsi_2, (scsi_level)controller->GetBuffer()[3]) << "Wrong response level"; EXPECT_EQ(0x1f, controller->GetBuffer()[4]) << "Wrong additional data size"; - ON_CALL(*device, InquiryInternal()).WillByDefault([&device]() { - return device->HandleInquiry(device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, true); + ON_CALL(*device, InquiryInternal()).WillByDefault([&d]() { + return d->HandleInquiry(device_type::direct_access, scsi_level::scsi_1_ccs, true); }); EXPECT_CALL(*device, InquiryInternal); EXPECT_CALL(*controller, DataIn); device->Dispatch(scsi_command::eCmdInquiry); - EXPECT_EQ(device_type::DIRECT_ACCESS, (device_type)controller->GetBuffer()[0]); + EXPECT_EQ(device_type::direct_access, (device_type)controller->GetBuffer()[0]); EXPECT_EQ(0x80, controller->GetBuffer()[1]) << "Device was not reported as removable"; - EXPECT_EQ(scsi_level::SCSI_1_CCS, (scsi_level)controller->GetBuffer()[2]) << "Wrong SCSI level"; - EXPECT_EQ(scsi_level::SCSI_1_CCS, (scsi_level)controller->GetBuffer()[3]) << "Wrong response level"; + EXPECT_EQ(scsi_level::scsi_1_ccs, (scsi_level)controller->GetBuffer()[2]) << "Wrong SCSI level"; + EXPECT_EQ(scsi_level::scsi_1_ccs, (scsi_level)controller->GetBuffer()[3]) << "Wrong response level"; EXPECT_EQ(0x1f, controller->GetBuffer()[4]) << "Wrong additional data size"; - cmd[1] = 0x01; + controller->SetCmdByte(1, 0x01); EXPECT_CALL(*controller, DataIn).Times(0); - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdInquiry); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdInquiry); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "EVPD bit is not supported"; - cmd[2] = 0x01; + controller->SetCmdByte(2, 0x01); EXPECT_CALL(*controller, DataIn).Times(0); - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdInquiry); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + EXPECT_THAT([&d] { d->Dispatch(scsi_command::eCmdInquiry); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "PAGE CODE field is not supported"; - cmd[1] = 0x00; - cmd[2] = 0x00; + controller->SetCmdByte(1, 0); + controller->SetCmdByte(2, 0); // ALLOCATION LENGTH - cmd[4] = 1; + controller->SetCmdByte(4, 1); EXPECT_CALL(*device, InquiryInternal); EXPECT_CALL(*controller, DataIn); device->Dispatch(scsi_command::eCmdInquiry); @@ -282,64 +237,51 @@ TEST(PrimaryDeviceTest, Inquiry) TEST(PrimaryDeviceTest, RequestSense) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); + auto [controller, device] = CreatePrimaryDevice(); + // Required by the bullseye clang++ compiler + auto d = device; - controller->AddDevice(device); - - auto& cmd = controller->GetCmd(); // ALLOCATION LENGTH - cmd[4] = 255; + controller->SetCmdByte(4, 255); device->SetReady(false); - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdRequestSense); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdRequestSense); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))); device->SetReady(true); EXPECT_CALL(*controller, DataIn); device->Dispatch(scsi_command::eCmdRequestSense); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(PrimaryDeviceTest, SendDiagnostic) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); - - controller->AddDevice(device); - - auto& cmd = controller->GetCmd(); + auto [controller, device] = CreatePrimaryDevice(); + // Required by the bullseye clang++ compiler + auto d = device; EXPECT_CALL(*controller, Status); device->Dispatch(scsi_command::eCmdSendDiagnostic); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); - cmd[1] = 0x10; - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdSendDiagnostic); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(1, 0x10); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSendDiagnostic); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "SEND DIAGNOSTIC must fail because PF bit is not supported"; - cmd[1] = 0; + controller->SetCmdByte(1, 0); - cmd[3] = 1; - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdSendDiagnostic); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(3, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSendDiagnostic); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "SEND DIAGNOSTIC must fail because parameter list is not supported"; - cmd[3] = 0; - cmd[4] = 1; - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdSendDiagnostic); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(3, 0); + controller->SetCmdByte(4, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSendDiagnostic); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "SEND DIAGNOSTIC must fail because parameter list is not supported"; } @@ -348,23 +290,19 @@ TEST(PrimaryDeviceTest, ReportLuns) const int LUN1 = 1; const int LUN2 = 4; - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); + auto controller = make_shared(0); auto device1 = make_shared(LUN1); auto device2 = make_shared(LUN2); - const unordered_map params; - device1->Init(params); - device2->Init(params); + EXPECT_TRUE(device1->Init({})); + EXPECT_TRUE(device2->Init({})); controller->AddDevice(device1); EXPECT_TRUE(controller->HasDeviceForLun(LUN1)); controller->AddDevice(device2); EXPECT_TRUE(controller->HasDeviceForLun(LUN2)); - auto& cmd = controller->GetCmd(); // ALLOCATION LENGTH - cmd[9] = 255; + controller->SetCmdByte(9, 255); EXPECT_CALL(*controller, DataIn); device1->Dispatch(scsi_command::eCmdReportLuns); @@ -380,33 +318,25 @@ TEST(PrimaryDeviceTest, ReportLuns) EXPECT_EQ(0, GetInt16(buffer, 20)) << "Wrong LUN2 number"; EXPECT_EQ(LUN2, GetInt16(buffer, 22)) << "Wrong LUN2 number"; - cmd[2] = 0x01; + controller->SetCmdByte(2, 0x01); EXPECT_THAT([&] { device1->Dispatch(scsi_command::eCmdReportLuns); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Only SELECT REPORT mode 0 is supported"; } TEST(PrimaryDeviceTest, Dispatch) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); - - controller->AddDevice(device); + auto [controller, device] = CreatePrimaryDevice(); EXPECT_THROW(device->Dispatch(static_cast(0x1f)), scsi_exception) << "Unknown command"; } TEST(PrimaryDeviceTest, WriteByteSequence) { - vector data; - MockPrimaryDevice device(0); + auto [controller, device] = CreatePrimaryDevice(); - EXPECT_FALSE(device.WriteByteSequence(data, 0)) << "Primary device does not support writing byte sequences"; + EXPECT_FALSE(device->WriteByteSequence({})) << "Primary device does not support writing byte sequences"; } TEST(PrimaryDeviceTest, GetSetSendDelay) @@ -420,16 +350,8 @@ TEST(PrimaryDeviceTest, GetSetSendDelay) TEST(PrimaryDeviceTest, Init) { - unordered_map params; + param_map params; MockPrimaryDevice device(0); EXPECT_TRUE(device.Init(params)) << "Initialization of primary device must not fail"; } - -TEST(PrimaryDeviceTest, FlushCache) -{ - MockPrimaryDevice device(0); - - // Method must be present - device.FlushCache(); -} diff --git a/cpp/test/protobuf_serializer_test.cpp b/cpp/test/protobuf_serializer_test.cpp deleted file mode 100644 index 6cc4e671..00000000 --- a/cpp/test/protobuf_serializer_test.cpp +++ /dev/null @@ -1,98 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Copyright (C) 2022 Uwe Seimet -// -//--------------------------------------------------------------------------- - -#include "mocks.h" -#include "shared/protobuf_serializer.h" -#include "shared/piscsi_exceptions.h" -#include "generated/piscsi_interface.pb.h" -#include - -using namespace filesystem; -using namespace piscsi_interface; - -TEST(ProtobufSerializerTest, SerializeMessage) -{ - PbResult result; - ProtobufSerializer serializer; - - const int fd = open("/dev/null", O_WRONLY); - EXPECT_NE(-1, fd); - serializer.SerializeMessage(fd, result); - EXPECT_THROW(serializer.SerializeMessage(-1, result), io_exception) << "Writing a message must fail"; - close(fd); -} - -TEST(ProtobufSerializerTest, DeserializeMessage) -{ - PbResult result; - ProtobufSerializer serializer; - vector buf(1); - - int fd = open("/dev/null", O_RDONLY); - EXPECT_NE(-1, fd); - EXPECT_THROW(serializer.DeserializeMessage(fd, result), io_exception) << "Reading the message header must fail"; - close(fd); - - auto [fd1, filename1] = OpenTempFile(); - // Data size -1 - buf = { byte{0xff}, byte{0xff}, byte{0xff}, byte{0xff} }; - EXPECT_EQ(buf.size(), write(fd1, buf.data(), buf.size())); - close(fd1); - fd1 = open(filename1.c_str(), O_RDONLY); - EXPECT_NE(-1, fd1); - EXPECT_THROW(serializer.DeserializeMessage(fd1, result), io_exception) << "Invalid header was not rejected"; - remove(filename1); - - auto [fd2, filename2] = OpenTempFile(); - // Data size 2 - buf = { byte{0x02}, byte{0x00}, byte{0x00}, byte{0x00} }; - EXPECT_EQ(buf.size(), write(fd2, buf.data(), buf.size())); - close(fd2); - fd2 = open(filename2.c_str(), O_RDONLY); - EXPECT_NE(-1, fd2); - EXPECT_THROW(serializer.DeserializeMessage(fd2, result), io_exception) << "Invalid data were not rejected"; - remove(filename2); -} - -TEST(ProtobufSerializerTest, SerializeDeserializeMessage) -{ - PbResult result; - result.set_status(true); - ProtobufSerializer serializer; - - auto [fd, filename] = OpenTempFile(); - EXPECT_NE(-1, fd); - serializer.SerializeMessage(fd, result); - close(fd); - - result.set_status(false); - fd = open(filename.c_str(), O_RDONLY); - EXPECT_NE(-1, fd); - serializer.DeserializeMessage(fd, result); - close(fd); - remove(filename); - - EXPECT_TRUE(result.status()); -} - -TEST(ProtobufSerializerTest, ReadBytes) -{ - ProtobufSerializer serializer; - vector buf(1); - - int fd = open("/dev/null", O_RDONLY); - EXPECT_NE(-1, fd); - EXPECT_EQ(0, serializer.ReadBytes(fd, buf)); - close(fd); - - fd = open("/dev/zero", O_RDONLY); - EXPECT_NE(-1, fd); - EXPECT_EQ(1, serializer.ReadBytes(fd, buf)); - close(fd); -} diff --git a/cpp/test/protobuf_util_test.cpp b/cpp/test/protobuf_util_test.cpp index afd71148..1c4a06e6 100644 --- a/cpp/test/protobuf_util_test.cpp +++ b/cpp/test/protobuf_util_test.cpp @@ -3,13 +3,15 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "mocks.h" #include "shared/protobuf_util.h" +#include "shared/piscsi_exceptions.h" #include "generated/piscsi_interface.pb.h" +#include using namespace piscsi_interface; using namespace protobuf_util; @@ -22,22 +24,14 @@ void TestSpecialDevice(const string& name) EXPECT_EQ("", GetParam(device, "interfaces")); } -TEST(ProtobufUtil, AddGetParam) +TEST(ProtobufUtil, GetSetParam) { + // The implementation is a function template, testing one possible T is sufficient PbCommand command; SetParam(command, "key", "value"); EXPECT_EQ("value", GetParam(command, "key")); EXPECT_EQ("", GetParam(command, "xyz")); - - PbDeviceDefinition definition; - SetParam(definition, "key", "value"); - EXPECT_EQ("value", GetParam(definition, "key")); - EXPECT_EQ("", GetParam(definition, "xyz")); - - PbDevice device; - SetParam(device, "key", "value"); - const auto& it = device.params().find("key"); - EXPECT_EQ("value", it->second); + EXPECT_EQ("", GetParam(command, "")); } TEST(ProtobufUtil, ParseParameters) @@ -84,7 +78,7 @@ TEST(ProtobufUtil, SetPatternParams) TEST(ProtobufUtil, ListDevices) { - list devices; + vector devices; EXPECT_FALSE(ListDevices(devices).empty()); @@ -136,10 +130,90 @@ TEST(ProtobufUtil, SetIdAndLun) { PbDeviceDefinition device; - EXPECT_NE("", SetIdAndLun(device, "", 32)); - EXPECT_EQ("", SetIdAndLun(device, "1", 32)); + EXPECT_NE("", SetIdAndLun(device, "")); + EXPECT_EQ("", SetIdAndLun(device, "1")); EXPECT_EQ(1, device.id()); - EXPECT_EQ("", SetIdAndLun(device, "2:0", 32)); + EXPECT_EQ("", SetIdAndLun(device, "2:0")); EXPECT_EQ(2, device.id()); EXPECT_EQ(0, device.unit()); } + +TEST(ProtobufUtil, SerializeMessage) +{ + PbResult result; + + const int fd = open("/dev/null", O_WRONLY); + ASSERT_NE(-1, fd); + SerializeMessage(fd, result); + close(fd); + EXPECT_THROW(SerializeMessage(-1, result), io_exception) << "Writing a message must fail"; +} + +TEST(ProtobufUtil, DeserializeMessage) +{ + PbResult result; + vector buf(1); + + int fd = open("/dev/null", O_RDONLY); + ASSERT_NE(-1, fd); + EXPECT_THROW(DeserializeMessage(fd, result), io_exception) << "Reading the message header must fail"; + close(fd); + + auto [fd1, filename1] = OpenTempFile(); + // Data size -1 + buf = { byte{0xff}, byte{0xff}, byte{0xff}, byte{0xff} }; + EXPECT_EQ(buf.size(), write(fd1, buf.data(), buf.size())); + close(fd1); + fd1 = open(filename1.c_str(), O_RDONLY); + ASSERT_NE(-1, fd1); + EXPECT_THROW(DeserializeMessage(fd1, result), io_exception) << "Invalid header was not rejected"; + remove(filename1); + + auto [fd2, filename2] = OpenTempFile(); + // Data size 2 + buf = { byte{0x02}, byte{0x00}, byte{0x00}, byte{0x00} }; + EXPECT_EQ(buf.size(), write(fd2, buf.data(), buf.size())); + close(fd2); + fd2 = open(filename2.c_str(), O_RDONLY); + EXPECT_NE(-1, fd2); + EXPECT_THROW(DeserializeMessage(fd2, result), io_exception) << "Invalid data were not rejected"; + remove(filename2); +} + +TEST(ProtobufUtil, SerializeDeserializeMessage) +{ + PbResult result; + result.set_status(true); + + auto [fd, filename] = OpenTempFile(); + ASSERT_NE(-1, fd); + SerializeMessage(fd, result); + close(fd); + + result.set_status(false); + fd = open(filename.c_str(), O_RDONLY); + ASSERT_NE(-1, fd); + DeserializeMessage(fd, result); + close(fd); + remove(filename); + + EXPECT_TRUE(result.status()); +} + +TEST(ProtobufUtil, ReadBytes) +{ + vector buf1(1); + vector buf2; + + int fd = open("/dev/null", O_RDONLY); + ASSERT_NE(-1, fd); + EXPECT_EQ(0, ReadBytes(fd, buf1)); + EXPECT_EQ(0, ReadBytes(fd, buf2)); + close(fd); + + fd = open("/dev/zero", O_RDONLY); + ASSERT_NE(-1, fd); + EXPECT_EQ(1, ReadBytes(fd, buf1)); + EXPECT_EQ(0, ReadBytes(fd, buf2)); + close(fd); +} diff --git a/cpp/test/scsi_command_util_test.cpp b/cpp/test/scsi_command_util_test.cpp index dbfdffa7..20afa389 100644 --- a/cpp/test/scsi_command_util_test.cpp +++ b/cpp/test/scsi_command_util_test.cpp @@ -10,7 +10,6 @@ #include "mocks.h" #include "shared/scsi.h" #include "shared/piscsi_exceptions.h" -#include "devices/device_logger.h" #include "devices/scsi_command_util.h" using namespace scsi_command_util; @@ -18,14 +17,13 @@ using namespace scsi_command_util; TEST(ScsiCommandUtilTest, ModeSelect6) { const int LENGTH = 26; - DeviceLogger logger; vector cdb(6); vector buf(LENGTH); // PF (vendor-specific parameter format) must not fail but be ignored cdb[1] = 0x00; - ModeSelect(logger, scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 0); + ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 0); cdb[0] = 0x15; // PF (standard parameter format) @@ -34,50 +32,49 @@ TEST(ScsiCommandUtilTest, ModeSelect6) buf[9] = 0x00; buf[10] = 0x02; buf[11] = 0x00; - EXPECT_THAT([&] { ModeSelect(logger, scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 256); }, + EXPECT_THAT([&] { ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 256); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_PARAMETER_LIST)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list)))) << "Requested sector size does not match current sector size"; // Page 0 buf[12] = 0x00; - EXPECT_THAT([&] { ModeSelect(logger, scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512); }, + EXPECT_THAT([&] { ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_PARAMETER_LIST)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list)))) << "Unsupported page 0 was not rejected"; // Page 3 (Format Device Page) buf[12] = 0x03; - EXPECT_THAT([&] { ModeSelect(logger, scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512); }, + EXPECT_THAT([&] { ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_PARAMETER_LIST)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list)))) << "Requested sector size does not match current sector size"; // Match the requested to the current sector size buf[24] = 0x02; - EXPECT_THAT([&] { ModeSelect(logger, scsi_command::eCmdModeSelect6, cdb, buf, LENGTH - 1, 512); }, + EXPECT_THAT([&] { ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH - 1, 512); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_PARAMETER_LIST)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list)))) << "Not enough command parameters"; - ModeSelect(logger, scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512); + EXPECT_FALSE(ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512).empty()); } TEST(ScsiCommandUtilTest, ModeSelect10) { const int LENGTH = 30; - DeviceLogger logger; vector cdb(10); vector buf(LENGTH); // PF (vendor-specific parameter format) must not fail but be ignored cdb[1] = 0x00; - ModeSelect(logger, scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 0); + ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 0); // PF (standard parameter format) cdb[1] = 0x10; @@ -85,37 +82,37 @@ TEST(ScsiCommandUtilTest, ModeSelect10) buf[13] = 0x00; buf[14] = 0x02; buf[15] = 0x00; - EXPECT_THAT([&] { ModeSelect(logger, scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 256); }, + EXPECT_THAT([&] { ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 256); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_PARAMETER_LIST)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list)))) << "Requested sector size does not match current sector size"; // Page 0 buf[16] = 0x00; - EXPECT_THAT([&] { ModeSelect(logger, scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 512); }, + EXPECT_THAT([&] { ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 512); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_PARAMETER_LIST)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list)))) << "Unsupported page 0 was not rejected"; // Page 3 (Format Device Page) buf[16] = 0x03; - EXPECT_THAT([&] { ModeSelect(logger, scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 512); }, + EXPECT_THAT([&] { ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 512); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_PARAMETER_LIST)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list)))) << "Requested sector size does not match current sector size"; // Match the requested to the current sector size buf[28] = 0x02; - EXPECT_THAT([&] { ModeSelect(logger, scsi_command::eCmdModeSelect10, cdb, buf, LENGTH - 1, 512); }, + EXPECT_THAT([&] { ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH - 1, 512); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_PARAMETER_LIST)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list)))) << "Not enough command parameters"; - ModeSelect(logger, scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 512); + EXPECT_FALSE(ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 512).empty()); } TEST(ScsiCommandUtilTest, EnrichFormatPage) diff --git a/cpp/test/scsi_controller_test.cpp b/cpp/test/scsi_controller_test.cpp index 60a2623f..4060bc3a 100644 --- a/cpp/test/scsi_controller_test.cpp +++ b/cpp/test/scsi_controller_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -19,8 +19,7 @@ TEST(ScsiControllerTest, GetInitiatorId) const int ID = 2; auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - MockScsiController controller(controller_manager, 0); + MockScsiController controller(bus, 0); controller.Process(ID); EXPECT_EQ(ID, controller.GetInitiatorId()); @@ -31,46 +30,42 @@ TEST(ScsiControllerTest, GetInitiatorId) TEST(ScsiControllerTest, Process) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - MockScsiController controller(controller_manager, 0); + MockScsiController controller(bus, 0); controller.SetPhase(phase_t::reserved); ON_CALL(*bus, GetRST).WillByDefault(Return(true)); EXPECT_CALL(*bus, Acquire); EXPECT_CALL(*bus, GetRST); - EXPECT_CALL(*bus, Reset); EXPECT_CALL(controller, Reset); - EXPECT_EQ(phase_t::reserved, controller.Process(0)); + EXPECT_FALSE(controller.Process(0)); controller.SetPhase(phase_t::busfree); ON_CALL(*bus, GetRST).WillByDefault(Return(false)); EXPECT_CALL(*bus, Acquire); EXPECT_CALL(*bus, GetRST); - EXPECT_EQ(phase_t::busfree, controller.Process(0)); + EXPECT_FALSE(controller.Process(0)); controller.SetPhase(phase_t::reserved); EXPECT_CALL(*bus, Acquire); EXPECT_CALL(*bus, GetRST); - EXPECT_CALL(*bus, Reset); EXPECT_CALL(controller, Reset); - EXPECT_EQ(phase_t::busfree, controller.Process(0)); + EXPECT_FALSE(controller.Process(0)); } TEST(ScsiControllerTest, BusFree) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - MockScsiController controller(controller_manager, 0); + MockScsiController controller(bus, 0); controller.SetPhase(phase_t::busfree); controller.BusFree(); EXPECT_EQ(phase_t::busfree, controller.GetPhase()); - controller.SetStatus(status::CHECK_CONDITION); + controller.SetStatus(status::check_condition); controller.SetPhase(phase_t::reserved); controller.BusFree(); EXPECT_EQ(phase_t::busfree, controller.GetPhase()); - EXPECT_EQ(status::GOOD, controller.GetStatus()); + EXPECT_EQ(status::good, controller.GetStatus()); controller.ScheduleShutdown(AbstractController::piscsi_shutdown_mode::NONE); controller.SetPhase(phase_t::reserved); @@ -86,14 +81,13 @@ TEST(ScsiControllerTest, BusFree) controller.ScheduleShutdown(AbstractController::piscsi_shutdown_mode::STOP_PISCSI); controller.SetPhase(phase_t::reserved); - EXPECT_EXIT(controller.BusFree(), ExitedWithCode(EXIT_SUCCESS), ""); + controller.BusFree(); } TEST(ScsiControllerTest, Selection) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); + auto controller = make_shared(bus, 0); controller->SetPhase(phase_t::selection); ON_CALL(*bus, GetSEL).WillByDefault(Return(true)); @@ -130,17 +124,7 @@ TEST(ScsiControllerTest, Selection) controller->Selection(); EXPECT_EQ(phase_t::msgout, controller->GetPhase()); - controller->SetPhase(phase_t::reserved); - ON_CALL(*bus, GetDAT).WillByDefault(Return(0)); - controller->Selection(); - EXPECT_EQ(phase_t::reserved, controller->GetPhase()); - ON_CALL(*bus, GetDAT).WillByDefault(Return(1)); - controller->Selection(); - EXPECT_EQ(phase_t::reserved, controller->GetPhase()) << "There is no device that can be selected"; - - auto device = make_shared(0); - controller->AddDevice(device); EXPECT_CALL(*bus, SetBSY(true)); controller->Selection(); EXPECT_EQ(phase_t::selection, controller->GetPhase()); @@ -149,8 +133,7 @@ TEST(ScsiControllerTest, Selection) TEST(ScsiControllerTest, Command) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - MockScsiController controller(controller_manager, 0); + MockScsiController controller(bus, 0); controller.SetPhase(phase_t::command); EXPECT_CALL(controller, Status); @@ -177,8 +160,7 @@ TEST(ScsiControllerTest, Command) TEST(ScsiControllerTest, MsgIn) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - MockScsiController controller(controller_manager, 0); + MockScsiController controller(bus, 0); controller.SetPhase(phase_t::reserved); EXPECT_CALL(*bus, SetMSG(true)); @@ -193,8 +175,7 @@ TEST(ScsiControllerTest, MsgIn) TEST(ScsiControllerTest, MsgOut) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - MockScsiController controller(controller_manager, 0); + MockScsiController controller(bus, 0); controller.SetPhase(phase_t::reserved); EXPECT_CALL(*bus, SetMSG(true)); @@ -209,8 +190,7 @@ TEST(ScsiControllerTest, MsgOut) TEST(ScsiControllerTest, DataIn) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - MockScsiController controller(controller_manager, 0); + MockScsiController controller(bus, 0); controller.SetPhase(phase_t::reserved); controller.SetLength(0); @@ -230,8 +210,7 @@ TEST(ScsiControllerTest, DataIn) TEST(ScsiControllerTest, DataOut) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - MockScsiController controller(controller_manager, 0); + MockScsiController controller(bus, 0); controller.SetPhase(phase_t::reserved); controller.SetLength(0); @@ -251,66 +230,58 @@ TEST(ScsiControllerTest, DataOut) TEST(ScsiControllerTest, Error) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - MockScsiController controller(controller_manager, 0); + MockScsiController controller(bus, 0); ON_CALL(*bus, GetRST).WillByDefault(Return(true)); controller.SetPhase(phase_t::reserved); EXPECT_CALL(*bus, Acquire); EXPECT_CALL(*bus, GetRST()); - EXPECT_CALL(*bus, Reset); EXPECT_CALL(controller, Reset); - controller.Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT); - EXPECT_EQ(status::GOOD, controller.GetStatus()); + controller.Error(sense_key::aborted_command, asc::no_additional_sense_information, status::reservation_conflict); + EXPECT_EQ(status::good, controller.GetStatus()); EXPECT_EQ(phase_t::reserved, controller.GetPhase()); ON_CALL(*bus, GetRST).WillByDefault(Return(false)); controller.SetPhase(phase_t::status); EXPECT_CALL(*bus, Acquire); EXPECT_CALL(*bus, GetRST()); - EXPECT_CALL(*bus, Reset).Times(0); EXPECT_CALL(controller, Reset).Times(0); - controller.Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT); + controller.Error(sense_key::aborted_command, asc::no_additional_sense_information, status::reservation_conflict); EXPECT_EQ(phase_t::busfree, controller.GetPhase()); controller.SetPhase(phase_t::msgin); EXPECT_CALL(*bus, Acquire); EXPECT_CALL(*bus, GetRST()); - EXPECT_CALL(*bus, Reset).Times(0); EXPECT_CALL(controller, Reset).Times(0); - controller.Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT); + controller.Error(sense_key::aborted_command, asc::no_additional_sense_information, status::reservation_conflict); EXPECT_EQ(phase_t::busfree, controller.GetPhase()); controller.SetPhase(phase_t::reserved); EXPECT_CALL(*bus, Acquire); EXPECT_CALL(*bus, GetRST()); - EXPECT_CALL(*bus, Reset).Times(0); EXPECT_CALL(controller, Reset).Times(0); EXPECT_CALL(controller, Status); - controller.Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT); - EXPECT_EQ(status::RESERVATION_CONFLICT, controller.GetStatus()); + controller.Error(sense_key::aborted_command, asc::no_additional_sense_information, status::reservation_conflict); + EXPECT_EQ(status::reservation_conflict, controller.GetStatus()); EXPECT_EQ(phase_t::reserved, controller.GetPhase()); } TEST(ScsiControllerTest, RequestSense) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); + auto controller = make_shared(bus, 0); auto device = make_shared(0); - const unordered_map params; - device->Init(params); + EXPECT_TRUE(device->Init({})); controller->AddDevice(device); - auto& cmd = controller->GetCmd(); // ALLOCATION LENGTH - cmd[4] = 255; + controller->SetCmdByte(4, 255); // Non-existing LUN - cmd[1] = 0x20; + controller->SetCmdByte(1, 0x20); device->SetReady(true); EXPECT_CALL(*controller, Status); device->Dispatch(scsi_command::eCmdRequestSense); - EXPECT_EQ(status::GOOD, controller->GetStatus()) << "Wrong CHECK CONDITION for non-existing LUN"; + EXPECT_EQ(status::good, controller->GetStatus()) << "Wrong CHECK CONDITION for non-existing LUN"; } diff --git a/cpp/test/scsi_daynaport_test.cpp b/cpp/test/scsi_daynaport_test.cpp index 122ff69b..7b54a3f3 100644 --- a/cpp/test/scsi_daynaport_test.cpp +++ b/cpp/test/scsi_daynaport_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -13,202 +13,168 @@ TEST(ScsiDaynaportTest, Inquiry) { - TestInquiry(SCDP, device_type::PROCESSOR, scsi_level::SCSI_2, "Dayna SCSI/Link 1.4a", 0x20, false); + TestInquiry::Inquiry(SCDP, device_type::processor, scsi_level::scsi_2, "Dayna SCSI/Link 1.4a", 0x20, false); } TEST(ScsiDaynaportTest, TestUnitReady) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto daynaport = CreateDevice(SCDP, *controller); + auto [controller, daynaport] = CreateDevice(SCDP); EXPECT_CALL(*controller, Status()); daynaport->Dispatch(scsi_command::eCmdTestUnitReady); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(ScsiDaynaportTest, Read) { - vector buf(0); - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto daynaport = dynamic_pointer_cast(CreateDevice(SCDP, *controller)); - - auto& cmd = controller->GetCmd(); + auto [controller, daynaport] = CreateDevice(SCDP); // ALLOCATION LENGTH - cmd[4] = 1; - EXPECT_EQ(0, daynaport->Read(cmd, buf, 0)) << "Trying to read the root sector must fail"; + controller->SetCmdByte(4, 1); + vector buf(0); + EXPECT_EQ(0, dynamic_pointer_cast(daynaport)->Read(controller->GetCmd(), buf, 0)) << "Trying to read the root sector must fail"; } -TEST(ScsiDaynaportTest, WriteBytes) +TEST(ScsiDaynaportTest, Write) { - vector buf(0); - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto daynaport = dynamic_pointer_cast(CreateDevice(SCDP, *controller)); - - auto& cmd = controller->GetCmd(); + auto [controller, daynaport] = CreateDevice(SCDP); // Unknown data format - cmd[5] = 0xff; - EXPECT_TRUE(daynaport->WriteBytes(cmd, buf, 0)); + controller->SetCmdByte(5, 0xff); + vector buf(0); + EXPECT_TRUE(dynamic_pointer_cast(daynaport)->Write(controller->GetCmd(), buf)); } TEST(ScsiDaynaportTest, Read6) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto daynaport = CreateDevice(SCDP, *controller); + auto [controller, daynaport] = CreateDevice(SCDP); + // Required by the bullseye clang++ compiler + auto d = daynaport; - auto& cmd = controller->GetCmd(); - - cmd[5] = 0xff; - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdRead6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(5, 0xff); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdRead6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Invalid data format"; } TEST(ScsiDaynaportTest, Write6) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto daynaport = CreateDevice(SCDP, *controller); + auto [controller, daynaport] = CreateDevice(SCDP); + // Required by the bullseye clang++ compiler + auto d = daynaport; - auto& cmd = controller->GetCmd(); - - cmd[5] = 0x00; - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(5, 0x00); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Invalid transfer length"; - cmd[3] = -1; - cmd[4] = -8; - cmd[5] = 0x80; - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(3, -1); + controller->SetCmdByte(4, -8); + controller->SetCmdByte(5, 0x08); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Invalid transfer length"; - cmd[3] = 0; - cmd[4] = 0; - cmd[5] = 0xff; - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(3, 0); + controller->SetCmdByte(4, 0); + controller->SetCmdByte(5, 0xff); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Invalid transfer length"; } TEST(ScsiDaynaportTest, TestRetrieveStats) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto daynaport = CreateDevice(SCDP, *controller); - - auto& cmd = controller->GetCmd(); + auto [controller, daynaport] = CreateDevice(SCDP); // ALLOCATION LENGTH - cmd[4] = 255; + controller->SetCmdByte(4, 255); EXPECT_CALL(*controller, DataIn()); daynaport->Dispatch(scsi_command::eCmdRetrieveStats); } TEST(ScsiDaynaportTest, SetInterfaceMode) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto daynaport = CreateDevice(SCDP, *controller); - - auto& cmd = controller->GetCmd(); + auto [controller, daynaport] = CreateDevice(SCDP); + // Required by the bullseye clang++ compiler + auto d = daynaport; // Unknown interface command - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdSetIfaceMode); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_COMMAND_OPERATION_CODE)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSetIfaceMode); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_command_operation_code)))); // Not implemented, do nothing - cmd[5] = SCSIDaynaPort::CMD_SCSILINK_SETMODE; + controller->SetCmdByte(5, SCSIDaynaPort::CMD_SCSILINK_SETMODE); EXPECT_CALL(*controller, Status()); daynaport->Dispatch(scsi_command::eCmdSetIfaceMode); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); - cmd[5] = SCSIDaynaPort::CMD_SCSILINK_SETMAC; + controller->SetCmdByte(5, SCSIDaynaPort::CMD_SCSILINK_SETMAC); EXPECT_CALL(*controller, DataOut()); daynaport->Dispatch(scsi_command::eCmdSetIfaceMode); // Not implemented - cmd[5] = SCSIDaynaPort::CMD_SCSILINK_STATS; - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdSetIfaceMode); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_COMMAND_OPERATION_CODE)))); + controller->SetCmdByte(5, SCSIDaynaPort::CMD_SCSILINK_STATS); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSetIfaceMode); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_command_operation_code)))); // Not implemented - cmd[5] = SCSIDaynaPort::CMD_SCSILINK_ENABLE; - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdSetIfaceMode); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_COMMAND_OPERATION_CODE)))); + controller->SetCmdByte(5, SCSIDaynaPort::CMD_SCSILINK_ENABLE); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSetIfaceMode); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_command_operation_code)))); // Not implemented - cmd[5] = SCSIDaynaPort::CMD_SCSILINK_SET; - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdSetIfaceMode); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_COMMAND_OPERATION_CODE)))); + controller->SetCmdByte(5, SCSIDaynaPort::CMD_SCSILINK_SET); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSetIfaceMode); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_command_operation_code)))); } TEST(ScsiDaynaportTest, SetMcastAddr) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto daynaport = CreateDevice(SCDP, *controller); + auto [controller, daynaport] = CreateDevice(SCDP); + // Required by the bullseye clang++ compiler + auto d = daynaport; - auto& cmd = controller->GetCmd(); - - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdSetMcastAddr); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSetMcastAddr); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Length of 0 is not supported"; - cmd[4] = 1; + controller->SetCmdByte(4, 1); EXPECT_CALL(*controller, DataOut()); daynaport->Dispatch(scsi_command::eCmdSetMcastAddr); } TEST(ScsiDaynaportTest, EnableInterface) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto daynaport = CreateDevice(SCDP, *controller); - - auto& cmd = controller->GetCmd(); + auto [controller, daynaport] = CreateDevice(SCDP); + // Required by the bullseye clang++ compiler + auto d = daynaport; // Enable - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdEnableInterface); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ABORTED_COMMAND), - Property(&scsi_exception::get_asc, asc::NO_ADDITIONAL_SENSE_INFORMATION)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdEnableInterface); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::aborted_command), + Property(&scsi_exception::get_asc, asc::no_additional_sense_information)))); // Disable - cmd[5] = 0x80; - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdEnableInterface); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ABORTED_COMMAND), - Property(&scsi_exception::get_asc, asc::NO_ADDITIONAL_SENSE_INFORMATION)))); + controller->SetCmdByte(5, 0x00); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdEnableInterface); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::aborted_command), + Property(&scsi_exception::get_asc, asc::no_additional_sense_information)))); } TEST(ScsiDaynaportTest, GetSendDelay) { SCSIDaynaPort daynaport(0); - const unordered_map params; - daynaport.Init(params); + daynaport.Init({}); EXPECT_EQ(6, daynaport.GetSendDelay()); } diff --git a/cpp/test/scsi_host_bridge_test.cpp b/cpp/test/scsi_host_bridge_test.cpp index 42a584da..f0b2843f 100644 --- a/cpp/test/scsi_host_bridge_test.cpp +++ b/cpp/test/scsi_host_bridge_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -11,5 +11,5 @@ TEST(ScsiHostBridgeTest, Inquiry) { - TestInquiry(SCBR, device_type::COMMUNICATIONS, scsi_level::SCSI_2, "PiSCSI RASCSI BRIDGE ", 0x27, false); + TestInquiry::Inquiry(SCBR, device_type::communications, scsi_level::scsi_2, "PiSCSI RASCSI BRIDGE ", 0x27, false); } diff --git a/cpp/test/scsi_printer_test.cpp b/cpp/test/scsi_printer_test.cpp index 821d5c5a..7f29f4cf 100644 --- a/cpp/test/scsi_printer_test.cpp +++ b/cpp/test/scsi_printer_test.cpp @@ -3,27 +3,22 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "mocks.h" #include "shared/piscsi_exceptions.h" -#include "controllers/controller_manager.h" #include "devices/scsi_printer.h" using namespace std; TEST(ScsiPrinterTest, Init) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto printer = CreateDevice(SCLP, *controller); - - unordered_map params; - EXPECT_TRUE(printer->Init(params)); + auto [controller, printer] = CreateDevice(SCLP); + EXPECT_TRUE(printer->Init({})); + param_map params; params["cmd"] = "missing_filename_specifier"; EXPECT_FALSE(printer->Init(params)); @@ -33,99 +28,80 @@ TEST(ScsiPrinterTest, Init) TEST(ScsiPrinterTest, TestUnitReady) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto printer = CreateDevice(SCLP, *controller); + auto [controller, printer] = CreateDevice(SCLP); EXPECT_CALL(*controller, Status()); printer->Dispatch(scsi_command::eCmdTestUnitReady); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(ScsiPrinterTest, Inquiry) { - TestInquiry(SCLP, device_type::PRINTER, scsi_level::SCSI_2, "PiSCSI SCSI PRINTER ", 0x1f, false); + TestInquiry::Inquiry(SCLP, device_type::printer, scsi_level::scsi_2, "PiSCSI SCSI PRINTER ", 0x1f, false); } TEST(ScsiPrinterTest, ReserveUnit) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto printer = CreateDevice(SCLP, *controller); + auto [controller, printer] = CreateDevice(SCLP); EXPECT_CALL(*controller, Status()).Times(1); printer->Dispatch(scsi_command::eCmdReserve6); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(ScsiPrinterTest, ReleaseUnit) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto printer = CreateDevice(SCLP, *controller); + auto [controller, printer] = CreateDevice(SCLP); EXPECT_CALL(*controller, Status()).Times(1); printer->Dispatch(scsi_command::eCmdRelease6); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(ScsiPrinterTest, SendDiagnostic) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto printer = CreateDevice(SCLP, *controller); + auto [controller, printer] = CreateDevice(SCLP); EXPECT_CALL(*controller, Status()).Times(1); printer->Dispatch(scsi_command::eCmdSendDiagnostic); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(ScsiPrinterTest, Print) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto printer = CreateDevice(SCLP, *controller); - - auto& cmd = controller->GetCmd(); + auto [controller, printer] = CreateDevice(SCLP); + // Required by the bullseye clang++ compiler + auto p = printer; EXPECT_CALL(*controller, DataOut()); printer->Dispatch(scsi_command::eCmdPrint); - cmd[3] = 0xff; - cmd[4] = 0xff; - EXPECT_THAT([&] { printer->Dispatch(scsi_command::eCmdPrint); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(3, 0xff); + controller->SetCmdByte(4, 0xff); + EXPECT_THAT([&] { p->Dispatch(scsi_command::eCmdPrint); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Buffer overflow was not reported"; } TEST(ScsiPrinterTest, StopPrint) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto printer = CreateDevice(SCLP, *controller); + auto [controller, printer] = CreateDevice(SCLP); EXPECT_CALL(*controller, Status()); printer->Dispatch(scsi_command::eCmdStopPrint); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(ScsiPrinterTest, SynchronizeBuffer) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto printer = CreateDevice(SCLP, *controller); + auto [controller, printer] = CreateDevice(SCLP); + // Required by the bullseye clang++ compiler + auto p = printer; - EXPECT_THAT([&] { printer->Dispatch(scsi_command::eCmdSynchronizeBuffer); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ABORTED_COMMAND), - Property(&scsi_exception::get_asc, asc::NO_ADDITIONAL_SENSE_INFORMATION)))) + EXPECT_THAT([&] { p->Dispatch(scsi_command::eCmdSynchronizeBuffer); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::aborted_command), + Property(&scsi_exception::get_asc, asc::no_additional_sense_information)))) << "Nothing to print"; // Further testing would use the printing system @@ -133,11 +109,8 @@ TEST(ScsiPrinterTest, SynchronizeBuffer) TEST(ScsiPrinterTest, WriteByteSequence) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto printer = CreateDevice(SCLP, *controller); + auto [controller, printer] = CreateDevice(SCLP); - vector buf(1); - EXPECT_TRUE(printer->WriteByteSequence(buf, buf.size())); + const vector buf(1); + EXPECT_TRUE(printer->WriteByteSequence(buf)); } diff --git a/cpp/test/scsicd_test.cpp b/cpp/test/scsicd_test.cpp index 925ad06d..fa5d8743 100644 --- a/cpp/test/scsicd_test.cpp +++ b/cpp/test/scsicd_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -29,16 +29,15 @@ void ScsiCdTest_SetUpModePages(map>& pages) TEST(ScsiCdTest, Inquiry) { - TestInquiry(SCCD, device_type::CD_ROM, scsi_level::SCSI_2, "PiSCSI SCSI CD-ROM ", 0x1f, true); + TestInquiry::Inquiry(SCCD, device_type::cd_rom, scsi_level::scsi_2, "PiSCSI SCSI CD-ROM ", 0x1f, true); - TestInquiry(SCCD, device_type::CD_ROM, scsi_level::SCSI_1_CCS, "PiSCSI SCSI CD-ROM ", 0x1f, true, ".is1"); + TestInquiry::Inquiry(SCCD, device_type::cd_rom, scsi_level::scsi_1_ccs, "PiSCSI SCSI CD-ROM ", 0x1f, true, "file.is1"); } TEST(ScsiCdTest, SetUpModePages) { map> pages; - const unordered_set sector_sizes; - MockSCSICD cd(0, sector_sizes); + MockSCSICD cd(0, {}); // Non changeable cd.SetUpModePages(pages, 0x3f, false); @@ -52,11 +51,10 @@ TEST(ScsiCdTest, SetUpModePages) TEST(ScsiCdTest, Open) { - const unordered_set sector_sizes; - MockSCSICD cd_iso(0, sector_sizes); - MockSCSICD cd_cue(0, sector_sizes); - MockSCSICD cd_raw(0, sector_sizes); - MockSCSICD cd_physical(0, sector_sizes); + MockSCSICD cd_iso(0, {}); + MockSCSICD cd_cue(0, {}); + MockSCSICD cd_raw(0, {}); + MockSCSICD cd_physical(0, {}); EXPECT_THROW(cd_iso.Open(), io_exception) << "Missing filename"; @@ -112,19 +110,16 @@ TEST(ScsiCdTest, Open) TEST(ScsiCdTest, ReadToc) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); + auto controller = make_shared(); const unordered_set sector_sizes; auto cd = make_shared(0, sector_sizes); - const unordered_map params; - cd->Init(params); + EXPECT_TRUE(cd->Init({})); controller->AddDevice(cd); EXPECT_THAT([&] { cd->Dispatch(scsi_command::eCmdReadToc); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))); + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))); // Further testing requires filesystem access } diff --git a/cpp/test/scsictl_commands_test.cpp b/cpp/test/scsictl_commands_test.cpp index b9394dd1..036f464c 100644 --- a/cpp/test/scsictl_commands_test.cpp +++ b/cpp/test/scsictl_commands_test.cpp @@ -92,8 +92,11 @@ TEST(ScsictlCommandsTest, Execute) command.set_operation(OPERATION_INFO); EXPECT_THROW(commands.Execute("", "", "", "", ""), io_exception); - command.set_operation(NO_OPERATION); + command.set_operation(DETACH_ALL); EXPECT_THROW(commands.Execute("", "", "", "", ""), io_exception); + + command.set_operation(NO_OPERATION); + EXPECT_FALSE(commands.Execute("", "", "", "", "")); } TEST(ScsictlCommandsTest, CommandDevicesInfo) diff --git a/cpp/test/scsictl_display_test.cpp b/cpp/test/scsictl_display_test.cpp index 4fd8f71c..e5c53269 100644 --- a/cpp/test/scsictl_display_test.cpp +++ b/cpp/test/scsictl_display_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // These tests only test key aspects of the expected output, because the output may change over time. // @@ -51,10 +51,9 @@ TEST(ScsictlDisplayTest, DisplayDeviceInfo) EXPECT_NE(string::npos, s.find(to_string(1234 *4321))); device.mutable_properties()->set_supports_file(true); - auto file = make_unique(); + auto file = device.mutable_file(); file->set_name("filename"); - device.set_allocated_file(file.release()); - s = display.DisplayDeviceInfo(device); //NOSONAR The allocated memory is managed by protobuf + s = display.DisplayDeviceInfo(device); EXPECT_FALSE(s.empty()); EXPECT_NE(string::npos, s.find("filename")); @@ -154,7 +153,7 @@ TEST(ScsictlDisplayTest, DisplayReservedIdsInfo) PbReservedIdsInfo info; string s = display.DisplayReservedIdsInfo(info); - EXPECT_FALSE(s.empty()); + EXPECT_TRUE(s.empty()); info.mutable_ids()->Add(5); s = display.DisplayReservedIdsInfo(info); diff --git a/cpp/test/scsihd_nec_test.cpp b/cpp/test/scsihd_nec_test.cpp index cfff8485..6b5cbd7c 100644 --- a/cpp/test/scsihd_nec_test.cpp +++ b/cpp/test/scsihd_nec_test.cpp @@ -9,7 +9,6 @@ #include "mocks.h" #include "shared/piscsi_exceptions.h" -#include "controllers/controller_manager.h" #include "devices/scsihd_nec.h" #include #include @@ -29,7 +28,7 @@ void ScsiHdNecTest_SetUpModePages(map>& pages) TEST(ScsiHdNecTest, Inquiry) { - TestInquiry(SCHD, device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, "PiSCSI ", 0x1f, false, ".hdn"); + TestInquiry::Inquiry(SCHD, device_type::direct_access, scsi_level::scsi_1_ccs, "PiSCSI ", 0x1f, false, "file.hdn"); } TEST(ScsiHdNecTest, SetUpModePages) @@ -134,19 +133,19 @@ TEST(ScsiHdNecTest, SetParameters) ofstream out; out.open(hdi); - const array cylinders1 = { 1, 0, 0, 0 }; + const array cylinders1 = { 1, 0, 0, 0 }; out.seekp(28); out.write(cylinders1.data(), cylinders1.size()); - const array heads1 = { 1, 0, 0, 0 }; + const array heads1 = { 1, 0, 0, 0 }; out.seekp(24); out.write(heads1.data(), heads1.size()); - const array sectors1 = { 1, 0, 0, 0 }; + const array sectors1 = { 1, 0, 0, 0 }; out.seekp(20); out.write(sectors1.data(), sectors1.size()); - const array sector_size1 = { 0, 2, 0, 0 }; + const array sector_size1 = { 0, 2, 0, 0 }; out.seekp(16); out.write(sector_size1.data(), sector_size1.size()); - const array image_size = { 0, 2, 0, 0 }; + const array image_size = { 0, 2, 0, 0 }; out.seekp(12); out.write(image_size.data(), image_size.size()); out.close(); @@ -179,18 +178,18 @@ TEST(ScsiHdNecTest, SetParameters) out.open(nhd); out << "T98HDDIMAGE.R0"; - const array cylinders2 = { 1, 0 }; + const array cylinders2 = { 1, 0 }; out.seekp(0x114); out.write(cylinders2.data(), cylinders2.size()); - const array heads2 = { 1, 0 }; + const array heads2 = { 1, 0 }; out.seekp(0x118); out.write(heads2.data(), heads2.size()); - const array sectors2 = { 1, 0 }; + const array sectors2 = { 1, 0 }; out.seekp(0x11a); out.write(sectors2.data(), sectors2.size()); out.seekp(0x11c); out.write(sector_size2.data(), sector_size2.size()); - const array image_offset = { 1, 0, 0, 0 }; + const array image_offset = { 1, 0, 0, 0 }; out.seekp(0x110); out.write(image_offset.data(), image_offset.size()); out.close(); diff --git a/cpp/test/scsihd_test.cpp b/cpp/test/scsihd_test.cpp index 179c7170..1cdacad0 100644 --- a/cpp/test/scsihd_test.cpp +++ b/cpp/test/scsihd_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -23,23 +23,21 @@ void ScsiHdTest_SetUpModePages(map>& pages) TEST(ScsiHdTest, Inquiry) { - TestInquiry(SCHD, device_type::DIRECT_ACCESS, scsi_level::SCSI_2, "PiSCSI ", 0x1f, false); + TestInquiry::Inquiry(SCHD, device_type::direct_access, scsi_level::scsi_2, "PiSCSI ", 0x1f, false); - TestInquiry(SCHD, device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, "PiSCSI ", 0x1f, false, ".hd1"); + TestInquiry::Inquiry(SCHD, device_type::direct_access, scsi_level::scsi_1_ccs, "PiSCSI ", 0x1f, false, "file.hd1"); } TEST(ScsiHdTest, SupportsSaveParameters) { - const unordered_set sector_sizes; - MockSCSIHD hd(0, sector_sizes, false); + MockSCSIHD hd(0, {}, false); EXPECT_TRUE(hd.SupportsSaveParameters()); } TEST(ScsiHdTest, FinalizeSetup) { - const unordered_set sector_sizes; - MockSCSIHD hd(0, sector_sizes, false); + MockSCSIHD hd(0, {}, false); hd.SetSectorSizeInBytes(1024); EXPECT_THROW(hd.FinalizeSetup(0), io_exception) << "Device has 0 blocks"; @@ -47,10 +45,9 @@ TEST(ScsiHdTest, FinalizeSetup) TEST(ScsiHdTest, GetProductData) { - const unordered_set sector_sizes; - MockSCSIHD hd_kb(0, sector_sizes, false); - MockSCSIHD hd_mb(0, sector_sizes, false); - MockSCSIHD hd_gb(0, sector_sizes, false); + MockSCSIHD hd_kb(0, {}, false); + MockSCSIHD hd_mb(0, {}, false); + MockSCSIHD hd_gb(0, {}, false); const path filename = CreateTempFile(1); hd_kb.SetFilename(string(filename)); @@ -79,8 +76,7 @@ TEST(ScsiHdTest, GetProductData) TEST(ScsiHdTest, SetUpModePages) { map> pages; - const unordered_set sector_sizes; - MockSCSIHD hd(0, sector_sizes, false); + MockSCSIHD hd(0, {}, false); // Non changeable hd.SetUpModePages(pages, 0x3f, false); @@ -94,8 +90,7 @@ TEST(ScsiHdTest, SetUpModePages) TEST(ScsiHdTest, ModeSelect) { - const unordered_set sector_sizes = { 512 }; - MockSCSIHD hd(0, sector_sizes, false); + MockSCSIHD hd(0, { 512 }, false); vector cmd(10); vector buf(255); diff --git a/cpp/test/scsimo_test.cpp b/cpp/test/scsimo_test.cpp index 21447696..2422c479 100644 --- a/cpp/test/scsimo_test.cpp +++ b/cpp/test/scsimo_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -22,14 +22,13 @@ void ScsiMo_SetUpModePages(map>& pages) TEST(ScsiMoTest, Inquiry) { - TestInquiry(SCMO, device_type::OPTICAL_MEMORY, scsi_level::SCSI_2, "PiSCSI SCSI MO ", 0x1f, true); + TestInquiry::Inquiry(SCMO, device_type::optical_memory, scsi_level::scsi_2, "PiSCSI SCSI MO ", 0x1f, true); } TEST(ScsiMoTest, SupportsSaveParameters) { map> pages; - const unordered_set sector_sizes; - MockSCSIMO mo(0, sector_sizes); + MockSCSIMO mo(0, {}); EXPECT_TRUE(mo.SupportsSaveParameters()); } @@ -37,8 +36,7 @@ TEST(ScsiMoTest, SupportsSaveParameters) TEST(ScsiMoTest, SetUpModePages) { map> pages; - const unordered_set sector_sizes; - MockSCSIMO mo(0, sector_sizes); + MockSCSIMO mo(0, {}); // Non changeable mo.SetUpModePages(pages, 0x3f, false); @@ -53,8 +51,7 @@ TEST(ScsiMoTest, SetUpModePages) TEST(ScsiMoTest, TestAddVendorPage) { map> pages; - const unordered_set sector_sizes; - MockSCSIMO mo(0, sector_sizes); + MockSCSIMO mo(0, {}); mo.SetReady(true); mo.SetUpModePages(pages, 0x21, false); @@ -125,8 +122,7 @@ TEST(ScsiMoTest, TestAddVendorPage) TEST(ScsiMoTest, ModeSelect) { - const unordered_set sector_sizes = { 1024, 2048 }; - MockSCSIMO mo(0, sector_sizes); + MockSCSIMO mo(0, { 1024, 2048 }); vector cmd(10); vector buf(255); diff --git a/cpp/test/storage_device_test.cpp b/cpp/test/storage_device_test.cpp index 9fbed528..033766f4 100644 --- a/cpp/test/storage_device_test.cpp +++ b/cpp/test/storage_device_test.cpp @@ -3,18 +3,19 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "mocks.h" +#include "shared/piscsi_util.h" #include "shared/piscsi_exceptions.h" #include "devices/storage_device.h" #include using namespace filesystem; -TEST(StorageDeviceTest, Filename) +TEST(StorageDeviceTest, SetGetFilename) { MockStorageDevice device; @@ -62,6 +63,8 @@ TEST(StorageDeviceTest, MediumChanged) { MockStorageDevice device; + EXPECT_FALSE(device.IsMediumChanged()); + device.SetMediumChanged(true); EXPECT_TRUE(device.IsMediumChanged()); @@ -72,22 +75,26 @@ TEST(StorageDeviceTest, MediumChanged) TEST(StorageDeviceTest, GetIdsForReservedFile) { const int ID = 1; - const int LUN = 2; + const int LUN = 0; + auto bus = make_shared(); + ControllerManager controller_manager; + MockAbstractController controller(bus, ID); + auto device = make_shared(LUN); + device->SetFilename("filename"); StorageDevice::UnreserveAll(); - MockStorageDevice device; - device.SetFilename("filename"); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device)); const auto [id1, lun1] = StorageDevice::GetIdsForReservedFile("filename"); EXPECT_EQ(-1, id1); EXPECT_EQ(-1, lun1); - device.ReserveFile("filename", ID, LUN); + device->ReserveFile(); const auto [id2, lun2] = StorageDevice::GetIdsForReservedFile("filename"); EXPECT_EQ(ID, id2); EXPECT_EQ(LUN, lun2); - device.UnreserveFile(); + device->UnreserveFile(); const auto [id3, lun3] = StorageDevice::GetIdsForReservedFile("filename"); EXPECT_EQ(-1, id3); EXPECT_EQ(-1, lun3); @@ -95,9 +102,17 @@ TEST(StorageDeviceTest, GetIdsForReservedFile) TEST(StorageDeviceTest, UnreserveAll) { - MockStorageDevice device; - device.ReserveFile("filename", 2, 31); + const int ID = 1; + const int LUN = 0; + auto bus = make_shared(); + ControllerManager controller_manager; + MockAbstractController controller(bus, ID); + auto device = make_shared(LUN); + device->SetFilename("filename"); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device)); + + device->ReserveFile(); StorageDevice::UnreserveAll(); const auto [id, lun] = StorageDevice::GetIdsForReservedFile("filename"); EXPECT_EQ(-1, id); @@ -107,18 +122,24 @@ TEST(StorageDeviceTest, UnreserveAll) TEST(StorageDeviceTest, GetSetReservedFiles) { const int ID = 1; - const int LUN = 16; + const int LUN = 0; + auto bus = make_shared(); + ControllerManager controller_manager; + MockAbstractController controller(bus, ID); + auto device = make_shared(LUN); + device->SetFilename("filename"); - MockStorageDevice device; - device.ReserveFile("filename", ID, LUN); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device)); - const unordered_map reserved_files = StorageDevice::GetReservedFiles(); + device->ReserveFile(); + + const auto& reserved_files = StorageDevice::GetReservedFiles(); EXPECT_EQ(1, reserved_files.size()); - EXPECT_NE(reserved_files.end(), reserved_files.find("filename")); + EXPECT_TRUE(reserved_files.contains("filename")); StorageDevice::SetReservedFiles(reserved_files); EXPECT_EQ(1, reserved_files.size()); - EXPECT_NE(reserved_files.end(), reserved_files.find("filename")); + EXPECT_TRUE(reserved_files.contains("filename")); } TEST(StorageDeviceTest, FileExists) @@ -127,17 +148,6 @@ TEST(StorageDeviceTest, FileExists) EXPECT_TRUE(StorageDevice::FileExists("/dev/null")); } -TEST(StorageDeviceTest, IsReadOnlyFile) -{ - MockStorageDevice device; - - device.SetFilename("/dev/null"); - EXPECT_FALSE(device.IsReadOnlyFile()); - - device.SetFilename("/dev/mem"); - EXPECT_TRUE(device.IsReadOnlyFile()); -} - TEST(StorageDeviceTest, GetFileSize) { MockStorageDevice device; diff --git a/cpp/test/test_setup.cpp b/cpp/test/test_setup.cpp index cfa0c20d..63dc4e29 100644 --- a/cpp/test/test_setup.cpp +++ b/cpp/test/test_setup.cpp @@ -3,35 +3,34 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include -#include "spdlog/spdlog.h" - -// Also used by the PiscsiExecutor tests -bool enable_logging; //NOSONAR Must be global in order to be shared with the tests - -class Environment final : public ::testing::Environment -{ -public: - - Environment() = default; - ~Environment() override = default; - - void SetUp() override { spdlog::set_level(enable_logging ? spdlog::level::trace : spdlog::level::off); } -}; +#include int main(int argc, char *[]) { - // If any argument is provided the log level is set to trace - enable_logging = argc > 1; + const bool disable_logging = argc <= 1; - testing::AddGlobalTestEnvironment(new Environment()); + // If any argument is provided the log level is set to trace + spdlog::set_level(disable_logging ? spdlog::level::off : spdlog::level::trace); + + int fd = -1; + if (disable_logging) { + fd = open("/dev/null", O_WRONLY); + dup2(fd, STDERR_FILENO); + } testing::InitGoogleTest(); - return RUN_ALL_TESTS(); + const int result = RUN_ALL_TESTS(); + + if (fd != -1) { + close(fd); + } + + return result; } diff --git a/cpp/test/test_shared.cpp b/cpp/test/test_shared.cpp index bcce2600..0e913460 100644 --- a/cpp/test/test_shared.cpp +++ b/cpp/test/test_shared.cpp @@ -8,7 +8,6 @@ //--------------------------------------------------------------------------- #include "test_shared.h" -#include "controllers/controller_manager.h" #include "mocks.h" #include "shared/piscsi_exceptions.h" #include "shared/piscsi_version.h" @@ -28,38 +27,33 @@ const path test_data_temp_path(temp_directory_path() / path(fmt::format("piscsi-test-{}", getpid()))); // NOSONAR Publicly writable directory is fine here -shared_ptr CreateDevice(PbDeviceType type, MockAbstractController& controller, const string& extension) +pair, shared_ptr> CreateDevice(PbDeviceType type, const string& extension) { DeviceFactory device_factory; + auto controller = make_shared>(0); auto device = device_factory.CreateDevice(type, 0, extension); - unordered_map params; - device->Init(params); + device->Init({}); - controller.AddDevice(device); + EXPECT_TRUE(controller->AddDevice(device)); - return device; + return { controller, device }; } -void TestInquiry(PbDeviceType type, device_type t, scsi_level l, const string& ident, int additional_length, - bool removable, const string& extension) +void TestInquiry::Inquiry(PbDeviceType type, device_type t, scsi_level l, const string& ident, int additional_length, + bool removable, const string& extension) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto device = CreateDevice(type, *controller, extension); - - auto& cmd = controller->GetCmd(); + auto [controller, device] = CreateDevice(type, extension); // ALLOCATION LENGTH - cmd[4] = 255; + controller->SetCmdByte(4, 255); EXPECT_CALL(*controller, DataIn()); device->Dispatch(scsi_command::eCmdInquiry); const vector& buffer = controller->GetBuffer(); EXPECT_EQ(t, static_cast(buffer[0])); EXPECT_EQ(removable ? 0x80 : 0x00, buffer[1]); EXPECT_EQ(l, static_cast(buffer[2])); - EXPECT_EQ(l > scsi_level::SCSI_2 ? scsi_level::SCSI_2 : l, static_cast(buffer[3])); + EXPECT_EQ(l > scsi_level::scsi_2 ? scsi_level::scsi_2 : l, static_cast(buffer[3])); EXPECT_EQ(additional_length, buffer[4]); string product_data; if (ident.size() == 24) { @@ -88,13 +82,18 @@ pair OpenTempFile() } path CreateTempFile(int size) +{ + const auto data = vector(size); + return CreateTempFileWithData(data); +} + +path CreateTempFileWithData(const span data) { const auto [fd, filename] = OpenTempFile(); - vector data(size); const size_t count = write(fd, data.data(), data.size()); - close(fd); EXPECT_EQ(count, data.size()) << "Couldn't create temporary file '" << string(filename) << "'"; + close(fd); return path(filename); } @@ -110,14 +109,14 @@ void CreateTempFileWithData(const string& filename, vector& data) FILE* fp = fopen(new_filename.c_str(), "wb"); if (fp == nullptr) { - printf("ERROR: Unable to open file %s\n", new_filename.c_str()); + cerr << "ERROR: Unable to open file '" << new_filename << "'"; return; } if (const size_t size_written = fwrite(&data[0], sizeof(uint8_t), data.size(), fp); size_written != sizeof(vector::value_type) * data.size()) { - printf("Expected to write %zu bytes, but only wrote %zu to %s", size_written, - sizeof(vector::value_type) * data.size(), filename.c_str()); + cerr << "ERROR: Expected to write " << sizeof(vector::value_type) * data.size() << " bytes" + << ", but only wrote " << data.size() << " to '" << filename << "'"; } fclose(fp); } @@ -129,14 +128,14 @@ void DeleteTempFile(const string& filename) remove(temp_file); } -void CleanupAllTempFiles() +void CleanUpAllTempFiles() { remove_all(test_data_temp_path); } string ReadTempFileToString(const string& filename) { - path temp_file = test_data_temp_path / path(filename); + const path temp_file = test_data_temp_path / path(filename); ifstream in_fs(temp_file); stringstream buffer; buffer << in_fs.rdbuf(); diff --git a/cpp/test/test_shared.h b/cpp/test/test_shared.h index 0c4b7c6a..97d59606 100644 --- a/cpp/test/test_shared.h +++ b/cpp/test/test_shared.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -13,6 +13,7 @@ #include "shared/scsi.h" #include #include +#include #include using namespace std; @@ -24,22 +25,27 @@ class MockAbstractController; extern const path test_data_temp_path; -shared_ptr CreateDevice(PbDeviceType, MockAbstractController&, const string& = ""); - -void TestInquiry(PbDeviceType, scsi_defs::device_type, scsi_defs::scsi_level, const string&, int, bool, - const string& = ""); +pair, shared_ptr> CreateDevice(PbDeviceType, const string& = ""); pair OpenTempFile(); path CreateTempFile(int); +path CreateTempFileWithData(span); // create a file with the specified data void CreateTempFileWithData(const string&, vector&); void DeleteTempFile(const string&); // Call this at the end of every test case to make sure things are cleaned up -void CleanupAllTempFiles(); +void CleanUpAllTempFiles(); string ReadTempFileToString(const string& filename); int GetInt16(const vector&, int); uint32_t GetInt32(const vector&, int); + +// This class is needed in order to be declared as friend, required to have access to AbstractController::SetCmdByte +class TestInquiry { +public: + static void Inquiry(PbDeviceType, scsi_defs::device_type, scsi_defs::scsi_level, const string&, int, bool, + const string& = ""); +}; diff --git a/doc/piscsi.1 b/doc/piscsi.1 index a40418e2..5bc58f0f 100644 --- a/doc/piscsi.1 +++ b/doc/piscsi.1 @@ -56,7 +56,7 @@ The optional block size, either 512, 1024, 2048 or 4096 bytes. Default size is 5 The default folder for image files. For files in this folder no absolute path needs to be specified. The initial default folder is '~/images'. .TP .BR \-L\fI " " \fILOG_LEVEL[:ID:[LUN]] -The piscsi log level (trace, debug, info, warn, err, off). The default log level is 'info' for all devices unless a particular device ID and an optional LUN was provided. +The piscsi log level (trace, debug, info, warning, error, off). The default log level is 'info' for all devices unless a particular device ID and an optional LUN was provided. .TP .BR \-P\fI " " \fIACCESS_TOKEN_FILE Enable authentication and read the access token from the specified file. The access token file must be owned by root and must be readable by root only. diff --git a/doc/piscsi_man_page.txt b/doc/piscsi_man_page.txt index 727a4982..4816dfb7 100644 --- a/doc/piscsi_man_page.txt +++ b/doc/piscsi_man_page.txt @@ -1,96 +1,125 @@ !! ------ THIS FILE IS AUTO_GENERATED! DO NOT MANUALLY UPDATE!!! -!! ------ The native file is piscsi.1. Re-run 'make docs' after updating - - -piscsi(1) General Commands Manual piscsi(1) +!! ------ The native file is piscsi.1. Re-run 'make docs' after updating\n\n +piscsi(1) General Commands Manual piscsi(1) NAME piscsi - Emulates SCSI devices using the Raspberry Pi GPIO pins SYNOPSIS - piscsi [-F FOLDER] [-L LOG_LEVEL[:ID:[LUN]]] [-P ACCESS_TOKEN_FILE] [-R SCAN_DEPTH] [-h] [-n VENDOR:PRODUCT:REVISION] [-p - PORT] [-r RESERVED_IDS] [-n TYPE] [-v] [-z LOCALE] [-IDn:[u] FILE] [-HDn[:u] FILE]... + piscsi [-F FOLDER] [-L LOG_LEVEL[:ID:[LUN]]] [-P ACCESS_TOKEN_FILE] [-R + SCAN_DEPTH] [-h] [-n VENDOR:PRODUCT:REVISION] [-p PORT] [-r RE‐ + SERVED_IDS] [-n TYPE] [-v] [-z LOCALE] [-IDn:[u] FILE] [-HDn[:u] + FILE]... DESCRIPTION piscsi emulates SCSI devices using the Raspberry Pi GPIO pins. - In the arguments to PiSCSI, one or more SCSI (-IDn[:u]) devices can be specified. The number (n) after the ID or HD iden‐ - tifier specifies the ID number for that device. The optional number (u) specifies the LUN (logical unit) for that device. - The default LUN is 0. For SCSI: The ID is limited from 0-7. However, typically SCSI ID 7 is reserved for the "initiator" - (the host computer). The LUN is limited from 0-31. + In the arguments to PiSCSI, one or more SCSI (-IDn[:u]) devices can be + specified. The number (n) after the ID or HD identifier specifies the + ID number for that device. The optional number (u) specifies the LUN + (logical unit) for that device. The default LUN is 0. For SCSI: The ID + is limited from 0-7. However, typically SCSI ID 7 is reserved for the + "initiator" (the host computer). The LUN is limited from 0-31. - PiSCSI will determine the type of device based upon the file extension of the FILE argument. + PiSCSI will determine the type of device based upon the file extension + of the FILE argument. hd1: SCSI Hard Disk image (generic, non-removable, SCSI-1) hds: SCSI Hard Disk image (generic, non-removable) hdr: SCSI Hard Disk image (generic, removable) - hdn: SCSI Hard Disk image (NEC compatible - only used with PC-98 computers) - hdi: SCSI Hard Disk image (Anex86 proprietary - only used with PC-98 computers) - nhd: SCSI Hard Disk image (T98Next proprietary - only used with PC-98 computers) - hda: SCSI Hard Disk image (Apple compatible - typically used with Macintosh computers) - mos: SCSI Magneto-Optical image (generic - typically used with NeXT, X68000, etc.) + hdn: SCSI Hard Disk image (NEC compatible - only used with PC-98 + computers) + hdi: SCSI Hard Disk image (Anex86 proprietary - only used with + PC-98 computers) + nhd: SCSI Hard Disk image (T98Next proprietary - only used with + PC-98 computers) + hda: SCSI Hard Disk image (Apple compatible - typically used with + Macintosh computers) + mos: SCSI Magneto-Optical image (generic - typically used with + NeXT, X68000, etc.) iso: SCSI CD-ROM or DVD-ROM image (ISO 9660 image) is1: SCSI CD-ROM or DVD-ROM image (ISO 9660 image, SCSI-1) - For example, if you want to specify an Apple-compatible HD image on ID 0, you can use the following command: + For example, if you want to specify an Apple-compatible HD image on ID + 0, you can use the following command: sudo piscsi -ID0 /path/to/drive/hdimage.hda - Note: PiSCSI is a fork of RaSCSI. The two cannot be run in parallel on the same system. + Note: PiSCSI is a fork of RaSCSI. The two cannot be run in parallel on + the same system. - Once PiSCSI starts, it will open a socket (default port is 6868) to allow external management commands. If another process - is using this port, PiSCSI will terminate, since it is likely another instance of PiSCSI or RaSCSI. Once PiSCSI has ini‐ - tialized, the scsictl utility can be used to send commands. + Once PiSCSI starts, it will open a socket (default port is 6868) to al‐ + low external management commands. If another process is using this + port, PiSCSI will terminate, since it is likely another instance of + PiSCSI or RaSCSI. Once PiSCSI has initialized, the scsictl utility can + be used to send commands. - To quit PiSCSI, press Control + C. If it is running in the background, you can kill it using an INT signal. + To quit PiSCSI, press Control + C. If it is running in the background, + you can kill it using an INT signal. OPTIONS -b BLOCK_SIZE - The optional block size, either 512, 1024, 2048 or 4096 bytes. Default size is 512 bytes. + The optional block size, either 512, 1024, 2048 or 4096 bytes. + Default size is 512 bytes. -F FOLDER - The default folder for image files. For files in this folder no absolute path needs to be specified. The initial de‐ - fault folder is '~/images'. + The default folder for image files. For files in this folder no + absolute path needs to be specified. The initial default folder + is '~/images'. -L LOG_LEVEL[:ID:[LUN]] - The piscsi log level (trace, debug, info, warn, err, off). The default log level is 'info' for all devices unless a - particular device ID and an optional LUN was provided. + The piscsi log level (trace, debug, info, warning, error, off). + The default log level is 'info' for all devices unless a partic‐ + ular device ID and an optional LUN was provided. -P ACCESS_TOKEN_FILE - Enable authentication and read the access token from the specified file. The access token file must be owned by root - and must be readable by root only. + Enable authentication and read the access token from the speci‐ + fied file. The access token file must be owned by root and must + be readable by root only. -R SCAN_DEPTH - Scan for image files recursively, up to a depth of SCAN_DEPTH. Depth 0 means to ignore any folders within the de‐ - fault image filder. Be careful when using this option with many sub-folders in the default image folder. The default - depth is 1. + Scan for image files recursively, up to a depth of SCAN_DEPTH. + Depth 0 means to ignore any folders within the default image + filder. Be careful when using this option with many sub-folders + in the default image folder. The default depth is 1. -h Show a help page. -n VENDOR:PRODUCT:REVISION - Set the vendor, product and revision for the device, to be returned with the INQUIRY data. A complete set of name - components must be provided. VENDOR may have up to 8, PRODUCT up to 16, REVISION up to 4 characters. Padding with - blanks to the maxium length is automatically applied. Once set the name of a device cannot be changed. + Set the vendor, product and revision for the device, to be re‐ + turned with the INQUIRY data. A complete set of name components + must be provided. VENDOR may have up to 8, PRODUCT up to 16, RE‐ + VISION up to 4 characters. Padding with blanks to the maxium + length is automatically applied. Once set the name of a device + cannot be changed. -p PORT The piscsi server port, default is 6868. -r RESERVED_IDS - Comma-separated list of IDs to reserve. Pass an empty list in order to not reserve anything. -p TYPE The optional - case-insensitive device type (SAHD, SCHD, SCRM, SCCD, SCMO, SCBR, SCDP, SCLP, SCHS). If no type is specified for de‐ - vices that support an image file, piscsi tries to derive the type from the file extension. + Comma-separated list of IDs to reserve. Pass an empty list in + order to not reserve anything. -p TYPE The optional case-insen‐ + sitive device type (SAHD, SCHD, SCRM, SCCD, SCMO, SCBR, SCDP, + SCLP, SCHS). If no type is specified for devices that support an + image file, piscsi tries to derive the type from the file exten‐ + sion. -v Display the piscsi version. -z LOCALE - Overrides the default locale for client-faces error messages. The client can override the locale. + Overrides the default locale for client-faces error messages. + The client can override the locale. -IDn[:u] FILE - n is the SCSI ID number (0-7). u (0-31) is the optional LUN (logical unit). The default LUN is 0. + n is the SCSI ID number (0-7). u (0-31) is the optional LUN + (logical unit). The default LUN is 0. - FILE is the name of the image file to use for the SCSI device. For devices that do not support an image file (SCBR, - SCDP, SCLP, SCHS) the filename may have a special meaning or a dummy name can be provided. For SCBR and SCDP it is - an optioinal prioritized list of network interfaces, an optional IP address and netmask, e.g. "inter‐ - face=eth0,eth1,wlan0:inet=10.10.20.1/24". For SCLP it is the print command to be used and a reservation timeout in - seconds, e.g. "cmd=lp -oraw %f:timeout=60". + FILE is the name of the image file to use for the SCSI device. + For devices that do not support an image file (SCBR, SCDP, SCLP, + SCHS) the filename may have a special meaning or a dummy name + can be provided. For SCBR and SCDP it is an optioinal priori‐ + tized list of network interfaces, an optional IP address and + netmask, e.g. "interface=eth0,eth1,wlan0:inet=10.10.20.1/24". + For SCLP it is the print command to be used and a reservation + timeout in seconds, e.g. "cmd=lp -oraw %f:timeout=60". FILE is the name of the image file to use for the SCSI device. @@ -98,17 +127,20 @@ EXAMPLES Launch PiSCSI with no emulated drives attached: piscsi - Launch PiSCSI with an Apple hard drive image as ID 0 and a CD-ROM as ID 2 + Launch PiSCSI with an Apple hard drive image as ID 0 and a CD-ROM as ID + 2 piscsi -ID0 /path/to/harddrive.hda -ID2 /path/to/cdimage.iso - Launch PiSCSI with a removable SCSI drive image as ID 0 and the raw device file /dev/hdb (e.g. a USB stick) and a DaynaPort - network adapter as ID 6: + Launch PiSCSI with a removable SCSI drive image as ID 0 and the raw de‐ + vice file /dev/hdb (e.g. a USB stick) and a DaynaPort network adapter + as ID 6: piscsi -ID0 -t scrm /dev/hdb -ID6 -t scdp daynaport To create an empty, 100MiB HD image, use the following command: dd if=/dev/zero of=/path/to/newimage.hda bs=512 count=204800 - In case the fallocate command is available a much faster alternative to the dd command is: + In case the fallocate command is available a much faster alternative to + the dd command is: fallocate -l 104857600 /path/to/newimage.hda SEE ALSO @@ -116,4 +148,4 @@ SEE ALSO Full documentation is available at: - piscsi(1) + piscsi(1) diff --git a/doc/scsictl.1 b/doc/scsictl.1 index 98a12f05..188c6930 100644 --- a/doc/scsictl.1 +++ b/doc/scsictl.1 @@ -60,7 +60,7 @@ Set the default image folder. Gets the list of reserved device IDs. .TP .BR \-L\fI " "\fILOG_LEVEL -Set the piscsi log level (trace, debug, info, warn, err, off). +Set the piscsi log level (trace, debug, info, warning, error, off). .TP .BR \-h\fI " " \fIHOST The piscsi host to connect to, default is 'localhost'. diff --git a/doc/scsictl_man_page.txt b/doc/scsictl_man_page.txt index 9281242c..1b5108fd 100644 --- a/doc/scsictl_man_page.txt +++ b/doc/scsictl_man_page.txt @@ -1,31 +1,32 @@ !! ------ THIS FILE IS AUTO_GENERATED! DO NOT MANUALLY UPDATE!!! -!! ------ The native file is scsictl.1. Re-run 'make docs' after updating - - -piscsi(1) General Commands Manual piscsi(1) +!! ------ The native file is scsictl.1. Re-run 'make docs' after updating\n\n +piscsi(1) General Commands Manual piscsi(1) NAME scsictl - Sends management commands to the piscsi process SYNOPSIS - scsictl -e | -l | -m | -o | -s | -v | -D | -I | -L | -O | -P | -T | -V | -X | [-C FILENAME:FILESIZE] [-E FILENAME] [-F IM‐ - AGE_FOLDER] [-R CURRENT_NAME:NEW_NAME] [-c CMD] [-f FILE|PARAM] [-g LOG_LEVEL] [-h HOST] [-i ID[:LUN] [-n NAME] [-p PORT] - [-r RESERVED_IDS] [-t TYPE] [-x CURRENT_NAME:NEW_NAME] [-z LOCALE] + scsictl -e | -l | -m | -o | -s | -v | -D | -I | -L | -O | -P | -T | -V + | -X | [-C FILENAME:FILESIZE] [-E FILENAME] [-F IMAGE_FOLDER] [-R CUR‐ + RENT_NAME:NEW_NAME] [-c CMD] [-f FILE|PARAM] [-g LOG_LEVEL] [-h HOST] + [-i ID[:LUN] [-n NAME] [-p PORT] [-r RESERVED_IDS] [-t TYPE] [-x CUR‐ + RENT_NAME:NEW_NAME] [-z LOCALE] DESCRIPTION - scsictl sends commands to the piscsi process to make configuration adjustments at runtime or to check the status of the de‐ - vices. + scsictl sends commands to the piscsi process to make configuration ad‐ + justments at runtime or to check the status of the devices. Either the -i or -l option should be specified at one time. Not both. You do NOT need root privileges to use scsictl. - Note: The command and type arguments are case insensitive. Only the first letter of the command/type is evaluated by the - tool. + Note: The command and type arguments are case insensitive. Only the + first letter of the command/type is evaluated by the tool. OPTIONS -C FILENAME:FILESIZE - Create an image file in the default image folder with the specified name and size in bytes. + Create an image file in the default image folder with the speci‐ + fied name and size in bytes. -D Detach all devices. @@ -38,22 +39,28 @@ OPTIONS -I Gets the list of reserved device IDs. -L LOG_LEVEL - Set the piscsi log level (trace, debug, info, warn, err, off). + Set the piscsi log level (trace, debug, info, warning, error, + off). -h HOST The piscsi host to connect to, default is 'localhost'. -e List all images files in the default image folder. - -N Lists all available network interfaces provided that they are up. + -N Lists all available network interfaces provided that they are + up. - -O Display the available piscsi server log levels and the current log level. + -O Display the available piscsi server log levels and the current + log level. - -P Prompt for the access token in case piscsi requires authentication. + -P Prompt for the access token in case piscsi requires authentica‐ + tion. - -l List all of the devices that are currently being emulated by PiSCSI, as well as their current status. + -l List all of the devices that are currently being emulated by + PiSCSI, as well as their current status. - -m List all file extensions recognized by PiSCSI and the device types they map to. + -m List all file extensions recognized by PiSCSI and the device + types they map to. -o Display operation meta data information. @@ -64,9 +71,11 @@ OPTIONS The piscsi port to connect to, default is 6868. -r RESERVED_IDS - Comma-separated list of IDs to reserve. Pass an empty list in order to not reserve anything. + Comma-separated list of IDs to reserve. Pass an empty list in + order to not reserve anything. - -s Display server-side settings like available images or supported device types. + -s Display server-side settings like available images or supported + device types. -T Display all device types and their properties. @@ -86,30 +95,36 @@ OPTIONS Overrides the default locale for client-facing error messages. -i ID[:LUN] - The SCSI ID and optional LUN that you want to control. (0-7:0-31) + The SCSI ID and optional LUN that you want to control. + (0-7:0-31) -c CMD Command is the operation being requested. Options are: a(ttach): Attach disk d(etach): Detach disk i(nsert): Insert media (removable media devices only) e(ject): Eject media (removable media devices only) - p(rotect): Write protect the medium (not for CD-ROMs, which are always read-only) - u(nprotect): Remove write protection from the medium (not for CD-ROMs, which are always read-only) + p(rotect): Write protect the medium (not for CD-ROMs, which + are always read-only) + u(nprotect): Remove write protection from the medium (not for + CD-ROMs, which are always read-only) s(how): Display device information eject, protect and unprotect are idempotent. -b BLOCK_SIZE - The optional block size, either 512, 1024, 2048 or 4096 bytes. The default size is 512 bytes. + The optional block size, either 512, 1024, 2048 or 4096 bytes. + The default size is 512 bytes. -f FILE|PARAM - Device-specific: Either a path to a disk image file, or a parameter for a non-disk device. See the piscsi(1) man - page for permitted file types. + Device-specific: Either a path to a disk image file, or a param‐ + eter for a non-disk device. See the piscsi(1) man page for per‐ + mitted file types. -t TYPE - Specifies the device type. This type overrides the type derived from the file extension of the specified image. See - the piscsi(1) man page for the available device types. For some types there are shortcuts (only the first letter is - required): + Specifies the device type. This type overrides the type derived + from the file extension of the specified image. See the + piscsi(1) man page for the available device types. For some + types there are shortcuts (only the first letter is required): hd: SCSI hard disk drive rm: SCSI removable media drive cd: CD-ROM @@ -120,13 +135,17 @@ OPTIONS services: Host services device -n VENDOR:PRODUCT:REVISION - The vendor, product and revision for the device, to be returned with the INQUIRY data. A complete set of name compo‐ - nents must be provided. VENDOR may have up to 8, PRODUCT up to 16, REVISION up to 4 characters. Padding with blanks - to the maxium length is automatically applied. Once set the name of a device cannot be changed. + The vendor, product and revision for the device, to be returned + with the INQUIRY data. A complete set of name components must be + provided. VENDOR may have up to 8, PRODUCT up to 16, REVISION up + to 4 characters. Padding with blanks to the maxium length is au‐ + tomatically applied. Once set the name of a device cannot be + changed. -u UNIT - Unit number (0-31). This will default to 0. This option is only used when there are multiple SCSI devices on a - shared SCSI controller. (This is not common) + Unit number (0-31). This will default to 0. This option is only + used when there are multiple SCSI devices on a shared SCSI con‐ + troller. (This is not common) EXAMPLES Show a listing of all of the SCSI devices and their current status. @@ -139,8 +158,8 @@ EXAMPLES | 0 | 1 | SCHD | /home/pi/harddisk.hda +----+-----+------+------------------------------------- - Request the PiSCSI process to attach a disk (assumed) to SCSI ID 0 with the contents of the file system image "HDIIM‐ - AGE0.HDS". + Request the PiSCSI process to attach a disk (assumed) to SCSI ID 0 with + the contents of the file system image "HDIIMAGE0.HDS". scsictl -i 0 -f HDIIMAGE0.HDS SEE ALSO @@ -148,4 +167,4 @@ SEE ALSO Full documentation is available at: - piscsi(1) + piscsi(1) diff --git a/docker/README.md b/docker/README.md index afb007a3..736ac6ef 100644 --- a/docker/README.md +++ b/docker/README.md @@ -36,7 +36,7 @@ The following environment variables are available when using Docker Compose: | Environment Variable | Default | | -------------------- |----------| -| `OS_VERSION` | buster | +| `OS_VERSION` | bullseye | | `WEB_HTTP_PORT` | 8080 | | `WEB_HTTPS_PORT` | 8443 | | `WEB_LOG_LEVEL` | info | diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 4ae30d27..e3ea35a2 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -21,13 +21,13 @@ services: web: container_name: piscsi_web - image: piscsi-web:${OS_VERSION:-buster} + image: piscsi-web:${OS_VERSION:-bullseye} pull_policy: never build: context: .. dockerfile: docker/web/Dockerfile args: - - OS_VERSION=${OS_VERSION:-buster} + - OS_VERSION=${OS_VERSION:-bullseye} volumes: - ./volumes/images:/home/pi/images:delegated - ./volumes/config:/home/pi/.config/piscsi:delegated diff --git a/docker/web/Dockerfile b/docker/web/Dockerfile index 4d235eca..1d74a423 100644 --- a/docker/web/Dockerfile +++ b/docker/web/Dockerfile @@ -1,5 +1,5 @@ ARG DEBIAN_FRONTEND=noninteractive -ARG OS_VERSION=buster +ARG OS_VERSION=bullseye FROM "debian:${OS_VERSION}-slim" diff --git a/easyinstall.sh b/easyinstall.sh index 28da5f8a..7e1754fe 100755 --- a/easyinstall.sh +++ b/easyinstall.sh @@ -47,14 +47,13 @@ echo -e $logo } CONNECT_TYPE="FULLSPEC" -# clang v11 is the latest distributed by Buster -COMPILER="clang++-11" +COMPILER="clang++" MEM=$(grep MemTotal /proc/meminfo | awk '{print $2}') CORES=`expr $MEM / 450000` if [ $CORES -gt $(nproc) ]; then - CORES=$(nproc) + CORES=$(nproc) elif [ $CORES -lt 1 ]; then - CORES=1 + CORES=1 fi USER=$(whoami) BASE=$(dirname "$(readlink -f "${0}")") @@ -78,7 +77,7 @@ FILE_SHARE_PATH="$HOME/shared_files" FILE_SHARE_NAME="Pi File Server" APT_PACKAGES_COMMON="build-essential git protobuf-compiler bridge-utils" -APT_PACKAGES_BACKEND="libspdlog-dev libpcap-dev libprotobuf-dev protobuf-compiler libgmock-dev clang-11" +APT_PACKAGES_BACKEND="libspdlog-dev libpcap-dev libprotobuf-dev protobuf-compiler libgmock-dev clang" APT_PACKAGES_PYTHON="python3 python3-dev python3-pip python3-venv python3-setuptools python3-wheel libev-dev libevdev2" APT_PACKAGES_WEB="nginx-light genisoimage man2html hfsutils dosfstools kpartx unzip unar disktype gettext" From aa927cb5042fc81ffe6013ae4b9220926745563f Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Mon, 16 Oct 2023 18:27:18 +0200 Subject: [PATCH 31/87] Move top-level .cpp files into their respective folders (#1249) * Update Makefile, move top-level .cpp files * Move top-level .cpp files into their respective folders --- cpp/Makefile | 33 ++++++++++----------- cpp/{ => piscsi}/piscsi.cpp | 0 cpp/{ => scsictl}/scsictl.cpp | 0 cpp/{ => scsidump}/scsidump.cpp | 0 cpp/{ => scsiloop}/scsiloop.cpp | 0 cpp/{ => scsimon}/scsimon.cpp | 2 +- cpp/{monitor => scsimon}/sm_core.cpp | 4 +-- cpp/{monitor => scsimon}/sm_core.h | 0 cpp/{monitor => scsimon}/sm_html_report.cpp | 0 cpp/{monitor => scsimon}/sm_json_report.cpp | 0 cpp/{monitor => scsimon}/sm_reports.h | 0 cpp/{monitor => scsimon}/sm_vcd_report.cpp | 0 12 files changed, 18 insertions(+), 21 deletions(-) rename cpp/{ => piscsi}/piscsi.cpp (100%) rename cpp/{ => scsictl}/scsictl.cpp (100%) rename cpp/{ => scsidump}/scsidump.cpp (100%) rename cpp/{ => scsiloop}/scsiloop.cpp (100%) rename cpp/{ => scsimon}/scsimon.cpp (93%) rename cpp/{monitor => scsimon}/sm_core.cpp (99%) rename cpp/{monitor => scsimon}/sm_core.h (100%) rename cpp/{monitor => scsimon}/sm_html_report.cpp (100%) rename cpp/{monitor => scsimon}/sm_json_report.cpp (100%) rename cpp/{monitor => scsimon}/sm_reports.h (100%) rename cpp/{monitor => scsimon}/sm_vcd_report.cpp (100%) diff --git a/cpp/Makefile b/cpp/Makefile index 9f6f2e06..425b6f9e 100644 --- a/cpp/Makefile +++ b/cpp/Makefile @@ -82,39 +82,36 @@ SRC_SHARED = \ shared/piscsi_util.cpp \ shared/network_util.cpp -SRC_PISCSI_CORE = $(shell find ./piscsi -name '*.cpp') +SRC_PISCSI_CORE = $(shell find ./piscsi -name '*.cpp' | grep -v piscsi.cpp) SRC_PISCSI_CORE += $(shell find ./controllers -name '*.cpp') SRC_PISCSI_CORE += $(shell find ./devices -name '*.cpp') SRC_PISCSI_CORE += $(shell find ./hal -name '*.cpp') -SRC_PISCSI = piscsi.cpp +SRC_PISCSI = piscsi/piscsi.cpp -SRC_SCSIMON = scsimon.cpp -SRC_SCSIMON += $(shell find ./monitor -name '*.cpp') +SRC_SCSIMON = scsimon/scsimon.cpp +SRC_SCSIMON += $(shell find ./scsimon -name '*.cpp' | grep -v scsimon.cpp) SRC_SCSIMON += $(shell find ./hal -name '*.cpp') -SRC_SCSICTL_CORE = $(shell find ./scsictl -name '*.cpp') +SRC_SCSICTL_CORE = $(shell find ./scsictl -name '*.cpp' | grep -v scsictl.cpp) -SRC_SCSICTL = scsictl.cpp +SRC_SCSICTL = scsictl/scsictl.cpp -SRC_SCSIDUMP = scsidump.cpp -SRC_SCSIDUMP += $(shell find ./scsidump -name '*.cpp') +SRC_SCSIDUMP = scsidump/scsidump.cpp +SRC_SCSIDUMP += $(shell find ./scsidump -name '*.cpp' | grep -v scsidump.cpp) SRC_SCSIDUMP += $(shell find ./hal -name '*.cpp') SRC_PISCSI_TEST = $(shell find ./test -name '*.cpp') -SRC_PISCSI_TEST += $(shell find ./scsidump -name '*.cpp') -SRC_PISCSI_TEST += $(shell find ./monitor -name '*.cpp') +SRC_PISCSI_TEST += $(shell find ./scsidump -name '*.cpp' | grep -v scsidump.cpp) -SRC_SCSILOOP = scsiloop.cpp -SRC_SCSILOOP += $(shell find ./scsiloop -name '*.cpp') +SRC_SCSILOOP = scsiloop/scsiloop.cpp +SRC_SCSILOOP += $(shell find ./scsiloop -name '*.cpp' | grep -v scsiloop.cpp) SRC_SCSILOOP += $(shell find ./hal -name '*.cpp') -vpath %.h ./ ./shared ./controllers ./devices ./monitor ./hal \ - ./hal/boards ./hal/pi_defs ./piscsi ./scsictl ./scsidump \ - ./scsiloop -vpath %.cpp ./ ./shared ./controllers ./devices ./monitor ./hal \ - ./hal/boards ./hal/pi_defs ./piscsi ./scsictl ./scsidump \ - ./scsiloop ./test +vpath %.h ./shared ./controllers ./devices ./scsimon ./hal \ + ./hal/pi_defs ./piscsi ./scsictl ./scsidump ./scsiloop +vpath %.cpp ./shared ./controllers ./devices ./scsimon ./hal \ + ./hal/pi_defs ./piscsi ./scsictl ./scsidump ./scsiloop ./test vpath %.o ./$(OBJDIR) vpath ./$(BINDIR) diff --git a/cpp/piscsi.cpp b/cpp/piscsi/piscsi.cpp similarity index 100% rename from cpp/piscsi.cpp rename to cpp/piscsi/piscsi.cpp diff --git a/cpp/scsictl.cpp b/cpp/scsictl/scsictl.cpp similarity index 100% rename from cpp/scsictl.cpp rename to cpp/scsictl/scsictl.cpp diff --git a/cpp/scsidump.cpp b/cpp/scsidump/scsidump.cpp similarity index 100% rename from cpp/scsidump.cpp rename to cpp/scsidump/scsidump.cpp diff --git a/cpp/scsiloop.cpp b/cpp/scsiloop/scsiloop.cpp similarity index 100% rename from cpp/scsiloop.cpp rename to cpp/scsiloop/scsiloop.cpp diff --git a/cpp/scsimon.cpp b/cpp/scsimon/scsimon.cpp similarity index 93% rename from cpp/scsimon.cpp rename to cpp/scsimon/scsimon.cpp index b0b00383..9cc84565 100644 --- a/cpp/scsimon.cpp +++ b/cpp/scsimon/scsimon.cpp @@ -7,7 +7,7 @@ // //--------------------------------------------------------------------------- -#include "monitor/sm_core.h" +#include "scsimon/sm_core.h" using namespace std; diff --git a/cpp/monitor/sm_core.cpp b/cpp/scsimon/sm_core.cpp similarity index 99% rename from cpp/monitor/sm_core.cpp rename to cpp/scsimon/sm_core.cpp index 9805bab4..d90a5798 100644 --- a/cpp/monitor/sm_core.cpp +++ b/cpp/scsimon/sm_core.cpp @@ -9,10 +9,10 @@ // //--------------------------------------------------------------------------- -#include "monitor/sm_core.h" +#include "scsimon/sm_core.h" #include "hal/gpiobus.h" #include "hal/gpiobus_factory.h" -#include "monitor/sm_reports.h" +#include "scsimon/sm_reports.h" #include "hal/log.h" #include "shared/piscsi_version.h" #include "shared/piscsi_util.h" diff --git a/cpp/monitor/sm_core.h b/cpp/scsimon/sm_core.h similarity index 100% rename from cpp/monitor/sm_core.h rename to cpp/scsimon/sm_core.h diff --git a/cpp/monitor/sm_html_report.cpp b/cpp/scsimon/sm_html_report.cpp similarity index 100% rename from cpp/monitor/sm_html_report.cpp rename to cpp/scsimon/sm_html_report.cpp diff --git a/cpp/monitor/sm_json_report.cpp b/cpp/scsimon/sm_json_report.cpp similarity index 100% rename from cpp/monitor/sm_json_report.cpp rename to cpp/scsimon/sm_json_report.cpp diff --git a/cpp/monitor/sm_reports.h b/cpp/scsimon/sm_reports.h similarity index 100% rename from cpp/monitor/sm_reports.h rename to cpp/scsimon/sm_reports.h diff --git a/cpp/monitor/sm_vcd_report.cpp b/cpp/scsimon/sm_vcd_report.cpp similarity index 100% rename from cpp/monitor/sm_vcd_report.cpp rename to cpp/scsimon/sm_vcd_report.cpp From e276166632c6cab584a5182b6728b7ca5ec4019f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 22 Oct 2023 19:00:25 +0900 Subject: [PATCH 32/87] Bump pillow from 9.3.0 to 10.0.1 in /python/oled (#1235) * Bump pillow from 9.3.0 to 10.0.1 in /python/oled Bumps [pillow](https://github.com/python-pillow/Pillow) from 9.3.0 to 10.0.1. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/9.3.0...10.0.1) --- updated-dependencies: - dependency-name: pillow dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- python/oled/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/oled/requirements.txt b/python/oled/requirements.txt index 3f97cda2..8ea5d44c 100644 --- a/python/oled/requirements.txt +++ b/python/oled/requirements.txt @@ -1,4 +1,4 @@ adafruit-circuitpython-ssd1306==2.12.11 -Pillow==9.3.0 +Pillow==10.0.1 protobuf==3.20.2 unidecode==1.3.6 From 8ae5455675b1ab195e3de5ea9b52c979fcb2c339 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 22 Oct 2023 19:01:28 +0900 Subject: [PATCH 33/87] Bump pillow from 9.3.0 to 10.0.1 in /python/ctrlboard (#1236) * Bump pillow from 9.3.0 to 10.0.1 in /python/ctrlboard Bumps [pillow](https://github.com/python-pillow/Pillow) from 9.3.0 to 10.0.1. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/9.3.0...10.0.1) --- updated-dependencies: - dependency-name: pillow dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- python/ctrlboard/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ctrlboard/requirements.txt b/python/ctrlboard/requirements.txt index 4eb9c68e..be85c311 100644 --- a/python/ctrlboard/requirements.txt +++ b/python/ctrlboard/requirements.txt @@ -2,7 +2,7 @@ #adafruit-circuitpython-framebuf==1.4.8 #adafruit-circuitpython-ssd1306==2.12.3 luma-oled==3.8.1 -Pillow==9.3.0 +Pillow==10.0.1 RPi.GPIO==0.7.0 protobuf==3.19.5 unidecode==1.3.2 From 2ec44332d04360e35464d80b0fa9a04ac2a14e2a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 22 Oct 2023 19:06:48 +0900 Subject: [PATCH 34/87] Bump postcss from 8.4.18 to 8.4.31 in /python/web (#1241) * Bump postcss from 8.4.18 to 8.4.31 in /python/web Bumps [postcss](https://github.com/postcss/postcss) from 8.4.18 to 8.4.31. - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.4.18...8.4.31) --- updated-dependencies: - dependency-name: postcss dependency-type: indirect ... Signed-off-by: dependabot[bot] --- python/web/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/web/package-lock.json b/python/web/package-lock.json index 06133d7f..b2460de6 100644 --- a/python/web/package-lock.json +++ b/python/web/package-lock.json @@ -1281,9 +1281,9 @@ } }, "node_modules/postcss": { - "version": "8.4.26", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.26.tgz", - "integrity": "sha512-jrXHFF8iTloAenySjM/ob3gSj7pCu0Ji49hnjqzsgSRa50hkWCKD0HQ+gMNJkW38jBI68MpAAg7ZWwHwX8NMMw==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "dev": true, "funding": [ { From 02d18b33592ec522c65936b35dc57848c61fa9ba Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Sun, 22 Oct 2023 16:18:33 +0200 Subject: [PATCH 35/87] Do not write data when executing VERIFY10/VERIFY16 (#1250) --- cpp/controllers/scsi_controller.cpp | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/cpp/controllers/scsi_controller.cpp b/cpp/controllers/scsi_controller.cpp index e6acca41..9d90d267 100644 --- a/cpp/controllers/scsi_controller.cpp +++ b/cpp/controllers/scsi_controller.cpp @@ -813,9 +813,6 @@ bool ScsiController::XferOutBlockOriented(bool cont) case scsi_command::eCmdWrite6: case scsi_command::eCmdWrite10: case scsi_command::eCmdWrite16: - // TODO Verify has to verify, not to write, see https://github.com/PiSCSI/piscsi/issues/807 - case scsi_command::eCmdVerify10: - case scsi_command::eCmdVerify16: { // TODO Get rid of this special case for SCBR if (auto bridge = dynamic_pointer_cast(device); bridge) { @@ -853,12 +850,29 @@ bool ScsiController::XferOutBlockOriented(bool cont) // If you do not need the next block, end here IncrementNext(); - if (!cont) { - break; + if (cont) { + SetLength(disk->GetSectorSizeInBytes()); + ResetOffset(); + } + + break; + } + + case scsi_command::eCmdVerify10: + case scsi_command::eCmdVerify16: + { + auto disk = dynamic_pointer_cast(device); + if (disk == nullptr) { + return false; + } + + // If you do not need the next block, end here + IncrementNext(); + if (cont) { + SetLength(disk->GetSectorSizeInBytes()); + ResetOffset(); } - SetLength(disk->GetSectorSizeInBytes()); - ResetOffset(); break; } From 2acb7420439c6d78895cff38910253caf724c7dc Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Sun, 22 Oct 2023 16:19:40 +0200 Subject: [PATCH 36/87] Fix output formatting (#1254) --- cpp/shared/protobuf_util.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/shared/protobuf_util.cpp b/cpp/shared/protobuf_util.cpp index 1cdff94b..012a5011 100644 --- a/cpp/shared/protobuf_util.cpp +++ b/cpp/shared/protobuf_util.cpp @@ -14,7 +14,7 @@ #include #include #include - +#include using namespace std; using namespace piscsi_util; @@ -132,7 +132,7 @@ string protobuf_util::ListDevices(const vector& pb_devices) break; } - s << "| " << device.id() << " | " << device.unit() << " | " << PbDeviceType_Name(device.type()) << " | " + s << "| " << device.id() << " | " << setw(3) << device.unit() << " | " << PbDeviceType_Name(device.type()) << " | " << (filename.empty() ? "NO MEDIUM" : filename) << (!device.status().removed() && (device.properties().read_only() || device.status().protected_()) ? " (READ-ONLY)" : "") << '\n'; From 43088ab3bcfef07ea891437755a82a1e41c7a0e4 Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Sun, 22 Oct 2023 17:29:26 +0200 Subject: [PATCH 37/87] Remove non-working Banana Pi code (#1252) * Remove non-working Banana Pi code * Remove SonarQube suppressions which can be resolved by a code update * Update device detection --- .github/workflows/build_code.yml | 10 - cpp/hal/connection_type/connection_aibom.h | 87 -- cpp/hal/connection_type/connection_fullspec.h | 31 - .../connection_type/connection_gamernium.h | 87 -- cpp/hal/connection_type/connection_standard.h | 31 - cpp/hal/data_sample_bananam2p.cpp | 64 - cpp/hal/data_sample_bananam2p.h | 94 -- cpp/hal/data_sample_raspberry.h | 6 +- cpp/hal/gpiobus.cpp | 17 +- cpp/hal/gpiobus.h | 20 +- cpp/hal/gpiobus_bananam2p.cpp | 1055 ----------------- cpp/hal/gpiobus_bananam2p.h | 203 ---- cpp/hal/gpiobus_factory.cpp | 45 +- cpp/hal/gpiobus_raspberry.cpp | 6 +- cpp/hal/gpiobus_raspberry.h | 4 +- cpp/hal/gpiobus_virtual.cpp | 2 - cpp/hal/gpiobus_virtual.h | 1 - cpp/hal/pi_defs/bpi-gpio.h | 356 ------ cpp/hal/pi_defs/bpi-m2p.h | 71 -- cpp/hal/sbc_version.cpp | 127 +- cpp/hal/sbc_version.h | 39 +- cpp/hal/sunxi_utils.cpp | 59 - cpp/hal/sunxi_utils.h | 189 --- cpp/hal/systimer.cpp | 19 +- cpp/hal/systimer_allwinner.cpp | 155 --- cpp/hal/systimer_allwinner.h | 106 -- cpp/hal/systimer_raspberry.cpp | 12 +- cpp/scsidump/scsidump_core.cpp | 5 +- cpp/scsiloop/scsiloop_gpio.cpp | 92 +- 29 files changed, 103 insertions(+), 2890 deletions(-) delete mode 100644 cpp/hal/connection_type/connection_aibom.h delete mode 100644 cpp/hal/connection_type/connection_gamernium.h delete mode 100644 cpp/hal/data_sample_bananam2p.cpp delete mode 100644 cpp/hal/data_sample_bananam2p.h delete mode 100644 cpp/hal/gpiobus_bananam2p.cpp delete mode 100644 cpp/hal/gpiobus_bananam2p.h delete mode 100644 cpp/hal/pi_defs/bpi-gpio.h delete mode 100755 cpp/hal/pi_defs/bpi-m2p.h delete mode 100644 cpp/hal/sunxi_utils.cpp delete mode 100644 cpp/hal/sunxi_utils.h delete mode 100644 cpp/hal/systimer_allwinner.cpp delete mode 100644 cpp/hal/systimer_allwinner.h diff --git a/.github/workflows/build_code.yml b/.github/workflows/build_code.yml index e0c38852..a17b01ba 100644 --- a/.github/workflows/build_code.yml +++ b/.github/workflows/build_code.yml @@ -32,16 +32,6 @@ jobs: with: connect-type: "STANDARD" - aibom: - uses: PiSCSI/piscsi/.github/workflows/arm_cross_compile.yml@develop - with: - connect-type: "AIBOM" - - gamernium: - uses: PiSCSI/piscsi/.github/workflows/arm_cross_compile.yml@develop - with: - connect-type: "GAMERNIUM" - # The fullspec connection board is the most common debug-fullspec: uses: PiSCSI/piscsi/.github/workflows/arm_cross_compile.yml@develop diff --git a/cpp/hal/connection_type/connection_aibom.h b/cpp/hal/connection_type/connection_aibom.h deleted file mode 100644 index 0dd3c314..00000000 --- a/cpp/hal/connection_type/connection_aibom.h +++ /dev/null @@ -1,87 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Powered by XM6 TypeG Technology. -// Copyright (C) 2016-2020 GIMONS -// -//--------------------------------------------------------------------------- - -#pragma once - -#include "hal/pi_defs/bpi-m2p.h" -#include - -// -// RaSCSI Adapter Aibom version -// - -const std::string CONNECT_DESC = "AIBOM PRODUCTS version"; // Startup message - -// Select signal control mode -const static int SIGNAL_CONTROL_MODE = 2; // SCSI positive logic specification - -// Control signal output logic -#define ACT_ON ON // ACTIVE SIGNAL ON -#define ENB_ON ON // ENABLE SIGNAL ON -#define IND_IN OFF // INITIATOR SIGNAL INPUT -#define TAD_IN OFF // TARGET SIGNAL INPUT -#define DTD_IN OFF // DATA SIGNAL INPUT - -// Control signal pin assignment (-1 means no control) -const static int PIN_ACT = 4; // ACTIVE -const static int PIN_ENB = 17; // ENABLE -const static int PIN_IND = 27; // INITIATOR CTRL DIRECTION -const static int PIN_TAD = -1; // TARGET CTRL DIRECTION -const static int PIN_DTD = 18; // DATA DIRECTION - -// SCSI signal pin assignment -const static int PIN_DT0 = 6; // Data 0 -const static int PIN_DT1 = 12; // Data 1 -const static int PIN_DT2 = 13; // Data 2 -const static int PIN_DT3 = 16; // Data 3 -const static int PIN_DT4 = 19; // Data 4 -const static int PIN_DT5 = 20; // Data 5 -const static int PIN_DT6 = 26; // Data 6 -const static int PIN_DT7 = 21; // Data 7 -const static int PIN_DP = 5; // Data parity -const static int PIN_ATN = 22; // ATN -const static int PIN_RST = 25; // RST -const static int PIN_ACK = 10; // ACK -const static int PIN_REQ = 7; // REQ -const static int PIN_MSG = 9; // MSG -const static int PIN_CD = 11; // CD -const static int PIN_IO = 23; // IO -const static int PIN_BSY = 24; // BSY -const static int PIN_SEL = 8; // SEL - -// Warning: The Allwinner/Banana Pi GPIO numbers DO NOT correspond to -// the Raspberry Pi GPIO numbers. -// For example, Pin 7 is GPIO4 on a Raspberry Pi. Its GPIO 6 on a Banana Pi -// For Banana Pi, the pins are specified by physical pin number, NOT GPIO number -// (The Macro's convert the pin number to logical BPi GPIO number) -const static int BPI_PIN_ACT = BPI_M2P_07; // ACTIVE -const static int BPI_PIN_ENB = BPI_M2P_11; // ENABLE -const static int BPI_PIN_IND = BPI_M2P_13; // INITIATOR CTRL DIRECTION -const static int BPI_PIN_TAD = -1; // TARGET CTRL DIRECTION -const static int BPI_PIN_DTD = BPI_M2P_12; // DATA DIRECTION - -const static int BPI_PIN_DT0 = BPI_M2P_31; // Data 0 -const static int BPI_PIN_DT1 = BPI_M2P_32; // Data 1 -const static int BPI_PIN_DT2 = BPI_M2P_33; // Data 2 -const static int BPI_PIN_DT3 = BPI_M2P_36; // Data 3 -const static int BPI_PIN_DT4 = BPI_M2P_35; // Data 4 -const static int BPI_PIN_DT5 = BPI_M2P_38; // Data 5 -const static int BPI_PIN_DT6 = BPI_M2P_37; // Data 6 -const static int BPI_PIN_DT7 = BPI_M2P_40; // Data 7 -const static int BPI_PIN_DP = BPI_M2P_29; // Data parity -const static int BPI_PIN_ATN = BPI_M2P_15; // ATN -const static int BPI_PIN_RST = BPI_M2P_22; // RST -const static int BPI_PIN_ACK = BPI_M2P_19; // ACK -const static int BPI_PIN_REQ = BPI_M2P_26; // REQ -const static int BPI_PIN_MSG = BPI_M2P_21; // MSG -const static int BPI_PIN_CD = BPI_M2P_23; // CD -const static int BPI_PIN_IO = BPI_M2P_16; // IO -const static int BPI_PIN_BSY = BPI_M2P_18; // BSY -const static int BPI_PIN_SEL = BPI_M2P_24; // SEL diff --git a/cpp/hal/connection_type/connection_fullspec.h b/cpp/hal/connection_type/connection_fullspec.h index b5e706f2..9a21c3c7 100644 --- a/cpp/hal/connection_type/connection_fullspec.h +++ b/cpp/hal/connection_type/connection_fullspec.h @@ -10,7 +10,6 @@ #pragma once -#include "hal/pi_defs/bpi-m2p.h" #include // @@ -55,33 +54,3 @@ const static int PIN_CD = 24; // CD const static int PIN_IO = 25; // IO const static int PIN_BSY = 26; // BSY const static int PIN_SEL = 27; // SEL - -// Warning: The Allwinner/Banana Pi GPIO numbers DO NOT correspond to -// the Raspberry Pi GPIO numbers. -// For example, Pin 7 is GPIO4 on a Raspberry Pi. Its GPIO 6 on a Banana Pi -// For Banana Pi, the pins are specified by physical pin number, NOT GPIO number -// (The Macro's convert the pin number to logical BPi GPIO number) -const static int BPI_PIN_ACT = BPI_M2P_07; // ACTIVE -const static int BPI_PIN_ENB = BPI_M2P_29; // ENABLE -const static int BPI_PIN_IND = BPI_M2P_31; // INITIATOR CTRL DIRECTION -const static int BPI_PIN_TAD = BPI_M2P_26; // TARGET CTRL DIRECTION -const static int BPI_PIN_DTD = BPI_M2P_24; // DATA DIRECTION - -const static int BPI_PIN_DT0 = BPI_M2P_19; // Data 0 -const static int BPI_PIN_DT1 = BPI_M2P_23; // Data 1 -const static int BPI_PIN_DT2 = BPI_M2P_32; // Data 2 -const static int BPI_PIN_DT3 = BPI_M2P_33; // Data 3 -const static int BPI_PIN_DT4 = BPI_M2P_08; // Data 4 -const static int BPI_PIN_DT5 = BPI_M2P_10; // Data 5 -const static int BPI_PIN_DT6 = BPI_M2P_36; // Data 6 -const static int BPI_PIN_DT7 = BPI_M2P_11; // Data 7 -const static int BPI_PIN_DP = BPI_M2P_12; // Data parity -const static int BPI_PIN_ATN = BPI_M2P_35; // ATN -const static int BPI_PIN_RST = BPI_M2P_38; // RST -const static int BPI_PIN_ACK = BPI_M2P_40; // ACK -const static int BPI_PIN_REQ = BPI_M2P_15; // REQ -const static int BPI_PIN_MSG = BPI_M2P_16; // MSG -const static int BPI_PIN_CD = BPI_M2P_18; // CD -const static int BPI_PIN_IO = BPI_M2P_22; // IO -const static int BPI_PIN_BSY = BPI_M2P_37; // BSY -const static int BPI_PIN_SEL = BPI_M2P_13; // SEL diff --git a/cpp/hal/connection_type/connection_gamernium.h b/cpp/hal/connection_type/connection_gamernium.h deleted file mode 100644 index eeeb4a5d..00000000 --- a/cpp/hal/connection_type/connection_gamernium.h +++ /dev/null @@ -1,87 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Powered by XM6 TypeG Technology. -// Copyright (C) 2016-2020 GIMONS -// -//--------------------------------------------------------------------------- - -#pragma once - -#include "hal/pi_defs/bpi-m2p.h" -#include - -// -// RaSCSI Adapter GAMERnium.com version -// - -const std::string CONNECT_DESC = "GAMERnium.com version"; // Startup message - -// Select signal control mode -const static int SIGNAL_CONTROL_MODE = 0; // SCSI logical specification - -// Control signal output logic -#define ACT_ON ON // ACTIVE SIGNAL ON -#define ENB_ON ON // ENABLE SIGNAL ON -#define IND_IN OFF // INITIATOR SIGNAL INPUT -#define TAD_IN OFF // TARGET SIGNAL INPUT -#define DTD_IN ON // DATA SIGNAL INPUT - -// Control signal pin assignment (-1 means no control) -const static int PIN_ACT = 14; // ACTIVE -const static int PIN_ENB = 6; // ENABLE -const static int PIN_IND = 7; // INITIATOR CTRL DIRECTION -const static int PIN_TAD = 8; // TARGET CTRL DIRECTION -const static int PIN_DTD = 5; // DATA DIRECTION - -// SCSI signal pin assignment -const static int PIN_DT0 = 21; // Data 0 -const static int PIN_DT1 = 26; // Data 1 -const static int PIN_DT2 = 20; // Data 2 -const static int PIN_DT3 = 19; // Data 3 -const static int PIN_DT4 = 16; // Data 4 -const static int PIN_DT5 = 13; // Data 5 -const static int PIN_DT6 = 12; // Data 6 -const static int PIN_DT7 = 11; // Data 7 -const static int PIN_DP = 25; // Data parity -const static int PIN_ATN = 10; // ATN -const static int PIN_RST = 22; // RST -const static int PIN_ACK = 24; // ACK -const static int PIN_REQ = 15; // REQ -const static int PIN_MSG = 17; // MSG -const static int PIN_CD = 18; // CD -const static int PIN_IO = 4; // IO -const static int PIN_BSY = 27; // BSY -const static int PIN_SEL = 23; // SEL - -// Warning: The Allwinner/Banana Pi GPIO numbers DO NOT correspond to -// the Raspberry Pi GPIO numbers. -// For example, Pin 7 is GPIO4 on a Raspberry Pi. Its GPIO 6 on a Banana Pi -// For Banana Pi, the pins are specified by physical pin number, NOT GPIO number -// (The Macro's convert the pin number to logical BPi GPIO number) -const static int BPI_PIN_ACT = BPI_M2P_08; // ACTIVE -const static int BPI_PIN_ENB = BPI_M2P_31; // ENABLE -const static int BPI_PIN_IND = BPI_M2P_04; // INITIATOR CTRL DIRECTION -const static int BPI_PIN_TAD = BPI_M2P_24; // TARGET CTRL DIRECTION -const static int BPI_PIN_DTD = BPI_M2P_29; // DATA DIRECTION - -const static int BPI_PIN_DT0 = BPI_M2P_40; // Data 0 -const static int BPI_PIN_DT1 = BPI_M2P_37; // Data 1 -const static int BPI_PIN_DT2 = BPI_M2P_38; // Data 2 -const static int BPI_PIN_DT3 = BPI_M2P_35; // Data 3 -const static int BPI_PIN_DT4 = BPI_M2P_36; // Data 4 -const static int BPI_PIN_DT5 = BPI_M2P_33; // Data 5 -const static int BPI_PIN_DT6 = BPI_M2P_32; // Data 6 -const static int BPI_PIN_DT7 = BPI_M2P_23; // Data 7 -const static int BPI_PIN_DP = BPI_M2P_22; // Data parity -const static int BPI_PIN_ATN = BPI_M2P_19; // ATN -const static int BPI_PIN_RST = BPI_M2P_15; // RST -const static int BPI_PIN_ACK = BPI_M2P_18; // ACK -const static int BPI_PIN_REQ = BPI_M2P_10; // REQ -const static int BPI_PIN_MSG = BPI_M2P_11; // MSG -const static int BPI_PIN_CD = BPI_M2P_12; // CD -const static int BPI_PIN_IO = BPI_M2P_07; // IO -const static int BPI_PIN_BSY = BPI_M2P_13; // BSY -const static int BPI_PIN_SEL = BPI_M2P_16; // SEL diff --git a/cpp/hal/connection_type/connection_standard.h b/cpp/hal/connection_type/connection_standard.h index fdde2784..7772347f 100644 --- a/cpp/hal/connection_type/connection_standard.h +++ b/cpp/hal/connection_type/connection_standard.h @@ -10,7 +10,6 @@ #pragma once -#include "hal/pi_defs/bpi-m2p.h" #include // @@ -55,33 +54,3 @@ const static int PIN_CD = 24; // CD const static int PIN_IO = 25; // IO const static int PIN_BSY = 26; // BSY const static int PIN_SEL = 27; // SEL - -// Warning: The Allwinner/Banana Pi GPIO numbers DO NOT correspond to -// the Raspberry Pi GPIO numbers. -// For example, Pin 7 is GPIO4 on a Raspberry Pi. Its GPIO 6 on a Banana Pi -// For Banana Pi, the pins are specified by physical pin number, NOT GPIO number -// (The Macro's convert the pin number to logical BPi GPIO number) -const static int BPI_PIN_ACT = BPI_M2P_07; // ACTIVE -const static int BPI_PIN_ENB = BPI_M2P_29; // ENABLE -const static int BPI_PIN_IND = -1; // INITIATOR CTRL DIRECTION -const static int BPI_PIN_TAD = -1; // TARGET CTRL DIRECTION -const static int BPI_PIN_DTD = -1; // DATA DIRECTION - -const static int BPI_PIN_DT0 = BPI_M2P_19; // Data 0 -const static int BPI_PIN_DT1 = BPI_M2P_23; // Data 1 -const static int BPI_PIN_DT2 = BPI_M2P_32; // Data 2 -const static int BPI_PIN_DT3 = BPI_M2P_33; // Data 3 -const static int BPI_PIN_DT4 = BPI_M2P_08; // Data 4 -const static int BPI_PIN_DT5 = BPI_M2P_10; // Data 5 -const static int BPI_PIN_DT6 = BPI_M2P_36; // Data 6 -const static int BPI_PIN_DT7 = BPI_M2P_11; // Data 7 -const static int BPI_PIN_DP = BPI_M2P_12; // Data parity -const static int BPI_PIN_ATN = BPI_M2P_35; // ATN -const static int BPI_PIN_RST = BPI_M2P_38; // RST -const static int BPI_PIN_ACK = BPI_M2P_40; // ACK -const static int BPI_PIN_REQ = BPI_M2P_15; // REQ -const static int BPI_PIN_MSG = BPI_M2P_16; // MSG -const static int BPI_PIN_CD = BPI_M2P_18; // CD -const static int BPI_PIN_IO = BPI_M2P_22; // IO -const static int BPI_PIN_BSY = BPI_M2P_37; // BSY -const static int BPI_PIN_SEL = BPI_M2P_13; // SEL diff --git a/cpp/hal/data_sample_bananam2p.cpp b/cpp/hal/data_sample_bananam2p.cpp deleted file mode 100644 index 8e00c15b..00000000 --- a/cpp/hal/data_sample_bananam2p.cpp +++ /dev/null @@ -1,64 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Copyright (C) 2022 akuker -// -// [ SCSI Bus Monitor ] -// -//--------------------------------------------------------------------------- - -#include "data_sample_bananam2p.h" -#include "hal/sunxi_utils.h" -#include - -uint8_t DataSample_BananaM2p::GetDAT() const -{ - uint8_t ret_val = 0; - ret_val |= GetSignal(BPI_PIN_DT0) ? 0x01 : 0x00; // NOSONAR: GCC 10 doesn't fully support std::byte - ret_val |= GetSignal(BPI_PIN_DT1) ? 0x02 : 0x00; // NOSONAR: GCC 10 doesn't fully support std::byte - ret_val |= GetSignal(BPI_PIN_DT2) ? 0x04 : 0x00; // NOSONAR: GCC 10 doesn't fully support std::byte - ret_val |= GetSignal(BPI_PIN_DT3) ? 0x08 : 0x00; // NOSONAR: GCC 10 doesn't fully support std::byte - ret_val |= GetSignal(BPI_PIN_DT4) ? 0x10 : 0x00; // NOSONAR: GCC 10 doesn't fully support std::byte - ret_val |= GetSignal(BPI_PIN_DT5) ? 0x20 : 0x00; // NOSONAR: GCC 10 doesn't fully support std::byte - ret_val |= GetSignal(BPI_PIN_DT6) ? 0x40 : 0x00; // NOSONAR: GCC 10 doesn't fully support std::byte - ret_val |= GetSignal(BPI_PIN_DT7) ? 0x80 : 0x00; // NOSONAR: GCC 10 doesn't fully support std::byte - return ret_val; -} - -bool DataSample_BananaM2p::GetSignal(int pin) const -{ - int bank = SunXI::GPIO_BANK(pin); - int num = SunXI::GPIO_NUM(pin); - - return (bool)((data[bank] >> num) & 0x1); -} - -// This will return the Banana Pi data in the "Raspberry Pi" data format. -uint32_t DataSample_BananaM2p::GetRawCapture() const -{ - uint32_t rpi_data = 0; - - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_BSY)) << PIN_BSY; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_SEL)) << PIN_SEL; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_ATN)) << PIN_ATN; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_ACK)) << PIN_ACK; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_RST)) << PIN_RST; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_MSG)) << PIN_MSG; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_CD)) << PIN_CD; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_IO)) << PIN_IO; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_REQ)) << PIN_REQ; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_ACT)) << PIN_ACT; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_DP)) << PIN_DP; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_DT0)) << PIN_DT0; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_DT1)) << PIN_DT1; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_DT2)) << PIN_DT2; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_DT3)) << PIN_DT3; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_DT4)) << PIN_DT4; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_DT5)) << PIN_DT5; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_DT6)) << PIN_DT6; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_DT7)) << PIN_DT7; - - return rpi_data; -} \ No newline at end of file diff --git a/cpp/hal/data_sample_bananam2p.h b/cpp/hal/data_sample_bananam2p.h deleted file mode 100644 index 35f04ee6..00000000 --- a/cpp/hal/data_sample_bananam2p.h +++ /dev/null @@ -1,94 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Copyright (C) 2022 akuker -// -// [ Logical representation of a single data sample ] -// -//--------------------------------------------------------------------------- - -#pragma once - -#include "hal/data_sample.h" -#include "shared/scsi.h" -#include - -#if defined CONNECT_TYPE_STANDARD -#include "hal/connection_type/connection_standard.h" -#elif defined CONNECT_TYPE_FULLSPEC -#include "hal/connection_type/connection_fullspec.h" -#elif defined CONNECT_TYPE_AIBOM -#include "hal/connection_type/connection_aibom.h" -#elif defined CONNECT_TYPE_GAMERNIUM -#include "hal/connection_type/connection_gamernium.h" -#else -#error Invalid connection type or none specified -#endif - -class DataSample_BananaM2p final : public DataSample -{ - public: - bool GetSignal(int pin) const override; - - bool GetBSY() const override - { - return GetSignal(BPI_PIN_BSY); - } - bool GetSEL() const override - { - return GetSignal(BPI_PIN_SEL); - } - bool GetATN() const override - { - return GetSignal(BPI_PIN_ATN); - } - bool GetACK() const override - { - return GetSignal(BPI_PIN_ACK); - } - bool GetRST() const override - { - return GetSignal(BPI_PIN_RST); - } - bool GetMSG() const override - { - return GetSignal(BPI_PIN_MSG); - } - bool GetCD() const override - { - return GetSignal(BPI_PIN_CD); - } - bool GetIO() const override - { - return GetSignal(BPI_PIN_IO); - } - bool GetREQ() const override - { - return GetSignal(BPI_PIN_REQ); - } - bool GetACT() const override - { - return GetSignal(BPI_PIN_ACT); - } - bool GetDP() const override - { - return GetSignal(BPI_PIN_DP); - } - - uint8_t GetDAT() const override; - - uint32_t GetRawCapture() const override; - - DataSample_BananaM2p(const array &in_data, uint64_t in_timestamp) - : DataSample{in_timestamp}, data{in_data} - { - } - DataSample_BananaM2p() = default; - - ~DataSample_BananaM2p() override = default; - - private: - array data = {0}; -}; \ No newline at end of file diff --git a/cpp/hal/data_sample_raspberry.h b/cpp/hal/data_sample_raspberry.h index a0b5e1e7..9951d661 100644 --- a/cpp/hal/data_sample_raspberry.h +++ b/cpp/hal/data_sample_raspberry.h @@ -18,10 +18,6 @@ #include "hal/connection_type/connection_standard.h" #elif defined CONNECT_TYPE_FULLSPEC #include "hal/connection_type/connection_fullspec.h" -#elif defined CONNECT_TYPE_AIBOM -#include "hal/connection_type/connection_aibom.h" -#elif defined CONNECT_TYPE_GAMERNIUM -#include "hal/connection_type/connection_gamernium.h" #else #error Invalid connection type or none specified #endif @@ -106,4 +102,4 @@ class DataSample_Raspberry final : public DataSample private: uint32_t data = 0; -}; \ No newline at end of file +}; diff --git a/cpp/hal/gpiobus.cpp b/cpp/hal/gpiobus.cpp index a6bf2555..1f467bfa 100644 --- a/cpp/hal/gpiobus.cpp +++ b/cpp/hal/gpiobus.cpp @@ -6,15 +6,12 @@ // Powered by XM6 TypeG Technology. // Copyright (C) 2016-2020 GIMONS // -// [ GPIO-SCSI bus ] -// //--------------------------------------------------------------------------- #include "hal/gpiobus.h" #include "hal/sbc_version.h" #include "hal/systimer.h" #include -#include #include #include #include @@ -393,12 +390,7 @@ bool GPIOBUS::PollSelectEvent() #else GPIO_FUNCTION_TRACE spdlog::trace(__PRETTY_FUNCTION__); - errno = 0; - int prev_mode = -1; - if (SBC_Version::IsBananaPi()) { - prev_mode = GetMode(BPI_PIN_SEL); - SetMode(BPI_PIN_SEL, GPIO_IRQ_IN); - } + errno = 0; if (epoll_event epev; epoll_wait(epfd, &epev, 1, -1) <= 0) { spdlog::warn("epoll_wait failed"); @@ -410,9 +402,6 @@ bool GPIOBUS::PollSelectEvent() return false; } - if (SBC_Version::IsBananaPi()) { - SetMode(BPI_PIN_SEL, prev_mode); - } return true; #endif } @@ -435,10 +424,10 @@ void GPIOBUS::ClearSelectEvent() bool GPIOBUS::WaitSignal(int pin, bool ast) { // Get current time - uint32_t now = SysTimer::GetTimerLow(); + const uint32_t now = SysTimer::GetTimerLow(); // Calculate timeout (3000ms) - uint32_t timeout = 3000 * 1000; + const uint32_t timeout = 3000 * 1000; do { // Immediately upon receiving a reset diff --git a/cpp/hal/gpiobus.h b/cpp/hal/gpiobus.h index 74fb820b..7dbff55c 100644 --- a/cpp/hal/gpiobus.h +++ b/cpp/hal/gpiobus.h @@ -5,7 +5,6 @@ // // Powered by XM6 TypeG Technology. // Copyright (C) 2016-2020 GIMONS -// [ GPIO-SCSI bus ] // //--------------------------------------------------------------------------- @@ -13,7 +12,6 @@ #include "hal/bus.h" #include "shared/scsi.h" -#include #include #include @@ -28,17 +26,11 @@ //--------------------------------------------------------------------------- //#define CONNECT_TYPE_STANDARD // Standard (SCSI logic, standard pin assignment) //#define CONNECT_TYPE_FULLSPEC // Full spec (SCSI logic, standard pin assignment) -//#define CONNECT_TYPE_AIBOM // AIBOM version (positive logic, unique pin assignment) -//#define CONNECT_TYPE_GAMERNIUM // GAMERnium.com version (standard logic, unique pin assignment) #if defined CONNECT_TYPE_STANDARD #include "hal/connection_type/connection_standard.h" #elif defined CONNECT_TYPE_FULLSPEC #include "hal/connection_type/connection_fullspec.h" -#elif defined CONNECT_TYPE_AIBOM -#include "hal/connection_type/connection_aibom.h" -#elif defined CONNECT_TYPE_GAMERNIUM -#include "hal/connection_type/connection_gamernium.h" #else #error Invalid connection type or none specified #endif @@ -176,11 +168,11 @@ class GPIOBUS : public BUS bool Init(mode_e mode = mode_e::TARGET) override; // Command receive handshake - int CommandHandShake(vector &) override; + int CommandHandShake(vector&) override; // Data receive handshake - int ReceiveHandShake(uint8_t *buf, int count) override; + int ReceiveHandShake(uint8_t *, int) override; // Data transmission handshake - int SendHandShake(uint8_t *buf, int count, int delay_after_bytes) override; + int SendHandShake(uint8_t *, int, int) override; // SEL signal event polling bool PollSelectEvent() override; @@ -208,13 +200,13 @@ class GPIOBUS : public BUS virtual void DrvConfig(uint32_t drive) = 0; // Operation mode - mode_e actmode = mode_e::TARGET; // NOSONAR: This protected so derived classes can access it + mode_e actmode = mode_e::TARGET; #ifdef __linux__ // SEL signal event request - struct gpioevent_request selevreq = {}; // NOSONAR: This protected so derived classes can access it + struct gpioevent_request selevreq = {}; // epoll file descriptor - int epfd = 0; // NOSONAR: This is protected so derived classes can access it + int epfd = 0; #endif }; diff --git a/cpp/hal/gpiobus_bananam2p.cpp b/cpp/hal/gpiobus_bananam2p.cpp deleted file mode 100644 index 7d42f3a5..00000000 --- a/cpp/hal/gpiobus_bananam2p.cpp +++ /dev/null @@ -1,1055 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi (And Banana Pi) -// -// Copyright (c) 2012-2015 Ben Croston -// Copyright (C) 2022 akuker -// -// Large portions of this functionality were derived from c_gpio.c, which -// is part of the RPI.GPIO library available here: -// https://github.com/BPI-SINOVOIP/RPi.GPIO/blob/master/source/c_gpio.c -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -// of the Software, and to permit persons to whom the Software is furnished to do -// so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// -//--------------------------------------------------------------------------- - -#include -#include -#include -#include -#ifdef __linux__ -#include -#endif -#include -#include - -#include "hal/gpiobus.h" -#include "hal/gpiobus_bananam2p.h" -#include "hal/pi_defs/bpi-gpio.h" -#include "hal/sunxi_utils.h" -#include "hal/systimer.h" -#include "hal/log.h" - -#define ARRAY_SIZE(x) (sizeof(x) / (sizeof(x[0]))) - -bool GPIOBUS_BananaM2p::Init(mode_e mode) -{ - GPIO_FUNCTION_TRACE - GPIOBUS::Init(mode); - SysTimer::Init(); - - sbc_version = SBC_Version::GetSbcVersion(); - - for (auto const gpio_num : SignalTable) { - if (gpio_num == -1) { - break; - } - - int gpio_bank = SunXI::GPIO_BANK(gpio_num); - - if (std::ranges::find(gpio_banks, gpio_bank) != gpio_banks.end()) { - LOGTRACE("Duplicate bank: %d", gpio_bank) - - } else { - LOGDEBUG("New bank: %d", gpio_bank) - gpio_banks.push_back(gpio_bank); - } - } - - if (int result = sunxi_setup(); result != SETUP_OK) { - return false; - } - - InitializeGpio(); - - MakeTable(); - - // SetupSelEvent needs to be called AFTER Initialize GPIO. This function - // reconfigures the SEL signal. - if (!SetupSelEvent()) { - LOGERROR("Failed to setup SELECT poll event") - return false; - } - LOGTRACE("SetupSelEvent OK!") - - // Set drive strength to maximum - DrvConfig(3); - - return true; -} - -void GPIOBUS_BananaM2p::InitializeGpio() -{ - GPIO_FUNCTION_TRACE - - // Set pull up/pull down -#if SIGNAL_CONTROL_MODE == 0 - int pullmode = GPIO_PULLNONE; -#elif SIGNAL_CONTROL_MODE == 1 - int pullmode = GPIO_PULLUP; -#else - int pullmode = GPIO_PULLDOWN; -#endif - - // Initialize all signals - for (int i = 0; SignalTable[i] >= 0; i++) { - int j = SignalTable[i]; - PinConfig(j, GPIO_INPUT); - PullConfig(j, pullmode); - PinSetSignal(j, OFF); - } - - // Set control signals - PinConfig(BPI_PIN_ACT, GPIO_OUTPUT); - PinConfig(BPI_PIN_TAD, GPIO_OUTPUT); - PinConfig(BPI_PIN_IND, GPIO_OUTPUT); - PinConfig(BPI_PIN_DTD, GPIO_OUTPUT); - PinSetSignal(BPI_PIN_ACT, OFF); - PinSetSignal(BPI_PIN_TAD, OFF); - PinSetSignal(BPI_PIN_IND, OFF); - PinSetSignal(BPI_PIN_DTD, OFF); - - // Set the ENABLE signal - // This is used to show that the application is running - PinConfig(BPI_PIN_ENB, GPIO_OUTPUT); - PinSetSignal(BPI_PIN_ENB, ON); -} - -void GPIOBUS_BananaM2p::Cleanup() -{ - GPIO_FUNCTION_TRACE -#if defined(__x86_64__) || defined(__X86__) || defined(__aarch64__) - dummy_var--; // Need to do something to prevent Sonar from claiming this should be a const function - return; -#else - -#ifdef USE_SEL_EVENT_ENABLE - // Release SEL signal interrupt - close(selevreq.fd); -#endif // USE_SEL_EVENT_ENABLE - - // Set control signals - PinConfig(BPI_PIN_ACT, GPIO_INPUT); - PinConfig(BPI_PIN_TAD, GPIO_INPUT); - PinConfig(BPI_PIN_IND, GPIO_INPUT); - PinConfig(BPI_PIN_DTD, GPIO_INPUT); - PinSetSignal(BPI_PIN_ENB, OFF); - PinSetSignal(BPI_PIN_ACT, OFF); - PinSetSignal(BPI_PIN_TAD, OFF); - PinSetSignal(BPI_PIN_IND, OFF); - PinSetSignal(BPI_PIN_DTD, OFF); - - // Initialize all signals - for (int i = 0; SignalTable[i] >= 0; i++) { - int pin = SignalTable[i]; - PinConfig(pin, GPIO_INPUT); - PullConfig(pin, GPIO_PULLNONE); - PinSetSignal(pin, OFF); - } - - // Set drive strength back to Default (Level 1) - DrvConfig(1); - - munmap((void *)gpio_map, SunXI::BLOCK_SIZE); - munmap((void *)r_gpio_map, SunXI::BLOCK_SIZE); - -#endif -} - -void GPIOBUS_BananaM2p::Reset() -{ -#if defined(__x86_64__) || defined(__X86__) || defined(__aarch64__) - dummy_var++; - return; -#else - int i; - int j; - - // Turn off active signal - SetControl(BPI_PIN_ACT, ACT_OFF); - - // Set all signals to off - for (i = 0;; i++) { - j = SignalTable[i]; - if (j < 0) { - break; - } - - SetSignal(j, OFF); - } - - if (actmode == mode_e::TARGET) { - // Target mode - - // Set target signal to input - SetControl(BPI_PIN_TAD, TAD_IN); - SetMode(BPI_PIN_BSY, IN); - SetMode(BPI_PIN_MSG, IN); - SetMode(BPI_PIN_CD, IN); - SetMode(BPI_PIN_REQ, IN); - SetMode(BPI_PIN_IO, IN); - - // Set the initiator signal to input - SetControl(BPI_PIN_IND, IND_IN); - SetMode(BPI_PIN_SEL, IN); - SetMode(BPI_PIN_ATN, IN); - SetMode(BPI_PIN_ACK, IN); - SetMode(BPI_PIN_RST, IN); - - // Set data bus signals to input - SetControl(BPI_PIN_DTD, DTD_IN); - SetMode(BPI_PIN_DT0, IN); - SetMode(BPI_PIN_DT1, IN); - SetMode(BPI_PIN_DT2, IN); - SetMode(BPI_PIN_DT3, IN); - SetMode(BPI_PIN_DT4, IN); - SetMode(BPI_PIN_DT5, IN); - SetMode(BPI_PIN_DT6, IN); - SetMode(BPI_PIN_DT7, IN); - SetMode(BPI_PIN_DP, IN); - } else { - // Initiator mode - - // Set target signal to input - SetControl(BPI_PIN_TAD, TAD_IN); - SetMode(BPI_PIN_BSY, IN); - SetMode(BPI_PIN_MSG, IN); - SetMode(BPI_PIN_CD, IN); - SetMode(BPI_PIN_REQ, IN); - SetMode(BPI_PIN_IO, IN); - - // Set the initiator signal to output - SetControl(BPI_PIN_IND, IND_OUT); - SetMode(BPI_PIN_SEL, OUT); - SetMode(BPI_PIN_ATN, OUT); - SetMode(BPI_PIN_ACK, OUT); - SetMode(BPI_PIN_RST, OUT); - - // Set the data bus signals to output - SetControl(BPI_PIN_DTD, DTD_OUT); - SetMode(BPI_PIN_DT0, OUT); - SetMode(BPI_PIN_DT1, OUT); - SetMode(BPI_PIN_DT2, OUT); - SetMode(BPI_PIN_DT3, OUT); - SetMode(BPI_PIN_DT4, OUT); - SetMode(BPI_PIN_DT5, OUT); - SetMode(BPI_PIN_DT6, OUT); - SetMode(BPI_PIN_DT7, OUT); - SetMode(BPI_PIN_DP, OUT); - } - - // Initialize all signals - // TODO!! For now, just re-run Acquire - Acquire(); -#endif // ifdef __x86_64__ || __X86__ -} - -bool GPIOBUS_BananaM2p::SetupSelEvent() -{ -#if defined(__x86_64__) || defined(__X86__) || defined(__aarch64__) - dummy_var += 2; // Need to do something to prevent Sonar from claiming this should be a const function - return false; -#else - GPIO_FUNCTION_TRACE - int gpio_pin = BPI_PIN_SEL; - - // GPIO chip open - LOGTRACE("%s GPIO chip open [%d]", __PRETTY_FUNCTION__, gpio_pin) - std::string gpio_dev = "/dev/gpiochip0"; - if (SunXI::GPIO_BANK(gpio_pin) >= 11) { - gpio_dev = "/dev/gpiochip1"; - LOGWARN("gpiochip1 support isn't implemented yet....") - LOGWARN("THIS PROBABLY WONT WORK!") - } - - int gpio_fd = open(gpio_dev.c_str(), 0); - if (gpio_fd == -1) { - LOGERROR("Unable to open /dev/gpiochip0. Is PiSCSI or RaSCSI already running?") - return false; - } - - // Event request setting - LOGTRACE("%s Event request setting (pin sel: %d)", __PRETTY_FUNCTION__, gpio_pin) - strncpy(selevreq.consumer_label, "PiSCSI", ARRAY_SIZE(selevreq.consumer_label)); - selevreq.lineoffset = gpio_pin; - selevreq.handleflags = GPIOHANDLE_REQUEST_INPUT; -#if SIGNAL_CONTROL_MODE < 2 - selevreq.eventflags = GPIOEVENT_REQUEST_FALLING_EDGE; - LOGTRACE("%s eventflags = GPIOEVENT_REQUEST_FALLING_EDGE", __PRETTY_FUNCTION__) -#else - selevreq.eventflags = GPIOEVENT_REQUEST_RISING_EDGE; - LOGTRACE("%s eventflags = GPIOEVENT_REQUEST_RISING_EDGE", __PRETTY_FUNCTION__) -#endif // SIGNAL_CONTROL_MODE - - // Get event request - if (ioctl(gpio_fd, GPIO_GET_LINEEVENT_IOCTL, &selevreq) == -1) { - LOGERROR("selevreq.fd = %d %08X", selevreq.fd, (unsigned int)selevreq.fd) - LOGERROR("Unable to register event request. Is PiSCSI or RaSCSI already running?") - LOGERROR("[%08X] %s", errno, strerror(errno)) - close(gpio_fd); - return false; - } - - // Close GPIO chip file handle - LOGTRACE("%s Close GPIO chip file handle", __PRETTY_FUNCTION__) - close(gpio_fd); - - // epoll initialization - LOGTRACE("%s epoll initialization", __PRETTY_FUNCTION__) - epfd = epoll_create(1); - if (epfd == -1) { - LOGERROR("Unable to create the epoll event") - return false; - } - epoll_event ev = {}; - memset(&ev, 0, sizeof(ev)); - ev.events = EPOLLIN | EPOLLPRI; - ev.data.fd = selevreq.fd; - if (epoll_ctl(epfd, EPOLL_CTL_ADD, selevreq.fd, &ev) < 0) { - return false; - } - - return true; -#endif -} - -void GPIOBUS_BananaM2p::SetENB(bool ast) -{ - PinSetSignal(BPI_PIN_ENB, ast ? ENB_ON : ENB_OFF); -} - -bool GPIOBUS_BananaM2p::GetBSY() const -{ - return GetSignal(BPI_PIN_BSY); -} - -void GPIOBUS_BananaM2p::SetBSY(bool ast) -{ - if (actmode == mode_e::TARGET) { - if (ast) { - // Turn on ACTIVE signal - SetControl(BPI_PIN_ACT, ACT_ON); - - // Set Target signal to output - SetControl(BPI_PIN_TAD, TAD_OUT); - - SetMode(BPI_PIN_BSY, OUT); - SetMode(BPI_PIN_MSG, OUT); - SetMode(BPI_PIN_CD, OUT); - SetMode(BPI_PIN_REQ, OUT); - SetMode(BPI_PIN_IO, OUT); - - // Set BSY signal - SetSignal(BPI_PIN_BSY, ast); - - } else { - // Turn off the ACTIVE signal - SetControl(BPI_PIN_ACT, ACT_OFF); - - // Set the target signal to input - SetControl(BPI_PIN_TAD, TAD_IN); - - SetMode(BPI_PIN_BSY, IN); - SetMode(BPI_PIN_MSG, IN); - SetMode(BPI_PIN_CD, IN); - SetMode(BPI_PIN_REQ, IN); - SetMode(BPI_PIN_IO, IN); - } - } else { - // Set BSY signal - SetSignal(BPI_PIN_BSY, ast); - } -} - -bool GPIOBUS_BananaM2p::GetSEL() const -{ - return GetSignal(BPI_PIN_SEL); -} - -void GPIOBUS_BananaM2p::SetSEL(bool ast) -{ - if (actmode == mode_e::INITIATOR && ast) { - // Turn on ACTIVE signal - SetControl(BPI_PIN_ACT, ACT_ON); - } - - // Set SEL signal - SetSignal(BPI_PIN_SEL, ast); -} - -bool GPIOBUS_BananaM2p::GetATN() const -{ - return GetSignal(BPI_PIN_ATN); -} - -void GPIOBUS_BananaM2p::SetATN(bool ast) -{ - SetSignal(BPI_PIN_ATN, ast); -} - -bool GPIOBUS_BananaM2p::GetACK() const -{ - return GetSignal(BPI_PIN_ACK); -} - -void GPIOBUS_BananaM2p::SetACK(bool ast) -{ - SetSignal(BPI_PIN_ACK, ast); -} - -bool GPIOBUS_BananaM2p::GetACT() const -{ - return GetSignal(BPI_PIN_ACT); -} - -void GPIOBUS_BananaM2p::SetACT(bool ast) -{ - SetSignal(BPI_PIN_ACT, ast); -} - -bool GPIOBUS_BananaM2p::GetRST() const -{ - return GetSignal(BPI_PIN_RST); -} - -void GPIOBUS_BananaM2p::SetRST(bool ast) -{ - SetSignal(BPI_PIN_RST, ast); -} - -bool GPIOBUS_BananaM2p::GetMSG() const -{ - return GetSignal(BPI_PIN_MSG); -} - -void GPIOBUS_BananaM2p::SetMSG(bool ast) -{ - SetSignal(BPI_PIN_MSG, ast); -} - -bool GPIOBUS_BananaM2p::GetCD() const -{ - return GetSignal(BPI_PIN_CD); -} - -void GPIOBUS_BananaM2p::SetCD(bool ast) -{ - SetSignal(BPI_PIN_CD, ast); -} - -bool GPIOBUS_BananaM2p::GetIO() -{ - bool ast = GetSignal(BPI_PIN_IO); - - if (actmode == mode_e::INITIATOR) { - // Change the data input/output direction by IO signal - if (ast) { - SetControl(BPI_PIN_DTD, DTD_IN); - SetMode(BPI_PIN_DT0, IN); - SetMode(BPI_PIN_DT1, IN); - SetMode(BPI_PIN_DT2, IN); - SetMode(BPI_PIN_DT3, IN); - SetMode(BPI_PIN_DT4, IN); - SetMode(BPI_PIN_DT5, IN); - SetMode(BPI_PIN_DT6, IN); - SetMode(BPI_PIN_DT7, IN); - SetMode(BPI_PIN_DP, IN); - } else { - SetControl(BPI_PIN_DTD, DTD_OUT); - SetMode(BPI_PIN_DT0, OUT); - SetMode(BPI_PIN_DT1, OUT); - SetMode(BPI_PIN_DT2, OUT); - SetMode(BPI_PIN_DT3, OUT); - SetMode(BPI_PIN_DT4, OUT); - SetMode(BPI_PIN_DT5, OUT); - SetMode(BPI_PIN_DT6, OUT); - SetMode(BPI_PIN_DT7, OUT); - SetMode(BPI_PIN_DP, OUT); - } - } - - return ast; -} - -void GPIOBUS_BananaM2p::SetIO(bool ast) -{ - if (actmode == mode_e::TARGET) { - // Change the data input/output direction by IO signal - if (ast) { - SetControl(BPI_PIN_DTD, DTD_OUT); - SetMode(BPI_PIN_DT0, OUT); - SetMode(BPI_PIN_DT1, OUT); - SetMode(BPI_PIN_DT2, OUT); - SetMode(BPI_PIN_DT3, OUT); - SetMode(BPI_PIN_DT4, OUT); - SetMode(BPI_PIN_DT5, OUT); - SetMode(BPI_PIN_DT6, OUT); - SetMode(BPI_PIN_DT7, OUT); - SetMode(BPI_PIN_DP, OUT); - - SetDAT(0); - SetSignal(BPI_PIN_IO, ast); - - } else { - SetControl(BPI_PIN_DTD, DTD_IN); - SetMode(BPI_PIN_DT0, IN); - SetMode(BPI_PIN_DT1, IN); - SetMode(BPI_PIN_DT2, IN); - SetMode(BPI_PIN_DT3, IN); - SetMode(BPI_PIN_DT4, IN); - SetMode(BPI_PIN_DT5, IN); - SetMode(BPI_PIN_DT6, IN); - SetMode(BPI_PIN_DT7, IN); - SetMode(BPI_PIN_DP, IN); - } - } else { - SetSignal(BPI_PIN_IO, ast); - } -} - -bool GPIOBUS_BananaM2p::GetREQ() const -{ - return GetSignal(BPI_PIN_REQ); -} - -void GPIOBUS_BananaM2p::SetREQ(bool ast) -{ - SetSignal(BPI_PIN_REQ, ast); -} - -uint8_t GPIOBUS_BananaM2p::GetDAT() -{ - GPIO_FUNCTION_TRACE - - Acquire(); - uint32_t data = - ((GetSignal(BPI_PIN_DT0) ? 0x01 : 0x00) << 0) | ((GetSignal(BPI_PIN_DT1) ? 0x01 : 0x00) << 1) | - ((GetSignal(BPI_PIN_DT2) ? 0x01 : 0x00) << 2) | ((GetSignal(BPI_PIN_DT3) ? 0x01 : 0x00) << 3) | - ((GetSignal(BPI_PIN_DT4) ? 0x01 : 0x00) << 4) | ((GetSignal(BPI_PIN_DT5) ? 0x01 : 0x00) << 5) | - ((GetSignal(BPI_PIN_DT6) ? 0x01 : 0x00) << 6) | - ((GetSignal(BPI_PIN_DT7) ? 0x01 : 0x00) << 7); // NOSONAR: GCC 10 doesn't support shift operations on std::byte - - return (uint8_t)(data & 0xFF); -} - -void GPIOBUS_BananaM2p::SetDAT(uint8_t dat) -{ - GPIO_FUNCTION_TRACE - - array gpio_reg_values = {0}; - - for (size_t gpio_num = 0; gpio_num < pintbl.size(); gpio_num++) { - bool value; - if (gpio_num < 8) { - // data bits - value = !(dat & (1 << gpio_num)); // NOSONAR: GCC 10 doesn't support shift operations on std::byte - } else { - // parity bit - value = (__builtin_parity(dat) == 1); - } - - if (value) { - uint32_t this_gpio = pintbl[gpio_num]; - int bank = SunXI::GPIO_BANK(this_gpio); - int offset = SunXI::GPIO_NUM(this_gpio); - gpio_reg_values[bank] |= (1 << offset); - } - } - - sunxi_set_all_gpios(gpio_data_masks, gpio_reg_values); -} - -//--------------------------------------------------------------------------- -// -// Signal table -// -//--------------------------------------------------------------------------- -const array GPIOBUS_BananaM2p::SignalTable = {BPI_PIN_DT0, BPI_PIN_DT1, BPI_PIN_DT2, BPI_PIN_DT3, BPI_PIN_DT4, - BPI_PIN_DT5, BPI_PIN_DT6, BPI_PIN_DT7, BPI_PIN_DP, BPI_PIN_SEL, - BPI_PIN_ATN, BPI_PIN_RST, BPI_PIN_ACK, BPI_PIN_BSY, BPI_PIN_MSG, - BPI_PIN_CD, BPI_PIN_IO, BPI_PIN_REQ, -1}; - -//--------------------------------------------------------------------------- -// -// Create work table -// -//--------------------------------------------------------------------------- -void GPIOBUS_BananaM2p::MakeTable(void) -{ - for (auto this_gpio : pintbl) { - int bank = SunXI::GPIO_BANK(this_gpio); - int offset = (SunXI::GPIO_NUM(this_gpio)); - - gpio_data_masks[bank] |= (1 << offset); - } -} - -bool GPIOBUS_BananaM2p::GetDP() const -{ - return GetSignal(BPI_PIN_DP); -} - -void GPIOBUS_BananaM2p::SetControl(int pin, bool ast) -{ - GPIO_FUNCTION_TRACE - PinSetSignal(pin, ast); -} - -// Set direction -int GPIOBUS_BananaM2p::GetMode(int pin) -{ - GPIO_FUNCTION_TRACE - - uint32_t regval = 0; - int bank = SunXI::GPIO_BANK(pin); - int index = SunXI::GPIO_CFG_INDEX(pin); - int offset = SunXI::GPIO_CFG_OFFSET(pin); - - volatile SunXI::sunxi_gpio_t *pio = &(pio_map->gpio_bank[bank]); - /* DK, for PL and PM */ - if (bank >= 11) { - bank -= 11; - pio = &(r_pio_map->gpio_bank[bank]); - } - - regval = pio->CFG[0 + index]; - - // Extract the CFG field - regval &= (0x7 << offset); // 0xf? - // Shift it down to the LSB - regval >>= offset; - return (int)regval; -} - -// Set direction -void GPIOBUS_BananaM2p::SetMode(int pin, int mode) -{ - GPIO_FUNCTION_TRACE - int direction = mode; - - uint32_t regval = 0; - int bank = SunXI::GPIO_BANK(pin); // gpio >> 5 - int index = SunXI::GPIO_CFG_INDEX(pin); // (gpio & 0x1F) >> 3 - int offset = SunXI::GPIO_CFG_OFFSET(pin); // ((gpio & 0x1F) & 0x7) << 2 - LOGTRACE("%s gpio(%d) bank(%d) index(%d) offset(%d) dir(%s)", __PRETTY_FUNCTION__, pin, bank, index, offset, - (GPIO_INPUT == direction) ? "IN" - : (GPIO_IRQ_IN == direction) ? "IRQ" - : "OUT") - - volatile SunXI::sunxi_gpio_t *pio = &(pio_map->gpio_bank[bank]); - /* DK, for PL and PM */ - if (bank >= 11) { - bank -= 11; - pio = &(r_pio_map->gpio_bank[bank]); - } - - regval = pio->CFG[0 + index]; - - // Clear the cfg field - regval &= ~(0x7 << offset); // 0xf? - if (GPIO_INPUT == direction) { - regval |= (((uint32_t)SunXI::gpio_configure_values_e::gpio_input) << offset); - } else if (GPIO_OUTPUT == direction) { - regval |= (((uint32_t)SunXI::gpio_configure_values_e::gpio_output) << offset); - } else if (GPIO_IRQ_IN == direction) { - regval |= (((uint32_t)SunXI::gpio_configure_values_e::gpio_interupt) << offset); - } else { - LOGERROR("line:%d gpio number error %d", __LINE__, pin) - } - pio->CFG[0 + index] = regval; -} - -bool GPIOBUS_BananaM2p::GetSignal(int pin) const -{ - GPIO_FUNCTION_TRACE - int gpio_num = pin; - - uint32_t regval = 0; - int bank = SunXI::GPIO_BANK(gpio_num); // gpio >> 5 - int num = SunXI::GPIO_NUM(gpio_num); // gpio & 0x1F - - regval = (signals[bank] >> num) & 0x1; - return regval != 0; -} - -void GPIOBUS_BananaM2p::SetSignal(int pin, bool ast) -{ - GPIO_FUNCTION_TRACE - int gpio_num = pin; - -#if SIGNAL_CONTROL_MODE == 0 - // True : 0V - // False : Open collector output (disconnect from bus) - int sunxi_gpio_state = (ast == true) ? SunXI::HIGH : SunXI::LOW; -#elif SIGNAL_CONTROL_MODE == 1 - // True : 0V -> (CONVERT) -> 0V - // False : 3.3V -> (CONVERT) -> Open collector output - LOGWARN("%s:%d THIS LOGIC NEEDS TO BE VALIDATED/TESTED", __PRETTY_FUNCTION__, __LINE__) - int sunxi_gpio_state = (ast == true) ? SunXI::HIGH : SunXI::LOW; -#elif SIGNAL_CONTROL_MODE == 2 - // True : 3.3V -> (CONVERT) -> 0V - // False : 0V -> (CONVERT) -> Open collector output - LOGWARN("%s:%d THIS LOGIC NEEDS TO BE VALIDATED/TESTED", __PRETTY_FUNCTION__, __LINE__) - int sunxi_gpio_state = (ast == true) ? SunXI::LOW : SunXI::HIGH; -#endif // SIGNAL_CONTROL_MODE - - LOGTRACE("gpio(%d) sunxi_state(%d)", gpio_num, sunxi_gpio_state) - - int bank = SunXI::GPIO_BANK(gpio_num); // gpio >> 5 - int num = SunXI::GPIO_NUM(gpio_num); // gpio & 0x1F - - volatile SunXI::sunxi_gpio_t *pio = &(pio_map->gpio_bank[bank]); - - /* DK, for PL and PM */ - if (bank >= 11) { - LOGTRACE("bank > 11") - bank -= 11; - pio = &(r_pio_map->gpio_bank[bank]); - } - - if (sunxi_gpio_state == SunXI::HIGH) - pio->DAT = pio->DAT & ~(1 << num); - else - pio->DAT = pio->DAT | (1 << num); -} - -void GPIOBUS_BananaM2p::DisableIRQ() -{ - *tmr_ctrl = 0b00; -} - -void GPIOBUS_BananaM2p::EnableIRQ() -{ - *tmr_ctrl = 0b11; -} - -void GPIOBUS_BananaM2p::PinConfig(int pin, int mode) -{ - GPIO_FUNCTION_TRACE - int gpio_num = pin; - int sunxi_direction = (mode == GPIO_INPUT) ? SunXI::INPUT : SunXI::OUTPUT; - - sunxi_setup_gpio(gpio_num, sunxi_direction, -1); -} - -void GPIOBUS_BananaM2p::PullConfig(int pin, int mode) -{ - GPIO_FUNCTION_TRACE -#if defined(__x86_64__) || defined(__X86__) || defined(__aarch64__) - dummy_var++; // Need to do something to prevent Sonar from claiming this should be a const function - (void)pin; - (void)mode; - return; -#else - - // Note: this will throw an exception if an invalid pin is specified - int gpio_num = pin; - int pull_up_down_state = 0; - - switch (mode) { - case GPIO_PULLNONE: - pull_up_down_state = SunXI::PUD_OFF; - break; - case GPIO_PULLUP: - pull_up_down_state = SunXI::PUD_UP; - break; - case GPIO_PULLDOWN: - pull_up_down_state = SunXI::PUD_DOWN; - break; - default: - LOGERROR("%s INVALID PIN MODE", __PRETTY_FUNCTION__); - return; - } - - uint32_t regval = 0; - int bank = SunXI::GPIO_BANK(gpio_num); // gpio >> 5 - int index = SunXI::GPIO_PUL_INDEX(gpio_num); // (gpio & 0x1f) >> 4 - int offset = SunXI::GPIO_PUL_OFFSET(gpio_num); // (gpio) & 0x0F) << 1 - LOGTRACE("%s gpio(%d) bank(%d) index(%d) offset(%d)", __PRETTY_FUNCTION__, gpio_num, bank, index, offset) - - volatile SunXI::sunxi_gpio_t *pio = &(pio_map->gpio_bank[bank]); - /* DK, for PL and PM */ - if (bank >= 11) { - bank -= 11; - pio = &(r_pio_map->gpio_bank[bank]); - } - - regval = *(&pio->PULL[0] + index); - regval &= ~(3 << offset); - regval |= pull_up_down_state << offset; - pio->PULL[0 + index] = regval; -#endif -} - -void GPIOBUS_BananaM2p::PinSetSignal(int pin, bool ast) -{ - GPIO_FUNCTION_TRACE - int gpio_num = pin; - - int sunxi_gpio_state = (ast == true) ? SunXI::HIGH : SunXI::LOW; - sunxi_output_gpio(gpio_num, sunxi_gpio_state); -} - -void GPIOBUS_BananaM2p::DrvConfig(uint32_t drive) -{ - GPIO_FUNCTION_TRACE - - for (auto pin : SignalTable) { - if (pin == -1) { - continue; - } - - LOGTRACE("Configuring GPIO %d to drive strength %d", pin, drive) - - uint32_t regval = 0; - int bank = SunXI::GPIO_BANK(pin); // gpio >> 5 - int index = SunXI::GPIO_DRV_INDEX(pin); // (gpio & 0x1F) >> 3 - int offset = SunXI::GPIO_DRV_OFFSET(pin); // ((gpio & 0x1F) & 0x7) << 2 - LOGTRACE("%s gpio(%d) bank(%d) index(%d) offset(%d)", __PRETTY_FUNCTION__, pin, bank, index, offset) - - volatile SunXI::sunxi_gpio_t *pio = &(pio_map->gpio_bank[bank]); - /* DK, for PL and PM */ - if (bank >= 11) { - bank -= 11; - pio = &(r_pio_map->gpio_bank[bank]); - } - - // Get current register value - regval = pio->DRV[0 + index]; - // Clear the DRV value for that gpio - regval &= ~(0x7 << offset); // 0xf? - // Set the new DRV strength - regval |= (drive & 0b11) << offset; - // Save back to the register - pio->DRV[0 + index] = regval; - } - - // #endif // if __arm__ -} - -uint32_t GPIOBUS_BananaM2p::Acquire() -{ - GPIO_FUNCTION_TRACE - - for (auto bank : gpio_banks) { - volatile SunXI::sunxi_gpio_t *pio = &(pio_map->gpio_bank[bank]); - /* DK, for PL and PM */ - if (bank >= 11) { - pio = &(r_pio_map->gpio_bank[bank - 11]); - } - - uint32_t regval = pio->DAT; - -#if SIGNAL_CONTROL_MODE < 2 - // Invert if negative logic (internal processing is unified to positive logic) - regval = ~regval; -#endif // SIGNAL_CONTROL_MODE - signals[bank] = regval; - } - // TODO: This should do something someday.... - return 0; -} - -int GPIOBUS_BananaM2p::sunxi_setup(void) -{ - GPIO_FUNCTION_TRACE -#if defined(__x86_64__) || defined(__X86__) || defined(__aarch64__) - dummy_var++; // Need to do something to prevent Sonar from claiming this should be a const function - return SunXI::SETUP_MMAP_FAIL; -#else - int mem_fd; - uint8_t *gpio_mem; - - // mmap the GPIO memory registers - if ((mem_fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) { - LOGERROR("Error: Unable to open /dev/mem. Are you running as root?") - LOGDEBUG("errno: [%08X] %s", errno, strerror(errno)); - return SunXI::SETUP_DEVMEM_FAIL; - } - - if ((gpio_mem = (uint8_t *)malloc(SunXI::BLOCK_SIZE + (SunXI::PAGE_SIZE - 1))) == NULL) { - LOGERROR("Error: Unable to allocate gpio memory. Are you running as root?") - LOGDEBUG("errno: [%08X] %s", errno, strerror(errno)); - return SunXI::SETUP_DEVMEM_FAIL; - } - - if ((uint32_t)gpio_mem % SunXI::PAGE_SIZE) - gpio_mem += SunXI::PAGE_SIZE - ((uint32_t)gpio_mem % SunXI::PAGE_SIZE); - - gpio_map = (uint32_t *)mmap((caddr_t)gpio_mem, SunXI::BLOCK_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, - mem_fd, SunXI::SUNXI_GPIO_BASE); - if ((void *)gpio_map == MAP_FAILED) { - LOGERROR("Error: Unable to map gpio memory. Are you running as root?") - LOGDEBUG("errno: [%08X] %s", errno, strerror(errno)); - return SunXI::SETUP_MMAP_FAIL; - } - pio_map = (volatile SunXI::sunxi_gpio_reg *)(gpio_map + (SunXI::SUNXI_GPIO_REG_OFFSET >> 2)); - LOGTRACE("gpio_mem[%p] gpio_map[%p] pio_map[%p]", gpio_mem, gpio_map, pio_map) - // R_PIO GPIO LMN - r_gpio_map = (uint32_t *)mmap((caddr_t)0, SunXI::BLOCK_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, - SunXI::SUNXI_R_GPIO_BASE); - if ((void *)r_gpio_map == MAP_FAILED) { - LOGERROR("Error: Unable to map r_gpio memory. Are you running as root?") - LOGDEBUG("errno: [%08X] %s", errno, strerror(errno)); - return SunXI::SETUP_MMAP_FAIL; - } - r_pio_map = (volatile SunXI::sunxi_gpio_reg *)(r_gpio_map + (SunXI::SUNXI_R_GPIO_REG_OFFSET >> 2)); - LOGTRACE("r_gpio_map[%p] r_pio_map[%p]", r_gpio_map, r_pio_map) - - tmr_ctrl = gpio_map + ((SunXI::TMR_REGISTER_BASE - SunXI::SUNXI_GPIO_BASE) >> 2); - // LOGINFO("tmr_ctrl offset: %08X value: %08X", (TMR_REGISTER_BASE - SUNXI_GPIO_BASE), *tmr_ctrl); - - close(mem_fd); - return SETUP_OK; -#endif -} - -void GPIOBUS_BananaM2p::sunxi_setup_gpio(int pin, int direction, int pud) -{ - GPIO_FUNCTION_TRACE -#if defined(__x86_64__) || defined(__X86__) || defined(__aarch64__) - dummy_var++; // Need to do something to prevent Sonar from claiming this should be a const function - (void)pin; - (void)direction; - (void)pud; - return; -#else - uint32_t regval = 0; - int bank = SunXI::GPIO_BANK(pin); // gpio >> 5 - int index = SunXI::GPIO_CFG_INDEX(pin); // (gpio & 0x1F) >> 3 - int offset = SunXI::GPIO_CFG_OFFSET(pin); // ((gpio & 0x1F) & 0x7) << 2 - LOGTRACE("%s gpio(%d) bank(%d) index(%d) offset(%d)", __PRETTY_FUNCTION__, pin, bank, index, offset) - - volatile SunXI::sunxi_gpio_t *pio = &(pio_map->gpio_bank[bank]); - /* DK, for PL and PM */ - if (bank >= 11) { - bank -= 11; - pio = &(r_pio_map->gpio_bank[bank]); - } - - if (pud != -1) { - set_pullupdn(pin, pud); - } - regval = *(&pio->CFG[0 + index]); - regval &= ~(0x7 << offset); // 0xf? - if (SunXI::INPUT == direction) { - pio->CFG[0 + index] = regval; - } else if (SunXI::OUTPUT == direction) { - regval |= (1 << offset); - pio->CFG[0 + index] = regval; - } else { - LOGERROR("line:%d gpio number error %d", __LINE__, pin) - } -#endif -} - -void GPIOBUS_BananaM2p::sunxi_output_gpio(int pin, int value) -{ - GPIO_FUNCTION_TRACE - if (pin < 0) { - LOGWARN("Invalid GPIO Num") - return; - } - int bank = SunXI::GPIO_BANK(pin); // gpio >> 5 - int num = SunXI::GPIO_NUM(pin); // gpio & 0x1F - - LOGTRACE("%s gpio(%d) bank(%d) num(%d) value(%d)", __PRETTY_FUNCTION__, pin, bank, num, value) - volatile SunXI::sunxi_gpio_t *pio = &(pio_map->gpio_bank[bank]); - - /* DK, for PL and PM */ - if (bank >= 11) { - bank -= 11; - pio = &(r_pio_map->gpio_bank[bank]); - } - - if (value == 0) - pio->DAT = pio->DAT & ~(1 << num); - else - pio->DAT = pio->DAT | (1 << num); -} - -void GPIOBUS_BananaM2p::sunxi_set_all_gpios(array &mask, array &value) -{ - GPIO_FUNCTION_TRACE - for (size_t bank = 0; bank < mask.size(); bank++) { - if (mask[bank] == 0) { - continue; - } - - volatile SunXI::sunxi_gpio_t *pio; - if (bank < 11) { - pio = &(pio_map->gpio_bank[bank]); - } else { - pio = &(r_pio_map->gpio_bank[bank - 11]); - } - - uint32_t reg_val = pio->DAT; - reg_val &= ~mask[bank]; - reg_val |= value[bank]; - pio->DAT = reg_val; - } -} - -int GPIOBUS_BananaM2p::sunxi_input_gpio(int pin) const -{ - GPIO_FUNCTION_TRACE - uint32_t regval = 0; - int bank = SunXI::GPIO_BANK(pin); // gpio >> 5 - int num = SunXI::GPIO_NUM(pin); // gpio & 0x1F - - LOGTRACE("%s gpio(%d) bank(%d) num(%d)", __PRETTY_FUNCTION__, pin, bank, num) - volatile SunXI::sunxi_gpio_t *pio = &(pio_map->gpio_bank[bank]); - /* DK, for PL and PM */ - if (bank >= 11) { - bank -= 11; - pio = &(r_pio_map->gpio_bank[bank]); - } - - regval = pio->DAT; - regval = regval >> num; - regval &= 1; - return regval; -} - -void GPIOBUS_BananaM2p::set_pullupdn(int pin, int pud) -{ - GPIO_FUNCTION_TRACE - int clk_offset = SunXI::PULLUPDNCLK_OFFSET + (pin / 32); - int shift = (pin % 32); - -#ifdef BPI - if (bpi_found == 1) { - gpio = *(pinTobcm_BP + pin); - return sunxi_set_pullupdn(pin, pud); - } -#endif - if (pud == SunXI::PUD_DOWN) - *(gpio_map + SunXI::PULLUPDN_OFFSET) = (*(gpio_map + SunXI::PULLUPDN_OFFSET) & ~3) | SunXI::PUD_DOWN; - else if (pud == SunXI::PUD_UP) - *(gpio_map + SunXI::PULLUPDN_OFFSET) = (*(gpio_map + SunXI::PULLUPDN_OFFSET) & ~3) | SunXI::PUD_UP; - else // pud == PUD_OFF - *(gpio_map + SunXI::PULLUPDN_OFFSET) = *(gpio_map + SunXI::PULLUPDN_OFFSET) & ~3; - - SunXI::short_wait(); - *(gpio_map + clk_offset) = 1 << shift; - SunXI::short_wait(); - *(gpio_map + SunXI::PULLUPDN_OFFSET) = *(gpio_map + SunXI::PULLUPDN_OFFSET) & ~3; - *(gpio_map + clk_offset) = 0; -} diff --git a/cpp/hal/gpiobus_bananam2p.h b/cpp/hal/gpiobus_bananam2p.h deleted file mode 100644 index a404763f..00000000 --- a/cpp/hal/gpiobus_bananam2p.h +++ /dev/null @@ -1,203 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Powered by XM6 TypeG Technology. -// Copyright (C) 2016-2020 GIMONS -// [ GPIO-SCSI bus ] -// -//--------------------------------------------------------------------------- - -#pragma once - -#include "hal/data_sample_bananam2p.h" -#include "hal/gpiobus.h" -#include "hal/pi_defs/bpi-gpio.h" -#include "hal/sbc_version.h" -#include "hal/sunxi_utils.h" -#include "shared/scsi.h" -#include - -//--------------------------------------------------------------------------- -// -// Class definition -// -//--------------------------------------------------------------------------- -class GPIOBUS_BananaM2p : public GPIOBUS -{ - public: - // Basic Functions - GPIOBUS_BananaM2p() = default; - ~GPIOBUS_BananaM2p() override = default; - // Destructor - bool Init(mode_e mode = mode_e::TARGET) override; - - void Cleanup() override; - void Reset() override; - - //--------------------------------------------------------------------------- - // - // Bus signal acquisition - // - //--------------------------------------------------------------------------- - uint32_t Acquire() override; - - void SetENB(bool ast) override; - // Set ENB signal - - bool GetBSY() const override; - // Get BSY signal - void SetBSY(bool ast) override; - // Set BSY signal - - bool GetSEL() const override; - // Get SEL signal - void SetSEL(bool ast) override; - // Set SEL signal - - bool GetATN() const override; - // Get ATN signal - void SetATN(bool ast) override; - // Set ATN signal - - bool GetACK() const override; - // Get ACK signal - void SetACK(bool ast) override; - // Set ACK signal - - bool GetACT() const override; - // Get ACT signal - void SetACT(bool ast) override; - // Set ACT signal - - bool GetRST() const override; - // Get RST signal - void SetRST(bool ast) override; - // Set RST signal - - bool GetMSG() const override; - // Get MSG signal - void SetMSG(bool ast) override; - // Set MSG signal - - bool GetCD() const override; - // Get CD signal - void SetCD(bool ast) override; - // Set CD signal - - bool GetIO() override; - // Get IO signal - void SetIO(bool ast) override; - // Set IO signal - - bool GetREQ() const override; - // Get REQ signal - void SetREQ(bool ast) override; - // Set REQ signal - - bool GetDP() const override; - - uint8_t GetDAT() override; - // Get DAT signal - void SetDAT(uint8_t dat) override; - // Set DAT signal - - bool WaitREQ(bool ast) override - { - return WaitSignal(BPI_PIN_REQ, ast); - } - bool WaitACK(bool ast) override - { - return WaitSignal(BPI_PIN_ACK, ast); - } - - // TODO: Restore these back to protected - // protected: - // SCSI I/O signal control - void MakeTable() override; - // Create work data - void SetControl(int pin, bool ast) override; - // Set Control Signal - void SetMode(int pin, int mode) override; - // Set SCSI I/O mode - int GetMode(int pin) override; - - inline bool GetSignal(int pin) const override; - - // Set SCSI output signal value - void SetSignal(int pin, bool ast) override; - - // Interrupt control - // IRQ Disabled - void DisableIRQ() override; - // IRQ Enabled - void EnableIRQ() override; - - // GPIO pin functionality settings - void PinConfig(int pin, int mode) override; - // GPIO pin direction setting - void PullConfig(int pin, int mode) override; - // GPIO pin pull up/down resistor setting - void PinSetSignal(int pin, bool ast) override; - // Set GPIO output signal - void DrvConfig(uint32_t drive) override; - // Set GPIO drive strength - - unique_ptr GetSample(uint64_t timestamp) override - { - Acquire(); - return make_unique(signals, timestamp); - } - - bool SetupSelEvent(); - - volatile uint32_t *gpio_map = nullptr; - - // Timer control register - volatile uint32_t *tmr_ctrl; - array signals = {0}; // All bus signals - - int sunxi_setup(void); - - void sunxi_set_pullupdn(int gpio, int pud); - void sunxi_setup_gpio(int gpio, int direction, int pud); - - void sunxi_output_gpio(int gpio, int value); - int sunxi_input_gpio(int gpio) const; - - int bpi_found = -1; - - volatile SunXI::sunxi_gpio_reg_t *pio_map; - volatile SunXI::sunxi_gpio_reg_t *r_pio_map; - - volatile uint32_t *r_gpio_map; - - uint8_t *gpio_mmap_reg; - uint32_t sunxi_capture_all_gpio(); - void set_pullupdn(int gpio, int pud); - - // These definitions are from c_gpio.c and should be removed at some point!! - const int SETUP_OK = 0; - - SBC_Version::sbc_version_type sbc_version; - - static const array SignalTable; - - void InitializeGpio(); - std::vector gpio_banks; - - // Note: These MUST be in order from bit 0 to bit 7 with parity as the last item in the array - const array pintbl = {BPI_PIN_DT0, BPI_PIN_DT1, BPI_PIN_DT2, BPI_PIN_DT3, BPI_PIN_DT4, - BPI_PIN_DT5, BPI_PIN_DT6, BPI_PIN_DT7, BPI_PIN_DP}; - - void sunxi_set_all_gpios(array &mask, array &value); - - array gpio_data_masks = {0}; - -#if defined(__x86_64__) || defined(__X86__) || defined(__aarch64__) - // The SEL_EVENT functions need to do something to prevent SonarCloud from - // claiming they should be const - int dummy_var = 0; -#endif -}; diff --git a/cpp/hal/gpiobus_factory.cpp b/cpp/hal/gpiobus_factory.cpp index a6fa6901..a9351a56 100644 --- a/cpp/hal/gpiobus_factory.cpp +++ b/cpp/hal/gpiobus_factory.cpp @@ -1,51 +1,48 @@ //--------------------------------------------------------------------------- // -// SCSI Target Emulator PiSCSI -// for Raspberry Pi +// SCSI Target Emulator PiSCSI +// for Raspberry Pi // -// Copyright (C) 2022 akuker -// [ GPIO bus factory ] +// Copyright (C) 2022 akuker +// Copyright (C) 2023 Uwe Seimet // //--------------------------------------------------------------------------- #include -#include "hal/gpiobus_bananam2p.h" #include "hal/gpiobus_factory.h" #include "hal/gpiobus_raspberry.h" #include "hal/gpiobus_virtual.h" #include "hal/sbc_version.h" +#include #include using namespace std; unique_ptr GPIOBUS_Factory::Create(BUS::mode_e mode) { - unique_ptr return_ptr; + unique_ptr bus; try { - // TODO Make the factory a friend of GPIOBUS and make the GPIOBUS constructor private - // so that clients cannot use it anymore but have to use the factory. - // Also make Init() private. SBC_Version::Init(); - if (SBC_Version::IsBananaPi()) { - spdlog::trace("Creating GPIOBUS_BananaM2p"); - return_ptr = make_unique(); - } else if (SBC_Version::IsRaspberryPi()) { - spdlog::trace("Creating GPIOBUS_Raspberry"); - return_ptr = make_unique(); + if (SBC_Version::IsRaspberryPi()) { + if (getuid()) { + spdlog::error("GPIO bus access requires root permissions. Are you running as root?"); + return nullptr; + } + + bus = make_unique(); } else { - spdlog::trace("Creating Virtual GPIOBUS"); - return_ptr = make_unique(); + bus = make_unique(); } - if (!return_ptr->Init(mode)) { - return nullptr; + + if (bus->Init(mode)) { + bus->Reset(); } - return_ptr->Reset(); - } catch (const invalid_argument&) { - spdlog::error("Exception while trying to initialize GPIO bus. Are you running as root?"); - return_ptr = nullptr; + } catch (const invalid_argument& e) { + spdlog::error(string("Exception while trying to initialize GPIO bus: ") + e.what()); + return nullptr; } - return return_ptr; + return bus; } diff --git a/cpp/hal/gpiobus_raspberry.cpp b/cpp/hal/gpiobus_raspberry.cpp index 1fdd9ea3..5b6f77f7 100644 --- a/cpp/hal/gpiobus_raspberry.cpp +++ b/cpp/hal/gpiobus_raspberry.cpp @@ -15,10 +15,10 @@ // //--------------------------------------------------------------------------- +#include #include "hal/gpiobus_raspberry.h" #include "hal/gpiobus.h" #include "hal/systimer.h" -#include "hal/log.h" #include #include #ifdef __linux__ @@ -185,7 +185,7 @@ bool GPIOBUS_Raspberry::Init(mode_e mode) // GPIO chip open fd = open("/dev/gpiochip0", 0); if (fd == -1) { - LOGERROR("Unable to open /dev/gpiochip0. If PiSCSI is running, please shut it down first.") + spdlog::error("Unable to open /dev/gpiochip0. If PiSCSI is running, please shut it down first."); return false; } @@ -201,7 +201,7 @@ bool GPIOBUS_Raspberry::Init(mode_e mode) // Get event request if (ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, &selevreq) == -1) { - LOGERROR("Unable to register event request. If PiSCSI is running, please shut it down first.") + spdlog::error("Unable to register event request. If PiSCSI is running, please shut it down first."); close(fd); return false; } diff --git a/cpp/hal/gpiobus_raspberry.h b/cpp/hal/gpiobus_raspberry.h index b0f7fd17..a79df0e5 100644 --- a/cpp/hal/gpiobus_raspberry.h +++ b/cpp/hal/gpiobus_raspberry.h @@ -162,9 +162,9 @@ class GPIOBUS_Raspberry : public GPIOBUS protected: // All bus signals - uint32_t signals = 0; // NOSONAR: Must be protected (not private) for testability + uint32_t signals = 0; // GPIO input level - volatile uint32_t *level = nullptr; // NOSONAR: Must be protected (not private) for testability + volatile uint32_t *level = nullptr; private: // SCSI I/O signal control diff --git a/cpp/hal/gpiobus_virtual.cpp b/cpp/hal/gpiobus_virtual.cpp index 1e15b338..136dae24 100644 --- a/cpp/hal/gpiobus_virtual.cpp +++ b/cpp/hal/gpiobus_virtual.cpp @@ -6,8 +6,6 @@ // Powered by XM6 TypeG Technology. // Copyright (C) 2016-2020 GIMONS // -// [ GPIO-SCSI bus ] -// //--------------------------------------------------------------------------- #include "hal/gpiobus_virtual.h" diff --git a/cpp/hal/gpiobus_virtual.h b/cpp/hal/gpiobus_virtual.h index 0fba1cd0..85e00c79 100644 --- a/cpp/hal/gpiobus_virtual.h +++ b/cpp/hal/gpiobus_virtual.h @@ -5,7 +5,6 @@ // // Powered by XM6 TypeG Technology. // Copyright (C) 2016-2020 GIMONS -// [ GPIO-SCSI bus ] // //--------------------------------------------------------------------------- diff --git a/cpp/hal/pi_defs/bpi-gpio.h b/cpp/hal/pi_defs/bpi-gpio.h deleted file mode 100644 index 89541fe8..00000000 --- a/cpp/hal/pi_defs/bpi-gpio.h +++ /dev/null @@ -1,356 +0,0 @@ -/* -Copyright (c) 2014-2017 Banana Pi -Updates Copyright (C) 2022 akuker - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -#pragma once - -const int GPIO_NOT_USED = -1; - -const int GPIO_PA00 = 0; -const int GPIO_PA01 = 1; -const int GPIO_PA02 = 2; -const int GPIO_PA03 = 3; -const int GPIO_PA04 = 4; -const int GPIO_PA05 = 5; -const int GPIO_PA06 = 6; -const int GPIO_PA07 = 7; -const int GPIO_PA08 = 8; -const int GPIO_PA09 = 9; -const int GPIO_PA10 = 10; -const int GPIO_PA11 = 11; -const int GPIO_PA12 = 12; -const int GPIO_PA13 = 13; -const int GPIO_PA14 = 14; -const int GPIO_PA15 = 15; -const int GPIO_PA16 = 16; -const int GPIO_PA17 = 17; -const int GPIO_PA18 = 18; -const int GPIO_PA19 = 19; -const int GPIO_PA20 = 20; -const int GPIO_PA21 = 21; -const int GPIO_PA22 = 22; -const int GPIO_PA23 = 23; -const int GPIO_PA24 = 24; -const int GPIO_PA25 = 25; -const int GPIO_PA26 = 26; -const int GPIO_PA27 = 27; -const int GPIO_PA28 = 28; -const int GPIO_PA29 = 29; -const int GPIO_PA30 = 30; -const int GPIO_PA31 = 31; - -const int GPIO_PB00 = 32; -const int GPIO_PB01 = 1 + GPIO_PB00; -const int GPIO_PB02 = 2 + GPIO_PB00; -const int GPIO_PB03 = 3 + GPIO_PB00; -const int GPIO_PB04 = 4 + GPIO_PB00; -const int GPIO_PB05 = 5 + GPIO_PB00; -const int GPIO_PB06 = 6 + GPIO_PB00; -const int GPIO_PB07 = 7 + GPIO_PB00; -const int GPIO_PB08 = 8 + GPIO_PB00; -const int GPIO_PB09 = 9 + GPIO_PB00; -const int GPIO_PB10 = 10 + GPIO_PB00; -const int GPIO_PB11 = 11 + GPIO_PB00; -const int GPIO_PB12 = 12 + GPIO_PB00; -const int GPIO_PB13 = 13 + GPIO_PB00; -const int GPIO_PB14 = 14 + GPIO_PB00; -const int GPIO_PB15 = 15 + GPIO_PB00; -const int GPIO_PB16 = 16 + GPIO_PB00; -const int GPIO_PB17 = 17 + GPIO_PB00; -const int GPIO_PB18 = 18 + GPIO_PB00; -const int GPIO_PB19 = 19 + GPIO_PB00; -const int GPIO_PB20 = 20 + GPIO_PB00; -const int GPIO_PB21 = 21 + GPIO_PB00; -const int GPIO_PB22 = 22 + GPIO_PB00; -const int GPIO_PB23 = 23 + GPIO_PB00; -const int GPIO_PB24 = 24 + GPIO_PB00; -const int GPIO_PB25 = 25 + GPIO_PB00; -const int GPIO_PB26 = 26 + GPIO_PB00; -const int GPIO_PB27 = 27 + GPIO_PB00; -const int GPIO_PB28 = 28 + GPIO_PB00; -const int GPIO_PB29 = 29 + GPIO_PB00; -const int GPIO_PB30 = 30 + GPIO_PB00; -const int GPIO_PB31 = 31 + GPIO_PB00; - -const int GPIO_PC00 = 64; -const int GPIO_PC01 = 1 + GPIO_PC00; -const int GPIO_PC02 = 2 + GPIO_PC00; -const int GPIO_PC03 = 3 + GPIO_PC00; -const int GPIO_PC04 = 4 + GPIO_PC00; -const int GPIO_PC05 = 5 + GPIO_PC00; -const int GPIO_PC06 = 6 + GPIO_PC00; -const int GPIO_PC07 = 7 + GPIO_PC00; -const int GPIO_PC08 = 8 + GPIO_PC00; -const int GPIO_PC09 = 9 + GPIO_PC00; -const int GPIO_PC10 = 10 + GPIO_PC00; -const int GPIO_PC11 = 11 + GPIO_PC00; -const int GPIO_PC12 = 12 + GPIO_PC00; -const int GPIO_PC13 = 13 + GPIO_PC00; -const int GPIO_PC14 = 14 + GPIO_PC00; -const int GPIO_PC15 = 15 + GPIO_PC00; -const int GPIO_PC16 = 16 + GPIO_PC00; -const int GPIO_PC17 = 17 + GPIO_PC00; -const int GPIO_PC18 = 18 + GPIO_PC00; -const int GPIO_PC19 = 19 + GPIO_PC00; -const int GPIO_PC20 = 20 + GPIO_PC00; -const int GPIO_PC21 = 21 + GPIO_PC00; -const int GPIO_PC22 = 22 + GPIO_PC00; -const int GPIO_PC23 = 23 + GPIO_PC00; -const int GPIO_PC24 = 24 + GPIO_PC00; -const int GPIO_PC25 = 25 + GPIO_PC00; -const int GPIO_PC26 = 26 + GPIO_PC00; -const int GPIO_PC27 = 27 + GPIO_PC00; -const int GPIO_PC28 = 28 + GPIO_PC00; -const int GPIO_PC29 = 29 + GPIO_PC00; -const int GPIO_PC30 = 30 + GPIO_PC00; -const int GPIO_PC31 = 31 + GPIO_PC00; - -const int GPIO_PD00 = 96; -const int GPIO_PD01 = 1 + GPIO_PD00; -const int GPIO_PD02 = 2 + GPIO_PD00; -const int GPIO_PD03 = 3 + GPIO_PD00; -const int GPIO_PD04 = 4 + GPIO_PD00; -const int GPIO_PD05 = 5 + GPIO_PD00; -const int GPIO_PD06 = 6 + GPIO_PD00; -const int GPIO_PD07 = 7 + GPIO_PD00; -const int GPIO_PD08 = 8 + GPIO_PD00; -const int GPIO_PD09 = 9 + GPIO_PD00; -const int GPIO_PD10 = 10 + GPIO_PD00; -const int GPIO_PD11 = 11 + GPIO_PD00; -const int GPIO_PD12 = 12 + GPIO_PD00; -const int GPIO_PD13 = 13 + GPIO_PD00; -const int GPIO_PD14 = 14 + GPIO_PD00; -const int GPIO_PD15 = 15 + GPIO_PD00; -const int GPIO_PD16 = 16 + GPIO_PD00; -const int GPIO_PD17 = 17 + GPIO_PD00; -const int GPIO_PD18 = 18 + GPIO_PD00; -const int GPIO_PD19 = 19 + GPIO_PD00; -const int GPIO_PD20 = 20 + GPIO_PD00; -const int GPIO_PD21 = 21 + GPIO_PD00; -const int GPIO_PD22 = 22 + GPIO_PD00; -const int GPIO_PD23 = 23 + GPIO_PD00; -const int GPIO_PD24 = 24 + GPIO_PD00; -const int GPIO_PD25 = 25 + GPIO_PD00; -const int GPIO_PD26 = 26 + GPIO_PD00; -const int GPIO_PD27 = 27 + GPIO_PD00; -const int GPIO_PD28 = 28 + GPIO_PD00; -const int GPIO_PD29 = 29 + GPIO_PD00; -const int GPIO_PD30 = 30 + GPIO_PD00; -const int GPIO_PD31 = 31 + GPIO_PD00; - -const int GPIO_PE00 = 128; -const int GPIO_PE01 = 1 + GPIO_PE00; -const int GPIO_PE02 = 2 + GPIO_PE00; -const int GPIO_PE03 = 3 + GPIO_PE00; -const int GPIO_PE04 = 4 + GPIO_PE00; -const int GPIO_PE05 = 5 + GPIO_PE00; -const int GPIO_PE06 = 6 + GPIO_PE00; -const int GPIO_PE07 = 7 + GPIO_PE00; -const int GPIO_PE08 = 8 + GPIO_PE00; -const int GPIO_PE09 = 9 + GPIO_PE00; -const int GPIO_PE10 = 10 + GPIO_PE00; -const int GPIO_PE11 = 11 + GPIO_PE00; -const int GPIO_PE12 = 12 + GPIO_PE00; -const int GPIO_PE13 = 13 + GPIO_PE00; -const int GPIO_PE14 = 14 + GPIO_PE00; -const int GPIO_PE15 = 15 + GPIO_PE00; -const int GPIO_PE16 = 16 + GPIO_PE00; -const int GPIO_PE17 = 17 + GPIO_PE00; -const int GPIO_PE18 = 18 + GPIO_PE00; -const int GPIO_PE19 = 19 + GPIO_PE00; -const int GPIO_PE20 = 20 + GPIO_PE00; -const int GPIO_PE21 = 21 + GPIO_PE00; -const int GPIO_PE22 = 22 + GPIO_PE00; -const int GPIO_PE23 = 23 + GPIO_PE00; -const int GPIO_PE24 = 24 + GPIO_PE00; -const int GPIO_PE25 = 25 + GPIO_PE00; -const int GPIO_PE26 = 26 + GPIO_PE00; -const int GPIO_PE27 = 27 + GPIO_PE00; -const int GPIO_PE28 = 28 + GPIO_PE00; -const int GPIO_PE29 = 29 + GPIO_PE00; -const int GPIO_PE30 = 30 + GPIO_PE00; -const int GPIO_PE31 = 31 + GPIO_PE00; - -const int GPIO_PG00 = 192; -const int GPIO_PG01 = 1 + GPIO_PG00; -const int GPIO_PG02 = 2 + GPIO_PG00; -const int GPIO_PG03 = 3 + GPIO_PG00; -const int GPIO_PG04 = 4 + GPIO_PG00; -const int GPIO_PG05 = 5 + GPIO_PG00; -const int GPIO_PG06 = 6 + GPIO_PG00; -const int GPIO_PG07 = 7 + GPIO_PG00; -const int GPIO_PG08 = 8 + GPIO_PG00; -const int GPIO_PG09 = 9 + GPIO_PG00; -const int GPIO_PG10 = 10 + GPIO_PG00; -const int GPIO_PG11 = 11 + GPIO_PG00; -const int GPIO_PG12 = 12 + GPIO_PG00; -const int GPIO_PG13 = 13 + GPIO_PG00; -const int GPIO_PG14 = 14 + GPIO_PG00; -const int GPIO_PG15 = 15 + GPIO_PG00; -const int GPIO_PG16 = 16 + GPIO_PG00; -const int GPIO_PG17 = 17 + GPIO_PG00; -const int GPIO_PG18 = 18 + GPIO_PG00; -const int GPIO_PG19 = 19 + GPIO_PG00; -const int GPIO_PG20 = 20 + GPIO_PG00; -const int GPIO_PG21 = 21 + GPIO_PG00; -const int GPIO_PG22 = 22 + GPIO_PG00; -const int GPIO_PG23 = 23 + GPIO_PG00; -const int GPIO_PG24 = 24 + GPIO_PG00; -const int GPIO_PG25 = 25 + GPIO_PG00; -const int GPIO_PG26 = 26 + GPIO_PG00; -const int GPIO_PG27 = 27 + GPIO_PG00; -const int GPIO_PG28 = 28 + GPIO_PG00; -const int GPIO_PG29 = 29 + GPIO_PG00; -const int GPIO_PG30 = 30 + GPIO_PG00; -const int GPIO_PG31 = 31 + GPIO_PG00; - -const int GPIO_PH00 = 224; -const int GPIO_PH01 = 1 + GPIO_PH00; -const int GPIO_PH02 = 2 + GPIO_PH00; -const int GPIO_PH03 = 3 + GPIO_PH00; -const int GPIO_PH04 = 4 + GPIO_PH00; -const int GPIO_PH05 = 5 + GPIO_PH00; -const int GPIO_PH06 = 6 + GPIO_PH00; -const int GPIO_PH07 = 7 + GPIO_PH00; -const int GPIO_PH08 = 8 + GPIO_PH00; -const int GPIO_PH09 = 9 + GPIO_PH00; -const int GPIO_PH10 = 10 + GPIO_PH00; -const int GPIO_PH11 = 11 + GPIO_PH00; -const int GPIO_PH12 = 12 + GPIO_PH00; -const int GPIO_PH13 = 13 + GPIO_PH00; -const int GPIO_PH14 = 14 + GPIO_PH00; -const int GPIO_PH15 = 15 + GPIO_PH00; -const int GPIO_PH16 = 16 + GPIO_PH00; -const int GPIO_PH17 = 17 + GPIO_PH00; -const int GPIO_PH18 = 18 + GPIO_PH00; -const int GPIO_PH19 = 19 + GPIO_PH00; -const int GPIO_PH20 = 20 + GPIO_PH00; -const int GPIO_PH21 = 21 + GPIO_PH00; -const int GPIO_PH22 = 22 + GPIO_PH00; -const int GPIO_PH23 = 23 + GPIO_PH00; -const int GPIO_PH24 = 24 + GPIO_PH00; -const int GPIO_PH25 = 25 + GPIO_PH00; -const int GPIO_PH26 = 26 + GPIO_PH00; -const int GPIO_PH27 = 27 + GPIO_PH00; -const int GPIO_PH28 = 28 + GPIO_PH00; -const int GPIO_PH29 = 29 + GPIO_PH00; -const int GPIO_PH30 = 30 + GPIO_PH00; -const int GPIO_PH31 = 31 + GPIO_PH00; - -const int GPIO_PI00 = 256; -const int GPIO_PI01 = 1 + GPIO_PI00; -const int GPIO_PI02 = 2 + GPIO_PI00; -const int GPIO_PI03 = 3 + GPIO_PI00; -const int GPIO_PI04 = 4 + GPIO_PI00; -const int GPIO_PI05 = 5 + GPIO_PI00; -const int GPIO_PI06 = 6 + GPIO_PI00; -const int GPIO_PI07 = 7 + GPIO_PI00; -const int GPIO_PI08 = 8 + GPIO_PI00; -const int GPIO_PI09 = 9 + GPIO_PI00; -const int GPIO_PI10 = 10 + GPIO_PI00; -const int GPIO_PI11 = 11 + GPIO_PI00; -const int GPIO_PI12 = 12 + GPIO_PI00; -const int GPIO_PI13 = 13 + GPIO_PI00; -const int GPIO_PI14 = 14 + GPIO_PI00; -const int GPIO_PI15 = 15 + GPIO_PI00; -const int GPIO_PI16 = 16 + GPIO_PI00; -const int GPIO_PI17 = 17 + GPIO_PI00; -const int GPIO_PI18 = 18 + GPIO_PI00; -const int GPIO_PI19 = 19 + GPIO_PI00; -const int GPIO_PI20 = 20 + GPIO_PI00; -const int GPIO_PI21 = 21 + GPIO_PI00; -const int GPIO_PI22 = 22 + GPIO_PI00; -const int GPIO_PI23 = 23 + GPIO_PI00; -const int GPIO_PI24 = 24 + GPIO_PI00; -const int GPIO_PI25 = 25 + GPIO_PI00; -const int GPIO_PI26 = 26 + GPIO_PI00; -const int GPIO_PI27 = 27 + GPIO_PI00; -const int GPIO_PI28 = 28 + GPIO_PI00; -const int GPIO_PI29 = 29 + GPIO_PI00; -const int GPIO_PI30 = 30 + GPIO_PI00; -const int GPIO_PI31 = 31 + GPIO_PI00; - -const int GPIO_PL00 = 352; -const int GPIO_PL01 = 1 + GPIO_PL00; -const int GPIO_PL02 = 2 + GPIO_PL00; -const int GPIO_PL03 = 3 + GPIO_PL00; -const int GPIO_PL04 = 4 + GPIO_PL00; -const int GPIO_PL05 = 5 + GPIO_PL00; -const int GPIO_PL06 = 6 + GPIO_PL00; -const int GPIO_PL07 = 7 + GPIO_PL00; -const int GPIO_PL08 = 8 + GPIO_PL00; -const int GPIO_PL09 = 9 + GPIO_PL00; -const int GPIO_PL10 = 10 + GPIO_PL00; -const int GPIO_PL11 = 11 + GPIO_PL00; -const int GPIO_PL12 = 12 + GPIO_PL00; -const int GPIO_PL13 = 13 + GPIO_PL00; -const int GPIO_PL14 = 14 + GPIO_PL00; -const int GPIO_PL15 = 15 + GPIO_PL00; -const int GPIO_PL16 = 16 + GPIO_PL00; -const int GPIO_PL17 = 17 + GPIO_PL00; -const int GPIO_PL18 = 18 + GPIO_PL00; -const int GPIO_PL19 = 19 + GPIO_PL00; -const int GPIO_PL20 = 20 + GPIO_PL00; -const int GPIO_PL21 = 21 + GPIO_PL00; -const int GPIO_PL22 = 22 + GPIO_PL00; -const int GPIO_PL23 = 23 + GPIO_PL00; -const int GPIO_PL24 = 24 + GPIO_PL00; -const int GPIO_PL25 = 25 + GPIO_PL00; -const int GPIO_PL26 = 26 + GPIO_PL00; -const int GPIO_PL27 = 27 + GPIO_PL00; -const int GPIO_PL28 = 28 + GPIO_PL00; -const int GPIO_PL29 = 29 + GPIO_PL00; -const int GPIO_PL30 = 30 + GPIO_PL00; -const int GPIO_PL31 = 31 + GPIO_PL00; - -const int GPIO_PM00 = 384; -const int GPIO_PM01 = 1 + GPIO_PM00; -const int GPIO_PM02 = 2 + GPIO_PM00; -const int GPIO_PM03 = 3 + GPIO_PM00; -const int GPIO_PM04 = 4 + GPIO_PM00; -const int GPIO_PM05 = 5 + GPIO_PM00; -const int GPIO_PM06 = 6 + GPIO_PM00; -const int GPIO_PM07 = 7 + GPIO_PM00; -const int GPIO_PM08 = 8 + GPIO_PM00; -const int GPIO_PM09 = 9 + GPIO_PM00; -const int GPIO_PM10 = 10 + GPIO_PM00; -const int GPIO_PM11 = 11 + GPIO_PM00; -const int GPIO_PM12 = 12 + GPIO_PM00; -const int GPIO_PM13 = 13 + GPIO_PM00; -const int GPIO_PM14 = 14 + GPIO_PM00; -const int GPIO_PM15 = 15 + GPIO_PM00; -const int GPIO_PM16 = 16 + GPIO_PM00; -const int GPIO_PM17 = 17 + GPIO_PM00; -const int GPIO_PM18 = 18 + GPIO_PM00; -const int GPIO_PM19 = 19 + GPIO_PM00; -const int GPIO_PM20 = 20 + GPIO_PM00; -const int GPIO_PM21 = 21 + GPIO_PM00; -const int GPIO_PM22 = 22 + GPIO_PM00; -const int GPIO_PM23 = 23 + GPIO_PM00; -const int GPIO_PM24 = 24 + GPIO_PM00; -const int GPIO_PM25 = 25 + GPIO_PM00; -const int GPIO_PM26 = 26 + GPIO_PM00; -const int GPIO_PM27 = 27 + GPIO_PM00; -const int GPIO_PM28 = 28 + GPIO_PM00; -const int GPIO_PM29 = 29 + GPIO_PM00; -const int GPIO_PM30 = 30 + GPIO_PM00; -const int GPIO_PM31 = 31 + GPIO_PM00; diff --git a/cpp/hal/pi_defs/bpi-m2p.h b/cpp/hal/pi_defs/bpi-m2p.h deleted file mode 100755 index 096fd260..00000000 --- a/cpp/hal/pi_defs/bpi-m2p.h +++ /dev/null @@ -1,71 +0,0 @@ -/* -Copyright (c) 2014-2017 Banana Pi -Updates Copyright (C) 2022 akuker - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -#pragma once - -#include "hal/pi_defs/bpi-gpio.h" - -/* The following define the mapping of the Banana Pi CPU pins to the logical - * GPIO numbers. The GPIO numbers are used by the software to set/configure - * the CPU registers. */ -const int BPI_M2P_01 = GPIO_NOT_USED; -const int BPI_M2P_03 = GPIO_PA12; -const int BPI_M2P_05 = GPIO_PA11; -const int BPI_M2P_07 = GPIO_PA06; -const int BPI_M2P_09 = GPIO_NOT_USED; -const int BPI_M2P_11 = GPIO_PA01; -const int BPI_M2P_13 = GPIO_PA00; -const int BPI_M2P_15 = GPIO_PA03; -const int BPI_M2P_17 = GPIO_NOT_USED; -const int BPI_M2P_19 = GPIO_PC00; -const int BPI_M2P_21 = GPIO_PC01; -const int BPI_M2P_23 = GPIO_PC02; -const int BPI_M2P_25 = GPIO_NOT_USED; -const int BPI_M2P_27 = GPIO_PA19; -const int BPI_M2P_29 = GPIO_PA07; -const int BPI_M2P_31 = GPIO_PA08; -const int BPI_M2P_33 = GPIO_PA09; -const int BPI_M2P_35 = GPIO_PA10; -const int BPI_M2P_37 = GPIO_PA17; -const int BPI_M2P_39 = GPIO_NOT_USED; - -const int BPI_M2P_02 = GPIO_NOT_USED; -const int BPI_M2P_04 = GPIO_NOT_USED; -const int BPI_M2P_06 = GPIO_NOT_USED; -const int BPI_M2P_08 = GPIO_PA13; -const int BPI_M2P_10 = GPIO_PA14; -const int BPI_M2P_12 = GPIO_PA16; -const int BPI_M2P_14 = GPIO_NOT_USED; -const int BPI_M2P_16 = GPIO_PA15; -const int BPI_M2P_18 = GPIO_PC04; -const int BPI_M2P_20 = GPIO_NOT_USED; -const int BPI_M2P_22 = GPIO_PA02; -const int BPI_M2P_24 = GPIO_PC03; -const int BPI_M2P_26 = GPIO_PC07; -const int BPI_M2P_28 = GPIO_PA18; -const int BPI_M2P_30 = GPIO_NOT_USED; -const int BPI_M2P_32 = GPIO_PL02; -const int BPI_M2P_34 = GPIO_NOT_USED; -const int BPI_M2P_36 = GPIO_PL04; -const int BPI_M2P_38 = GPIO_PA21; -const int BPI_M2P_40 = GPIO_PA20; diff --git a/cpp/hal/sbc_version.cpp b/cpp/hal/sbc_version.cpp index 99971b41..caebbedf 100644 --- a/cpp/hal/sbc_version.cpp +++ b/cpp/hal/sbc_version.cpp @@ -10,26 +10,18 @@ //--------------------------------------------------------------------------- #include "sbc_version.h" -#include "log.h" +#include #include #include #include -SBC_Version::sbc_version_type SBC_Version::m_sbc_version = sbc_version_type::sbc_unknown; +SBC_Version::sbc_version_type SBC_Version::sbc_version = sbc_version_type::sbc_unknown; // TODO: THESE NEED TO BE VALIDATED!!!! -const std::string SBC_Version::m_str_raspberry_pi_1 = "Raspberry Pi 1"; -const std::string SBC_Version::m_str_raspberry_pi_2_3 = "Raspberry Pi 2/3"; -const std::string SBC_Version::m_str_raspberry_pi_4 = "Raspberry Pi 4"; -const std::string SBC_Version::m_str_bananapi_m1_plus = "Banana Pi M1 Plus"; -const std::string SBC_Version::m_str_bananapi_m2_berry = "Banana Pi M2 Berry/Ultra"; -const std::string SBC_Version::m_str_bananapi_m2_zero = "Banana Pi M2 Zero"; -const std::string SBC_Version::m_str_bananapi_m2_plus = "Banana Pi BPI-M2-Plus H3"; -const std::string SBC_Version::m_str_bananapi_m3 = "Banana Pi M3"; -const std::string SBC_Version::m_str_bananapi_m4 = "Banana Pi M4"; -const std::string SBC_Version::m_str_bananapi_m5 = "Banana Pi M5"; -const std::string SBC_Version::m_str_bananapi_m64 = "Banana Pi M64"; -const std::string SBC_Version::m_str_unknown_sbc = "Unknown SBC"; +const string SBC_Version::str_raspberry_pi_1 = "Raspberry Pi 1"; +const string SBC_Version::str_raspberry_pi_2_3 = "Raspberry Pi 2/3"; +const string SBC_Version::str_raspberry_pi_4 = "Raspberry Pi 4"; +const string SBC_Version::str_unknown_sbc = "Unknown SBC"; // The strings in this table should align with the 'model' embedded // in the device tree. This can be aquired by running: @@ -39,57 +31,41 @@ const std::string SBC_Version::m_str_unknown_sbc = "Unknown SBC"; // "Raspberry Pi 4 Model B" will match with both of the following: // - Raspberry Pi 4 Model B Rev 1.4 // - Raspberry Pi 4 Model B Rev 1.3 -const std::map> SBC_Version::m_proc_device_tree_mapping = { +// TODO Is there a better way to detect the Pi type than relying on strings? +const map> SBC_Version::proc_device_tree_mapping = { {"Raspberry Pi 1 Model ", sbc_version_type::sbc_raspberry_pi_1}, {"Raspberry Pi 2 Model ", sbc_version_type::sbc_raspberry_pi_2_3}, {"Raspberry Pi 3 Model ", sbc_version_type::sbc_raspberry_pi_2_3}, {"Raspberry Pi 4 Model ", sbc_version_type::sbc_raspberry_pi_4}, {"Raspberry Pi 400 ", sbc_version_type::sbc_raspberry_pi_4}, {"Raspberry Pi Zero W", sbc_version_type::sbc_raspberry_pi_1}, - {"Raspberry Pi Zero", sbc_version_type::sbc_raspberry_pi_1}, - {"Banana Pi BPI-M2-Zero ", sbc_version_type::sbc_bananapi_m2_zero}, - {"Banana Pi BPI-M2-Ultra ", sbc_version_type::sbc_bananapi_m2_berry}, - {"Banana Pi BPI-M2-Plus H3", sbc_version_type::sbc_bananapi_m2_plus}, - {"Banana Pi M2 Berry ", sbc_version_type::sbc_bananapi_m2_berry}, - // sbc_bananapi_m3, TBD.... - // sbc_bananapi_m4, + {"Raspberry Pi Zero", sbc_version_type::sbc_raspberry_pi_1} }; -const std::string SBC_Version::m_device_tree_model_path = "/proc/device-tree/model"; +const string SBC_Version::m_device_tree_model_path = "/proc/device-tree/model"; //--------------------------------------------------------------------------- // // Convert the SBC Version to a printable string // //--------------------------------------------------------------------------- -const std::string *SBC_Version::GetString() +string SBC_Version::GetAsString() { - switch (m_sbc_version) { + switch (sbc_version) { case sbc_version_type::sbc_raspberry_pi_1: - return &m_str_raspberry_pi_1; + return str_raspberry_pi_1; case sbc_version_type::sbc_raspberry_pi_2_3: - return &m_str_raspberry_pi_2_3; + return str_raspberry_pi_2_3; case sbc_version_type::sbc_raspberry_pi_4: - return &m_str_raspberry_pi_4; - case sbc_version_type::sbc_bananapi_m2_berry: - return &m_str_bananapi_m2_berry; - case sbc_version_type::sbc_bananapi_m2_zero: - return &m_str_bananapi_m2_zero; - case sbc_version_type::sbc_bananapi_m2_plus: - return &m_str_bananapi_m2_plus; - case sbc_version_type::sbc_bananapi_m3: - return &m_str_bananapi_m3; - case sbc_version_type::sbc_bananapi_m4: - return &m_str_bananapi_m4; + return str_raspberry_pi_4; default: - LOGERROR("Unknown type of sbc detected: %d", static_cast(m_sbc_version)) - return &m_str_unknown_sbc; + return str_unknown_sbc; } } SBC_Version::sbc_version_type SBC_Version::GetSbcVersion() { - return m_sbc_version; + return sbc_version; } //--------------------------------------------------------------------------- @@ -100,71 +76,43 @@ SBC_Version::sbc_version_type SBC_Version::GetSbcVersion() //--------------------------------------------------------------------------- void SBC_Version::Init() { - LOGTRACE("%s", __PRETTY_FUNCTION__) - std::string device_tree_model; - - const std::ifstream input_stream(SBC_Version::m_device_tree_model_path); + ifstream input_stream(SBC_Version::m_device_tree_model_path); if (input_stream.fail()) { #if defined(__x86_64__) || defined(__X86__) // We expect this to fail on x86 - LOGINFO("Detected device %s", GetString()->c_str()) - m_sbc_version = sbc_version_type::sbc_unknown; + spdlog::warn("Detected " + GetAsString()); + sbc_version = sbc_version_type::sbc_unknown; return; #else - LOGERROR("Failed to open %s. Are you running as root?", SBC_Version::m_device_tree_model_path.c_str()) - throw std::invalid_argument("Failed to open /proc/device-tree/model"); + spdlog::error("Failed to open " + SBC_Version::m_device_tree_model_path + ". Are you running as root?"); + throw invalid_argument("Failed to open /proc/device-tree/model"); #endif } - std::stringstream str_buffer; + stringstream str_buffer; str_buffer << input_stream.rdbuf(); - device_tree_model = str_buffer.str(); + const string device_tree_model = str_buffer.str(); - for (const auto &[key, value] : m_proc_device_tree_mapping) { - if (device_tree_model.rfind(key, 0) == 0) { - m_sbc_version = value; - LOGINFO("Detected device %s", GetString()->c_str()) - return; - } + for (const auto& [key, value] : proc_device_tree_mapping) { + if (device_tree_model.starts_with(key)) { + sbc_version = value; + spdlog::info("Detected " + GetAsString()); + return; + } } - LOGERROR("%s Unable to determine single board computer type. Defaulting to Raspberry Pi 4", __PRETTY_FUNCTION__) - m_sbc_version = sbc_version_type::sbc_raspberry_pi_4; + + sbc_version = sbc_version_type::sbc_raspberry_pi_4; + spdlog::error("Unable to determine single board computer type. Defaulting to Raspberry Pi 4"); } bool SBC_Version::IsRaspberryPi() { - LOGTRACE("%s", __PRETTY_FUNCTION__) - switch (m_sbc_version) { + switch (sbc_version) { case sbc_version_type::sbc_raspberry_pi_1: case sbc_version_type::sbc_raspberry_pi_2_3: case sbc_version_type::sbc_raspberry_pi_4: return true; - case sbc_version_type::sbc_bananapi_m2_berry: - case sbc_version_type::sbc_bananapi_m2_zero: - case sbc_version_type::sbc_bananapi_m2_plus: - case sbc_version_type::sbc_bananapi_m3: - case sbc_version_type::sbc_bananapi_m4: - return false; - default: - return false; - } -} - -bool SBC_Version::IsBananaPi() -{ - LOGTRACE("%s", __PRETTY_FUNCTION__) - switch (m_sbc_version) { - case sbc_version_type::sbc_raspberry_pi_1: - case sbc_version_type::sbc_raspberry_pi_2_3: - case sbc_version_type::sbc_raspberry_pi_4: - return false; - case sbc_version_type::sbc_bananapi_m2_berry: - case sbc_version_type::sbc_bananapi_m2_zero: - case sbc_version_type::sbc_bananapi_m2_plus: - case sbc_version_type::sbc_bananapi_m3: - case sbc_version_type::sbc_bananapi_m4: - return true; default: return false; } @@ -174,11 +122,10 @@ bool SBC_Version::IsBananaPi() // (imported from bcm_host.c) uint32_t SBC_Version::GetDeviceTreeRanges(const char *filename, uint32_t offset) { - LOGTRACE("%s", __PRETTY_FUNCTION__) uint32_t address = ~0; if (FILE *fp = fopen(filename, "rb"); fp) { fseek(fp, offset, SEEK_SET); - if (std::array buf; fread(buf.data(), 1, buf.size(), fp) == buf.size()) { + if (array buf; fread(buf.data(), 1, buf.size(), fp) == buf.size()) { address = (int)buf[0] << 24 | (int)buf[1] << 16 | (int)buf[2] << 8 | (int)buf[3] << 0; } fclose(fp); @@ -189,15 +136,12 @@ uint32_t SBC_Version::GetDeviceTreeRanges(const char *filename, uint32_t offset) #if defined __linux__ uint32_t SBC_Version::GetPeripheralAddress(void) { - LOGTRACE("%s", __PRETTY_FUNCTION__) uint32_t address = GetDeviceTreeRanges("/proc/device-tree/soc/ranges", 4); if (address == 0) { address = GetDeviceTreeRanges("/proc/device-tree/soc/ranges", 8); } address = (address == (uint32_t)~0) ? 0x20000000 : address; - LOGDEBUG("Peripheral address : 0x%8x\n", address) - return address; } #elif defined __NetBSD__ @@ -215,7 +159,6 @@ uint32_t SBC_Version::GetPeripheralAddress(void) // Use BCM2835 address address = 0x20000000; } - LOGDEBUG("Peripheral address : 0x%lx\n", address); return address; } #else diff --git a/cpp/hal/sbc_version.h b/cpp/hal/sbc_version.h index 36922b5a..c51100e4 100644 --- a/cpp/hal/sbc_version.h +++ b/cpp/hal/sbc_version.h @@ -15,6 +15,8 @@ #include #include +using namespace std; + //=========================================================================== // // Single Board Computer Versions @@ -28,16 +30,7 @@ class SBC_Version sbc_unknown = 0, sbc_raspberry_pi_1, sbc_raspberry_pi_2_3, - sbc_raspberry_pi_4, - sbc_bananapi_m1_plus, - sbc_bananapi_m2_ultra, - sbc_bananapi_m2_berry, - sbc_bananapi_m2_zero, - sbc_bananapi_m2_plus, - sbc_bananapi_m3, - sbc_bananapi_m4, - sbc_bananapi_m5, - sbc_bananapi_m64, + sbc_raspberry_pi_4 }; SBC_Version() = delete; @@ -48,32 +41,22 @@ class SBC_Version static sbc_version_type GetSbcVersion(); static bool IsRaspberryPi(); - static bool IsBananaPi(); - static const std::string *GetString(); + static string GetAsString(); static uint32_t GetPeripheralAddress(); private: - static sbc_version_type m_sbc_version; + static sbc_version_type sbc_version; - static const std::string m_str_raspberry_pi_1; - static const std::string m_str_raspberry_pi_2_3; - static const std::string m_str_raspberry_pi_4; - static const std::string m_str_bananapi_m1_plus; - static const std::string m_str_bananapi_m2_ultra; - static const std::string m_str_bananapi_m2_berry; - static const std::string m_str_bananapi_m2_zero; - static const std::string m_str_bananapi_m2_plus; - static const std::string m_str_bananapi_m3; - static const std::string m_str_bananapi_m4; - static const std::string m_str_bananapi_m5; - static const std::string m_str_bananapi_m64; - static const std::string m_str_unknown_sbc; + static const string str_raspberry_pi_1; + static const string str_raspberry_pi_2_3; + static const string str_raspberry_pi_4; + static const string str_unknown_sbc; - static const std::map> m_proc_device_tree_mapping; + static const map> proc_device_tree_mapping; - static const std::string m_device_tree_model_path; + static const string m_device_tree_model_path; static uint32_t GetDeviceTreeRanges(const char *filename, uint32_t offset); }; diff --git a/cpp/hal/sunxi_utils.cpp b/cpp/hal/sunxi_utils.cpp deleted file mode 100644 index f4b150a1..00000000 --- a/cpp/hal/sunxi_utils.cpp +++ /dev/null @@ -1,59 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Copyright (C) 2022 akuker -// -// [ Utility functions for working with Allwinner CPUs ] -// -// This should include generic functions that can be applicable to -// different variants of the SunXI (Allwinner) SoCs -// -// Large portions of this functionality were derived from c_gpio.c, which -// is part of the RPI.GPIO library available here: -// https://github.com/BPI-SINOVOIP/RPi.GPIO/blob/master/source/c_gpio.c -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -// of the Software, and to permit persons to whom the Software is furnished to do -// so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// -//--------------------------------------------------------------------------- - -#include "hal/sunxi_utils.h" -#include -#include - -using namespace std; - -static const string CYAN = "\033[36m"; /* Cyan */ -static const string WHITE = "\033[37m"; /* White */ - -// TODO: this is only a debug function that will be removed at a later date..... -void dump_gpio_registers(const SunXI::sunxi_gpio_reg_t *regs) -{ - printf("%s--- GPIO BANK 0 CFG: %08X %08X %08X %08X\n", CYAN.c_str(), regs->gpio_bank[0].CFG[0], - regs->gpio_bank[0].CFG[1], regs->gpio_bank[0].CFG[2], regs->gpio_bank[0].CFG[3]); - - printf("--- Dat: (%08X) DRV: %08X %08X\n", regs->gpio_bank[0].DAT, regs->gpio_bank[0].DRV[0], - regs->gpio_bank[0].DRV[1]); - printf("--- Pull: %08X %08x\n", regs->gpio_bank[0].PULL[0], regs->gpio_bank[0].PULL[1]); - - printf("--- GPIO INT CFG: %08X %08X %08X\n", regs->gpio_int.CFG[0], regs->gpio_int.CFG[1], regs->gpio_int.CFG[2]); - printf("--- CTL: (%08X) STA: %08X DEB: %08X\n %s", regs->gpio_int.CTL, regs->gpio_int.STA, regs->gpio_int.DEB, - WHITE.c_str()); -} diff --git a/cpp/hal/sunxi_utils.h b/cpp/hal/sunxi_utils.h deleted file mode 100644 index 14e7d8f7..00000000 --- a/cpp/hal/sunxi_utils.h +++ /dev/null @@ -1,189 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Copyright (C) 2022 akuker -// -// [ Utility functions for working with Allwinner CPUs ] -// -// This should include generic functions that can be applicable to -// different variants of the SunXI (Allwinner) SoCs -// -// Large portions of this functionality were derived from c_gpio.c, which -// is part of the RPI.GPIO library available here: -// https://github.com/BPI-SINOVOIP/RPi.GPIO/blob/master/source/c_gpio.c -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -// of the Software, and to permit persons to whom the Software is furnished to do -// so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// -//--------------------------------------------------------------------------- - -#pragma once - -#include -#ifndef __arm__ -#include -#endif - -class SunXI -{ - public: - static inline int GPIO_BANK(int pin) - { - return (pin >> 5); - } - static inline int GPIO_NUM(int pin) - { - return (pin & 0x1F); - } - static inline int GPIO_CFG_INDEX(int pin) - { - return ((pin & 0x1F) >> 3); - } - static inline int GPIO_CFG_OFFSET(int pin) - { - return (((pin & 0x1F) & 0x7) << 2); - } - static inline int GPIO_PUL_INDEX(int pin) - { - return ((pin & 0x1F) >> 4); - } - static inline int GPIO_PUL_OFFSET(int pin) - { - return ((pin & 0x0F) << 1); - } - static inline int GPIO_DRV_INDEX(int pin) - { - return ((pin & 0x1F) >> 4); - } - static inline int GPIO_DRV_OFFSET(int pin) - { - return ((pin & 0x0F) << 1); - } - - static inline void short_wait(void) - { - for (int i = 0; i < 150; i++) { -#ifndef __arm__ - // Timing doesn't really matter if we're not on ARM. - // The SunXI SoCs are all ARM-based. - const timespec ts = {.tv_sec = 0, .tv_nsec = 1}; - nanosleep(&ts, nullptr); -#else - // wait 150 cycles - asm volatile("nop"); -#endif - } - } - - enum class gpio_configure_values_e : uint8_t { - gpio_input = 0b000, - gpio_output = 0b001, - gpio_alt_func_1 = 0b010, - gpio_alt_func_2 = 0b011, - gpio_reserved_1 = 0b100, - gpio_reserved_2 = 0b101, - gpio_interupt = 0b110, - gpio_disable = 0b111 - }; - - struct sunxi_gpio { - unsigned int CFG[4]; // NOSONAR: Intentionally using C style arrays for low level register access - unsigned int DAT; - unsigned int DRV[2]; // NOSONAR: Intentionally using C style arrays for low level register access - unsigned int PULL[2]; // NOSONAR: Intentionally using C style arrays for low level register access - }; - using sunxi_gpio_t = struct sunxi_gpio; - - /* gpio interrupt control */ - struct sunxi_gpio_int { - unsigned int CFG[3]; // NOSONAR: Intentionally using C style arrays for low level register access - unsigned int CTL; - unsigned int STA; - unsigned int DEB; - }; - using sunxi_gpio_int_t = struct sunxi_gpio_int; - - struct sunxi_gpio_reg { - struct sunxi_gpio gpio_bank[9]; // NOSONAR: Intentionally using C style arrays for low level register access - unsigned char res[0xbc]; // NOSONAR: Intentionally using C style arrays for low level register access - struct sunxi_gpio_int gpio_int; - }; - using sunxi_gpio_reg_t = struct sunxi_gpio_reg; - - static const uint32_t PAGE_SIZE = (4 * 1024); - static const uint32_t BLOCK_SIZE = (4 * 1024); - - static const int SETUP_DEVMEM_FAIL = 1; - static const int SETUP_MALLOC_FAIL = 2; - static const int SETUP_MMAP_FAIL = 3; - static const int SETUP_CPUINFO_FAIL = 4; - static const int SETUP_NOT_RPI_FAIL = 5; - static const int INPUT = 1; - static const int OUTPUT = 0; - static const int ALT0 = 4; - static const int HIGH = 1; - static const int LOW = 0; - static const int PUD_OFF = 0; - static const int PUD_DOWN = 1; - static const int PUD_UP = 2; - - static const uint32_t SUNXI_R_GPIO_BASE = 0x01F02000; - static const uint32_t SUNXI_R_GPIO_REG_OFFSET = 0xC00; - static const uint32_t SUNXI_GPIO_BASE = 0x01C20000; - static const uint32_t SUNXI_GPIO_REG_OFFSET = 0x800; - static const uint32_t SUNXI_CFG_OFFSET = 0x00; - static const uint32_t SUNXI_DATA_OFFSET = 0x10; - static const uint32_t SUNXI_PUD_OFFSET = 0x1C; - static const uint32_t SUNXI_BANK_SIZE = 0x24; - - static const uint32_t MAP_SIZE = (4096 * 2); - static const uint32_t MAP_MASK = (MAP_SIZE - 1); - - static const int FSEL_OFFSET = 0; // 0x0000 - static const int SET_OFFSET = 7; // 0x001c / 4 - static const int CLR_OFFSET = 10; // 0x0028 / 4 - static const int PINLEVEL_OFFSET = 13; // 0x0034 / 4 - static const int EVENT_DETECT_OFFSET = 16; // 0x0040 / 4 - static const int RISING_ED_OFFSET = 19; // 0x004c / 4 - static const int FALLING_ED_OFFSET = 22; // 0x0058 / 4 - static const int HIGH_DETECT_OFFSET = 25; // 0x0064 / 4 - static const int LOW_DETECT_OFFSET = 28; // 0x0070 / 4 - static const int PULLUPDN_OFFSET = 37; // 0x0094 / 4 - static const int PULLUPDNCLK_OFFSET = 38; // 0x0098 / 4 - - static const uint32_t TMR_REGISTER_BASE = 0x01C20C00; - static const uint32_t TMR_IRQ_EN_REG = 0x0; // T imer IRQ Enable Register - static const uint32_t TMR_IRQ_STA_REG = 0x4; // Timer Status Register - static const uint32_t TMR0_CTRL_REG = 0x10; // Timer 0 Control Register - static const uint32_t TMR0_INTV_VALUE_REG = 0x14; // Timer 0 Interval Value Register - static const uint32_t TMR0_CUR_VALUE_REG = 0x18; // Timer 0 Current Value Register - static const uint32_t TMR1_CTRL_REG = 0x20; // Timer 1 Control Register - static const uint32_t TMR1_INTV_VALUE_REG = 0x24; // Timer 1 Interval Value Register - static const uint32_t TMR1_CUR_VALUE_REG = 0x28; // Timer 1 Current Value Register - static const uint32_t AVS_CNT_CTL_REG = 0x80; // AVS Control Register - static const uint32_t AVS_CNT0_REG = 0x84; // AVS Counter 0 Register - static const uint32_t AVS_CNT1_REG = 0x88; // AVS Counter 1 Register - static const uint32_t AVS_CNT_DIV_REG = 0x8C; // AVS Divisor Register - static const uint32_t WDOG0_IRQ_EN_REG = 0xA0; // Watchdog 0 IRQ Enable Register - static const uint32_t WDOG0_IRQ_STA_REG = 0xA4; // Watchdog 0 Status Register - static const uint32_t WDOG0_CTRL_REG = 0xB0; // Watchdog 0 Control Register - static const uint32_t WDOG0_CFG_REG = 0xB4; // Watchdog 0 Configuration Register - static const uint32_t WDOG0_MODE_REG = 0xB8; // Watchdog 0 Mode Register -}; \ No newline at end of file diff --git a/cpp/hal/systimer.cpp b/cpp/hal/systimer.cpp index 21dc7b22..c2b4e7b8 100644 --- a/cpp/hal/systimer.cpp +++ b/cpp/hal/systimer.cpp @@ -12,31 +12,25 @@ //--------------------------------------------------------------------------- #include "hal/systimer.h" -#include "hal/systimer_allwinner.h" #include "hal/systimer_raspberry.h" #include -#include #include "hal/gpiobus.h" #include "hal/sbc_version.h" -bool SysTimer::initialized = false; -bool SysTimer::is_allwinnner = false; -bool SysTimer::is_raspberry = false; +bool SysTimer::initialized = false; +bool SysTimer::is_raspberry = false; -std::unique_ptr SysTimer::systimer_ptr; +using namespace std; + +unique_ptr SysTimer::systimer_ptr; void SysTimer::Init() { - spdlog::trace(__PRETTY_FUNCTION__); - if (!initialized) { if (SBC_Version::IsRaspberryPi()) { systimer_ptr = make_unique(); is_raspberry = true; - } else if (SBC_Version::IsBananaPi()) { - systimer_ptr = make_unique(); - is_allwinnner = true; } systimer_ptr->Init(); initialized = true; @@ -48,16 +42,19 @@ uint32_t SysTimer::GetTimerLow() { return systimer_ptr->GetTimerLow(); } + // Get system timer high byte uint32_t SysTimer::GetTimerHigh() { return systimer_ptr->GetTimerHigh(); } + // Sleep for N nanoseconds void SysTimer::SleepNsec(uint32_t nsec) { systimer_ptr->SleepNsec(nsec); } + // Sleep for N microseconds void SysTimer::SleepUsec(uint32_t usec) { diff --git a/cpp/hal/systimer_allwinner.cpp b/cpp/hal/systimer_allwinner.cpp deleted file mode 100644 index 077dba57..00000000 --- a/cpp/hal/systimer_allwinner.cpp +++ /dev/null @@ -1,155 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Copyright (C) 2022 akuker -// -// [ High resolution timer for the Allwinner series of SoC's] -// -//--------------------------------------------------------------------------- - -#include "hal/systimer_allwinner.h" -#include - -#include "hal/gpiobus.h" - -#include "hal/log.h" - -const std::string SysTimer_AllWinner::dev_mem_filename = "/dev/mem"; - -//--------------------------------------------------------------------------- -// -// Initialize the system timer -// -//--------------------------------------------------------------------------- -void SysTimer_AllWinner::Init() -{ - LOGTRACE("%s", __PRETTY_FUNCTION__) - - int fd; - - if ((fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) { - LOGERROR("I can't open /dev/mem. Are you running as root?") - exit(-1); - } - - hsitimer_regs = - (sun8i_hsitimer_registers *)mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, hs_timer_base_address); - - if (hsitimer_regs == MAP_FAILED) { - LOGERROR("Unable to map high speed timer registers. Are you running as root?") - } - - sysbus_regs = (struct sun8i_sysbus_registers *)mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, - system_bus_base_address); - - if (sysbus_regs == MAP_FAILED) { - LOGERROR("Unable to map system bus registers. Are you running as root?") - } - - enable_hs_timer(); -} - -void SysTimer_AllWinner::enable_hs_timer() -{ - // By default, the HSTimer clock gating is masked. When it is necessary to use - // the HSTimer, its clock gating should be opened in BUS Clock Gating Register 0 - // and then de-assert the software reset in BUS Software Reset Register 0 on the - // CCU module. If it is not needed to use the HSTimer, both the gating bit and - // the software reset bit should be set 0. - - LOGTRACE("%s [Before Enable] CLK GATE: %08X SOFT RST: %08X", __PRETTY_FUNCTION__, sysbus_regs->bus_clk_gating_reg0, - sysbus_regs->bus_soft_rst_reg0) - - sysbus_regs->bus_clk_gating_reg0 = sysbus_regs->bus_clk_gating_reg0 | (1 << BUS_CLK_GATING_REG0_HSTMR); - sysbus_regs->bus_soft_rst_reg0 = sysbus_regs->bus_soft_rst_reg0 | (1 << BUS_SOFT_RST_REG0_HSTMR); - LOGTRACE("%s [After Enable] CLK GATE: %08X SOFT RST: %08X", __PRETTY_FUNCTION__, sysbus_regs->bus_clk_gating_reg0, - sysbus_regs->bus_soft_rst_reg0) - - // Set interval value to the maximum value. (its a 52 bit register) - hsitimer_regs->hs_tmr_intv_hi_reg = (1 << 20) - 1; //(0xFFFFF) - hsitimer_regs->hs_tmr_intv_lo_reg = UINT32_MAX; - - // Select prescale value of 1, continuous mode - hsitimer_regs->hs_tmr_ctrl_reg = HS_TMR_CLK_PRE_SCALE_1; - - // Set reload bit - hsitimer_regs->hs_tmr_ctrl_reg = hsitimer_regs->hs_tmr_ctrl_reg | HS_TMR_RELOAD; - - // Enable HSTimer - hsitimer_regs->hs_tmr_ctrl_reg = hsitimer_regs->hs_tmr_ctrl_reg | HS_TMR_EN; -} - -// TODO: According to the data sheet, we should turn off the HS timer when we're done with it. But, its just going to -// eat up a little extra power if we leave it running. -void SysTimer_AllWinner::disable_hs_timer() -{ - LOGTRACE("%s", __PRETTY_FUNCTION__) - - LOGINFO("[Before Disable] CLK GATE: %08X SOFT RST: %08X", sysbus_regs->bus_clk_gating_reg0, - sysbus_regs->bus_soft_rst_reg0) - - sysbus_regs->bus_clk_gating_reg0 = sysbus_regs->bus_clk_gating_reg0 & ~(1 << BUS_CLK_GATING_REG0_HSTMR); - sysbus_regs->bus_soft_rst_reg0 = sysbus_regs->bus_soft_rst_reg0 & ~(1 << BUS_SOFT_RST_REG0_HSTMR); - - LOGINFO("[After Disable] CLK GATE: %08X SOFT RST: %08X", sysbus_regs->bus_clk_gating_reg0, - sysbus_regs->bus_soft_rst_reg0) -} - -uint32_t SysTimer_AllWinner::GetTimerLow() -{ - // PiSCSI expects the timer to count UP, but the Allwinner HS timer counts - // down. So, we subtract the current timer value from UINT32_MAX - return UINT32_MAX - (hsitimer_regs->hs_tmr_curnt_lo_reg / 200); -} - -uint32_t SysTimer_AllWinner::GetTimerHigh() -{ - return (uint32_t)0; -} - -//--------------------------------------------------------------------------- -// -// Sleep in nanoseconds -// -//--------------------------------------------------------------------------- -void SysTimer_AllWinner::SleepNsec(uint32_t nsec) -{ - // If time is less than one HS timer clock tick, don't do anything - if (nsec < 20) { - return; - } - - // The HS timer receives a 200MHz clock input, which equates to - // one clock tick every 5 ns. - auto clockticks = (uint32_t)std::ceil(nsec / 5); - - uint32_t enter_time = hsitimer_regs->hs_tmr_curnt_lo_reg; - - // TODO: NEED TO HANDLE COUNTER OVERFLOW - LOGTRACE("%s entertime: %08X ns: %d clockticks: %d", __PRETTY_FUNCTION__, enter_time, nsec, clockticks) - while ((enter_time - hsitimer_regs->hs_tmr_curnt_lo_reg) < clockticks) - ; - - return; -} - -//--------------------------------------------------------------------------- -// -// Sleep in microseconds -// -//--------------------------------------------------------------------------- -void SysTimer_AllWinner::SleepUsec(uint32_t usec) -{ - LOGTRACE("%s", __PRETTY_FUNCTION__) - - // If time is 0, don't do anything - if (usec == 0) { - return; - } - - uint32_t enter_time = GetTimerLow(); - while ((GetTimerLow() - enter_time) < usec) - ; -} diff --git a/cpp/hal/systimer_allwinner.h b/cpp/hal/systimer_allwinner.h deleted file mode 100644 index 8378e1ac..00000000 --- a/cpp/hal/systimer_allwinner.h +++ /dev/null @@ -1,106 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Copyright (C) 2022 akuker -// -// [ High resolution timer ] -// -//--------------------------------------------------------------------------- -#pragma once - -#include "systimer.h" -#include -#include - -//=========================================================================== -// -// System timer -// -//=========================================================================== -class SysTimer_AllWinner : public PlatformSpecificTimer -{ - public: - // Default constructor - SysTimer_AllWinner() = default; - // Default destructor - ~SysTimer_AllWinner() override = default; - // Initialization - void Init() override; - // Get system timer low byte - uint32_t GetTimerLow() override; - // Get system timer high byte - uint32_t GetTimerHigh() override; - // Sleep for N nanoseconds - void SleepNsec(uint32_t nsec) override; - // Sleep for N microseconds - void SleepUsec(uint32_t usec) override; - - private: - void enable_hs_timer(); - void disable_hs_timer(); - - static const std::string dev_mem_filename; - - /* Reference: Allwinner H3 Datasheet, section 4.9.3 */ - static const uint32_t hs_timer_base_address = 0x01C60000; - /* Note: Currently the high speed timer is NOT in the armbian - * device tree. If it is ever added, this should be pulled - * from there */ - - using sun8i_hsitimer_registers = struct { - /* 0x00 HS Timer IRQ Enabled Register */ - uint32_t hs_tmr_irq_en_reg; - /* 0x04 HS Timer Status Register */ - uint32_t hs_tmr_irq_stat_reg; - /* 0x08 Unused */ - uint32_t unused_08; - /* 0x0C Unused */ - uint32_t unused_0C; - /* 0x10 HS Timer Control Register */ - uint32_t hs_tmr_ctrl_reg; - /* 0x14 HS Timer Interval Value Low Reg */ - uint32_t hs_tmr_intv_lo_reg; - /* 0x18 HS Timer Interval Value High Register */ - uint32_t hs_tmr_intv_hi_reg; - /* 0x1C HS Timer Current Value Low Register */ - uint32_t hs_tmr_curnt_lo_reg; - /* 0x20 HS Timer Current Value High Register */ - uint32_t hs_tmr_curnt_hi_reg; - }; - - /* Constants for the HS Timer IRQ enable Register (section 4.9.4.1) */ - static const uint32_t HS_TMR_INTERUPT_ENB = (1 << 0); - - /* Constants for the HS Timer Control Register (section 4.9.4.3) */ - static const uint32_t HS_TMR_EN = (1 << 0); - static const uint32_t HS_TMR_RELOAD = (1 << 1); - static const uint32_t HS_TMR_CLK_PRE_SCALE_1 = (0 << 4); - static const uint32_t HS_TMR_CLK_PRE_SCALE_2 = (1 << 4); - static const uint32_t HS_TMR_CLK_PRE_SCALE_4 = (2 << 4); - static const uint32_t HS_TMR_CLK_PRE_SCALE_8 = (3 << 4); - static const uint32_t HS_TMR_CLK_PRE_SCALE_16 = (4 << 4); // NOSONAR This matches the datasheet - static const uint32_t HS_TMR_MODE_SINGLE = (1 << 7); - static const uint32_t HS_TMR_TEST_MODE = (1 << 31); - - volatile sun8i_hsitimer_registers *hsitimer_regs; - - /* Reference: Allwinner H3 Datasheet, section 4.3.4 */ - static const uint32_t system_bus_base_address = 0x01C20000; - - struct sun8i_sysbus_registers { - uint32_t pad_00_5C[(0x60 / sizeof(uint32_t))]; // NOSONAR c-style array used for padding - /* 0x0060 Bus Clock Gating Register 0 */ - uint32_t bus_clk_gating_reg0; - uint32_t pad_64_2C0[((0x2C0 - 0x64) / sizeof(uint32_t))]; // NOSONAR c-style array used for padding - /* 0x2C0 Bus Software Reset Register 0 */ - uint32_t bus_soft_rst_reg0; - }; - - /* Bit associated with the HS Timer */ - static const uint32_t BUS_CLK_GATING_REG0_HSTMR = 19; - static const uint32_t BUS_SOFT_RST_REG0_HSTMR = 19; - - struct sun8i_sysbus_registers *sysbus_regs; -}; diff --git a/cpp/hal/systimer_raspberry.cpp b/cpp/hal/systimer_raspberry.cpp index 249642cb..8814ea02 100644 --- a/cpp/hal/systimer_raspberry.cpp +++ b/cpp/hal/systimer_raspberry.cpp @@ -26,6 +26,8 @@ volatile uint32_t *SysTimer_Raspberry::systaddr = nullptr; volatile uint32_t *SysTimer_Raspberry::armtaddr = nullptr; volatile uint32_t SysTimer_Raspberry::corefreq = 0; +using namespace std; + //--------------------------------------------------------------------------- // // Initialize the system timer @@ -34,7 +36,7 @@ volatile uint32_t SysTimer_Raspberry::corefreq = 0; void SysTimer_Raspberry::Init() { // Get the base address - auto baseaddr = SBC_Version::GetPeripheralAddress(); + const auto baseaddr = SBC_Version::GetPeripheralAddress(); // Open /dev/mem int mem_fd = open("/dev/mem", O_RDWR | O_SYNC); @@ -63,7 +65,7 @@ void SysTimer_Raspberry::Init() // // Clock id // 0x000000004: CORE - std::array maxclock = {32, 0, 0x00030004, 8, 0, 4, 0, 0}; + const array maxclock = {32, 0, 0x00030004, 8, 0, 4, 0, 0}; // Save the base address systaddr = (uint32_t *)map + SYST_OFFSET / sizeof(uint32_t); @@ -113,7 +115,7 @@ void SysTimer_Raspberry::SleepNsec(uint32_t nsec) } // Calculate the timer difference - uint32_t diff = corefreq * nsec / 1000; + const uint32_t diff = corefreq * nsec / 1000; // Return if the difference in time is too small if (diff == 0) { @@ -121,7 +123,7 @@ void SysTimer_Raspberry::SleepNsec(uint32_t nsec) } // Start - uint32_t start = armtaddr[ARMT_FREERUN]; + const uint32_t start = armtaddr[ARMT_FREERUN]; // Loop until timer has elapsed while ((armtaddr[ARMT_FREERUN] - start) < diff) @@ -140,7 +142,7 @@ void SysTimer_Raspberry::SleepUsec(uint32_t usec) return; } - uint32_t now = GetTimerLow(); + const uint32_t now = GetTimerLow(); while ((GetTimerLow() - now) < usec) ; } diff --git a/cpp/scsidump/scsidump_core.cpp b/cpp/scsidump/scsidump_core.cpp index 9df536ca..971d79b6 100644 --- a/cpp/scsidump/scsidump_core.cpp +++ b/cpp/scsidump/scsidump_core.cpp @@ -119,6 +119,9 @@ void ScsiDump::ParseArguments(span args) if (!error.empty()) { throw parser_exception(error); } + if (target_lun == -1) { + target_lun = 0; + } } break; case 'v': @@ -534,7 +537,7 @@ ScsiDump::inquiry_info_t ScsiDump::GetDeviceInfo() cout << divider_str << "\n"; cout << "PiSCSI board ID: " << initiator_id << "\n"; cout << divider_str << "\n" << flush; - cout << "Target device ID: " << target_id << ", LUN: " << target_lun << "\n"; + cout << "Target device is " << target_id << ":" << target_lun << "\n"; Inquiry(); diff --git a/cpp/scsiloop/scsiloop_gpio.cpp b/cpp/scsiloop/scsiloop_gpio.cpp index 8032eadf..97d53192 100644 --- a/cpp/scsiloop/scsiloop_gpio.cpp +++ b/cpp/scsiloop/scsiloop_gpio.cpp @@ -7,6 +7,7 @@ // //--------------------------------------------------------------------------- +#include #include "scsiloop/scsiloop_gpio.h" #include "hal/gpiobus_factory.h" #include "hal/sbc_version.h" @@ -17,18 +18,12 @@ #include "hal/connection_type/connection_standard.h" #elif defined CONNECT_TYPE_FULLSPEC #include "hal/connection_type/connection_fullspec.h" -#elif defined CONNECT_TYPE_AIBOM -#include "hal/connection_type/connection_aibom.h" -#elif defined CONNECT_TYPE_GAMERNIUM -#include "hal/connection_type/connection_gamernium.h" #else #error Invalid connection type or none specified #endif ScsiLoop_GPIO::ScsiLoop_GPIO() { - LOGTRACE("%s", __PRETTY_FUNCTION__); - bus = GPIOBUS_Factory::Create(BUS::mode_e::TARGET); if (bus == nullptr) { throw bus_exception("Unable to create bus"); @@ -116,91 +111,8 @@ ScsiLoop_GPIO::ScsiLoop_GPIO() local_pin_dt6 = PIN_DT6; local_pin_dt7 = PIN_DT7; local_pin_dp = PIN_DP; - - } else if (SBC_Version::GetSbcVersion() == SBC_Version::sbc_version_type::sbc_bananapi_m2_plus) { - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_DT0, .connected_pin = BPI_PIN_ACK, .dir_ctrl_pin = BPI_PIN_DTD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_DT1, .connected_pin = BPI_PIN_SEL, .dir_ctrl_pin = BPI_PIN_DTD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_DT2, .connected_pin = BPI_PIN_ATN, .dir_ctrl_pin = BPI_PIN_DTD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_DT3, .connected_pin = BPI_PIN_RST, .dir_ctrl_pin = BPI_PIN_DTD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_DT4, .connected_pin = BPI_PIN_CD, .dir_ctrl_pin = BPI_PIN_DTD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_DT5, .connected_pin = BPI_PIN_IO, .dir_ctrl_pin = BPI_PIN_DTD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_DT6, .connected_pin = BPI_PIN_MSG, .dir_ctrl_pin = BPI_PIN_DTD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_DT7, .connected_pin = BPI_PIN_REQ, .dir_ctrl_pin = BPI_PIN_DTD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_DP, .connected_pin = BPI_PIN_BSY, .dir_ctrl_pin = BPI_PIN_DTD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_ATN, .connected_pin = BPI_PIN_DT2, .dir_ctrl_pin = BPI_PIN_IND}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_RST, .connected_pin = BPI_PIN_DT3, .dir_ctrl_pin = BPI_PIN_IND}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_ACK, .connected_pin = BPI_PIN_DT0, .dir_ctrl_pin = BPI_PIN_IND}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_REQ, .connected_pin = BPI_PIN_DT7, .dir_ctrl_pin = BPI_PIN_TAD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_MSG, .connected_pin = BPI_PIN_DT6, .dir_ctrl_pin = BPI_PIN_TAD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_CD, .connected_pin = BPI_PIN_DT4, .dir_ctrl_pin = BPI_PIN_TAD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_IO, .connected_pin = BPI_PIN_DT5, .dir_ctrl_pin = BPI_PIN_TAD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_BSY, .connected_pin = BPI_PIN_DP, .dir_ctrl_pin = BPI_PIN_TAD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_SEL, .connected_pin = BPI_PIN_DT1, .dir_ctrl_pin = BPI_PIN_IND}); - - pin_name_lookup[BPI_PIN_DT0] = " d0"; - pin_name_lookup[BPI_PIN_DT1] = " d1"; - pin_name_lookup[BPI_PIN_DT2] = " d2"; - pin_name_lookup[BPI_PIN_DT3] = " d3"; - pin_name_lookup[BPI_PIN_DT4] = " d4"; - pin_name_lookup[BPI_PIN_DT5] = " d5"; - pin_name_lookup[BPI_PIN_DT6] = " d6"; - pin_name_lookup[BPI_PIN_DT7] = " d7"; - pin_name_lookup[BPI_PIN_DP] = " dp"; - pin_name_lookup[BPI_PIN_ATN] = "atn"; - pin_name_lookup[BPI_PIN_RST] = "rst"; - pin_name_lookup[BPI_PIN_ACK] = "ack"; - pin_name_lookup[BPI_PIN_REQ] = "req"; - pin_name_lookup[BPI_PIN_MSG] = "msg"; - pin_name_lookup[BPI_PIN_CD] = " cd"; - pin_name_lookup[BPI_PIN_IO] = " io"; - pin_name_lookup[BPI_PIN_BSY] = "bsy"; - pin_name_lookup[BPI_PIN_SEL] = "sel"; - pin_name_lookup[BPI_PIN_IND] = "ind"; - pin_name_lookup[BPI_PIN_TAD] = "tad"; - pin_name_lookup[BPI_PIN_DTD] = "dtd"; - - local_pin_dtd = BPI_PIN_DTD; - local_pin_tad = BPI_PIN_TAD; - local_pin_ind = BPI_PIN_IND; - local_pin_ack = BPI_PIN_ACK; - local_pin_sel = BPI_PIN_SEL; - local_pin_atn = BPI_PIN_ATN; - local_pin_rst = BPI_PIN_RST; - local_pin_cd = BPI_PIN_CD; - local_pin_io = BPI_PIN_IO; - local_pin_msg = BPI_PIN_MSG; - local_pin_req = BPI_PIN_REQ; - local_pin_bsy = BPI_PIN_BSY; - local_pin_dt0 = BPI_PIN_DT0; - local_pin_dt1 = BPI_PIN_DT1; - local_pin_dt2 = BPI_PIN_DT2; - local_pin_dt3 = BPI_PIN_DT3; - local_pin_dt4 = BPI_PIN_DT4; - local_pin_dt5 = BPI_PIN_DT5; - local_pin_dt6 = BPI_PIN_DT6; - local_pin_dt7 = BPI_PIN_DT7; - local_pin_dp = BPI_PIN_DP; - } else { - LOGERROR("Unsupported board version: %s", SBC_Version::GetString()->c_str()); + spdlog::error("Unsupported board version: " + SBC_Version::GetAsString()); } } From d6116bf5c21cd086dca53cf8ff050eff63ce0fc3 Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Sun, 29 Oct 2023 13:01:12 +0100 Subject: [PATCH 38/87] Remove unused duplicate code dealing with MODE SELECT (#1268) (#1269) * Remove unused code --- cpp/controllers/scsi_controller.cpp | 14 ++------------ cpp/controllers/scsi_controller.h | 2 +- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/cpp/controllers/scsi_controller.cpp b/cpp/controllers/scsi_controller.cpp index 9d90d267..e6d1f45c 100644 --- a/cpp/controllers/scsi_controller.cpp +++ b/cpp/controllers/scsi_controller.cpp @@ -691,7 +691,7 @@ bool ScsiController::XferOut(bool cont) return device != nullptr ? device->WriteByteSequence(span(GetBuffer().data(), count)) : false; } -void ScsiController::DataOutNonBlockOriented() +void ScsiController::DataOutNonBlockOriented() const { assert(IsDataOut()); @@ -703,18 +703,8 @@ void ScsiController::DataOutNonBlockOriented() case scsi_command::eCmdWriteLong16: case scsi_command::eCmdVerify10: case scsi_command::eCmdVerify16: - break; - case scsi_command::eCmdModeSelect6: - case scsi_command::eCmdModeSelect10: { - if (auto device = dynamic_pointer_cast(GetDeviceForLun(GetEffectiveLun())); - device != nullptr) { - device->ModeSelect(GetOpcode(), GetCmd(), GetBuffer(), GetOffset()); - } - else { - throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code); - } - } + case scsi_command::eCmdModeSelect10: break; case scsi_command::eCmdSetMcastAddr: diff --git a/cpp/controllers/scsi_controller.h b/cpp/controllers/scsi_controller.h index 68a4be25..2fe3d525 100644 --- a/cpp/controllers/scsi_controller.h +++ b/cpp/controllers/scsi_controller.h @@ -93,7 +93,7 @@ private: bool XferOutBlockOriented(bool); void ReceiveBytes(); - void DataOutNonBlockOriented(); + void DataOutNonBlockOriented() const; void Receive(); // TODO Make non-virtual as soon as SysTimer calls do not segfault anymore on a regular PC, e.g. by using ifdef __arm__. From b5323d0cd3b6ae96a666364dd0c47dea7e019ab5 Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Wed, 25 Oct 2023 19:35:15 +0900 Subject: [PATCH 39/87] Bump to Flask 3 / Werkzeug 3 --- python/web/requirements.txt | 6 +++--- python/web/src/web.py | 34 +++++++++++++++++----------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/python/web/requirements.txt b/python/web/requirements.txt index a11d5f40..ed6efd1d 100644 --- a/python/web/requirements.txt +++ b/python/web/requirements.txt @@ -1,10 +1,10 @@ bjoern==3.2.2 -Flask==2.3.3 +Flask==3.0.0 Jinja2==3.1.2 protobuf==3.20.2 requests==2.31.0 simplepam==0.1.5 -flask_babel==2.0.0 +flask_babel==4.0.0 ua-parser==0.16.1 vcgencmd==0.1.1 -werkzeug==2.3.7 +werkzeug==3.0.1 diff --git a/python/web/src/web.py b/python/web/src/web.py index 6829b3d4..d11a3934 100644 --- a/python/web/src/web.py +++ b/python/web/src/web.py @@ -74,8 +74,25 @@ from settings import ( ) +def get_locale(): + """ + Uses the session language, or tries to detect based on accept-languages header + """ + session_locale = session.get("language") + if session_locale: + return session_locale + + client_locale = request.accept_languages.best_match(LANGUAGES) + if client_locale: + return client_locale + + logging.info("The default locale could not be detected. Falling back to English.") + return "en" + + APP = Flask(__name__) BABEL = Babel(APP) +BABEL.init_app(APP, locale_selector=get_locale) def get_env_info(): @@ -185,23 +202,6 @@ def response( return redirect(url_for("index")) -@BABEL.localeselector -def get_locale(): - """ - Uses the session language, or tries to detect based on accept-languages header - """ - session_locale = session.get("language") - if session_locale: - return session_locale - - client_locale = request.accept_languages.best_match(LANGUAGES) - if client_locale: - return client_locale - - logging.info("The default locale could not be detected. Falling back to English.") - return "en" - - def get_supported_locales(): """ Returns a list of languages supported by the web UI From 2de0aa090a95bdad0523cd3e9b90d757dc2367b7 Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Wed, 25 Oct 2023 19:35:22 +0900 Subject: [PATCH 40/87] Stop supporting Python 3.7, start supporting 3.11 --- python/README.md | 27 +++++++++++++++++++++++---- python/pyproject.toml | 2 +- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/python/README.md b/python/README.md index eaa41b59..791e4fe8 100644 --- a/python/README.md +++ b/python/README.md @@ -12,12 +12,31 @@ 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) +- Python 3.11 in [Debian Bookworm](https://packages.debian.org/bookworm/python3) +- Python 3.9 in [Debian Bullseye](https://packages.debian.org/bullseye/python3) -## Static analysis with pylint +## Static analysis and formatting -It is recommended to run pylint against new code to protect against bugs +The CI workflow is set up to check code formatting with `black`, +and linting with `flake8`. If non-conformant code is found, the CI job +will fail. + +Before checking in new code, install the development packages and run +these two tools locally. + +``` +pip install -r web/requirements-dev.txt +``` + +Note that `black` only works correctly if you run it in the root of the +`python/` dir: + +``` +cd python +black . +``` + +Optionally: It is recommended to run pylint against new code to protect against bugs and keep the code readable and maintainable. The local pylint configuration lives in .pylintrc. In order for pylint to recognize venv libraries, the pylint-venv package is required. diff --git a/python/pyproject.toml b/python/pyproject.toml index aea91ba3..0ede3f46 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -1,4 +1,4 @@ [tool.black] line-length = 100 -target-version = ['py37', 'py38', 'py39'] +target-version = ['py39', 'py310', 'py311'] extend-exclude = ".*_pb2.py" From c78ba80088285e24f971b61dc016ccaab63dac04 Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Mon, 30 Oct 2023 11:24:18 +0100 Subject: [PATCH 41/87] Move default device parameters from DeviceFactory to the respective devices #1257 (#1259) * Move default parameter handling --- cpp/devices/ctapdriver.cpp | 8 +++++++ cpp/devices/ctapdriver.h | 4 ++++ cpp/devices/device.cpp | 2 +- cpp/devices/device.h | 5 +---- cpp/devices/device_factory.cpp | 27 +++++++---------------- cpp/devices/device_factory.h | 10 +-------- cpp/devices/scsi_daynaport.cpp | 22 +++++++++---------- cpp/devices/scsi_daynaport.h | 7 +++--- cpp/devices/scsi_host_bridge.cpp | 16 +++++++------- cpp/devices/scsi_host_bridge.h | 4 +++- cpp/devices/scsi_printer.cpp | 35 ++++++++++++++++++------------ cpp/devices/scsi_printer.h | 2 ++ cpp/devices/scsihd_nec.h | 4 +--- cpp/piscsi/piscsi_response.cpp | 2 +- cpp/test/device_factory_test.cpp | 29 ------------------------- cpp/test/device_test.cpp | 28 +++++++----------------- cpp/test/piscsi_response_test.cpp | 1 - cpp/test/scsi_daynaport_test.cpp | 7 ++++++ cpp/test/scsi_host_bridge_test.cpp | 7 ++++++ cpp/test/scsi_printer_test.cpp | 7 ++++++ 20 files changed, 103 insertions(+), 124 deletions(-) diff --git a/cpp/devices/ctapdriver.cpp b/cpp/devices/ctapdriver.cpp index c010c93c..6f394ae6 100644 --- a/cpp/devices/ctapdriver.cpp +++ b/cpp/devices/ctapdriver.cpp @@ -213,6 +213,14 @@ void CTapDriver::CleanUp() const } } +param_map CTapDriver::GetDefaultParams() const +{ + return { + { "interface", Join(GetNetworkInterfaces(), ",") }, + { "inet", DEFAULT_IP } + }; +} + pair CTapDriver::ExtractAddressAndMask(const string& s) { string address = s; diff --git a/cpp/devices/ctapdriver.h b/cpp/devices/ctapdriver.h index f8b3bdfd..1e0eff68 100644 --- a/cpp/devices/ctapdriver.h +++ b/cpp/devices/ctapdriver.h @@ -31,6 +31,8 @@ class CTapDriver { static const string BRIDGE_NAME; + const inline static string DEFAULT_IP = "10.10.20.1/24"; //NOSONAR This hardcoded IP address is safe + public: CTapDriver() = default; @@ -41,6 +43,8 @@ public: bool Init(const param_map&); void CleanUp() const; + param_map GetDefaultParams() const; + void GetMacAddr(uint8_t *) const; int Receive(uint8_t *) const; int Send(const uint8_t *, int) const; diff --git a/cpp/devices/device.cpp b/cpp/devices/device.cpp index 58b071f8..6c764ec3 100644 --- a/cpp/devices/device.cpp +++ b/cpp/devices/device.cpp @@ -89,7 +89,7 @@ string Device::GetParam(const string& key) const void Device::SetParams(const param_map& set_params) { - params = default_params; + params = GetDefaultParams(); // Devices with image file support implicitly support the "file" parameter if (SupportsFile()) { diff --git a/cpp/devices/device.h b/cpp/devices/device.h index 80f1a272..edff1d13 100644 --- a/cpp/devices/device.h +++ b/cpp/devices/device.h @@ -68,9 +68,6 @@ class Device //NOSONAR The number of fields and methods is justified, the comple // The parameters the device was created with param_map params; - // The default parameters - param_map default_params; - // Sense Key and ASC // MSB Reserved (0x00) // Sense Key @@ -139,7 +136,7 @@ public: void SupportsParams(bool b) { supports_params = b; } void SupportsFile(bool b) { supports_file = b; } auto GetParams() const { return params; } - void SetDefaultParams(const param_map& p) { default_params = p; } + virtual param_map GetDefaultParams() const { return {}; } void SetStatusCode(int s) { status_code = s; } diff --git a/cpp/devices/device_factory.cpp b/cpp/devices/device_factory.cpp index da69fa98..f951d645 100644 --- a/cpp/devices/device_factory.cpp +++ b/cpp/devices/device_factory.cpp @@ -29,14 +29,6 @@ DeviceFactory::DeviceFactory() sector_sizes[SCMO] = { 512, 1024, 2048, 4096 }; sector_sizes[SCCD] = { 512, 2048}; - const string interfaces = Join(GetNetworkInterfaces(), ","); - - default_params[SCBR]["interface"] = interfaces; - default_params[SCBR]["inet"] = DEFAULT_IP; - default_params[SCDP]["interface"] = interfaces; - default_params[SCDP]["inet"] = DEFAULT_IP; - default_params[SCLP]["cmd"] = "lp -oraw %f"; - extension_mapping["hd1"] = SCHD; extension_mapping["hds"] = SCHD; extension_mapping["hda"] = SCHD; @@ -115,7 +107,6 @@ shared_ptr DeviceFactory::CreateDevice(PbDeviceType type, int lun device = make_shared(lun); // Since this is an emulation for a specific driver the product name has to be set accordingly device->SetProduct("RASCSI BRIDGE"); - device->SetDefaultParams(default_params.find(type)->second); break; case SCDP: @@ -124,7 +115,6 @@ shared_ptr DeviceFactory::CreateDevice(PbDeviceType type, int lun device->SetVendor("Dayna"); device->SetProduct("SCSI/Link"); device->SetRevision("1.4a"); - device->SetDefaultParams(default_params.find(type)->second); break; case SCHS: @@ -137,7 +127,6 @@ shared_ptr DeviceFactory::CreateDevice(PbDeviceType type, int lun case SCLP: device = make_shared(lun); device->SetProduct("SCSI PRINTER"); - device->SetDefaultParams(default_params.find(type)->second); break; default: @@ -147,14 +136,14 @@ shared_ptr DeviceFactory::CreateDevice(PbDeviceType type, int lun return device; } -const unordered_set& DeviceFactory::GetSectorSizes(PbDeviceType type) const +// TODO Move to respective device, which may require changes in the SCSI_HD/SCSIHD_NEC inheritance hierarchy +unordered_set DeviceFactory::GetSectorSizes(PbDeviceType type) const { const auto& it = sector_sizes.find(type); - return it != sector_sizes.end() ? it->second : EMPTY_SET; -} - -const param_map& DeviceFactory::GetDefaultParams(PbDeviceType type) const -{ - const auto& it = default_params.find(type); - return it != default_params.end() ? it->second : EMPTY_PARAM_MAP; + if (it != sector_sizes.end()) { + return it->second; + } + else { + return {}; + } } diff --git a/cpp/devices/device_factory.h b/cpp/devices/device_factory.h index cdce0ba2..fef1c17d 100644 --- a/cpp/devices/device_factory.h +++ b/cpp/devices/device_factory.h @@ -12,7 +12,6 @@ #pragma once #include "shared/piscsi_util.h" -#include "devices/device.h" #include #include #include @@ -25,7 +24,6 @@ class PrimaryDevice; class DeviceFactory { - const inline static string DEFAULT_IP = "10.10.20.1/24"; //NOSONAR This hardcoded IP address is safe public: @@ -34,20 +32,14 @@ public: shared_ptr CreateDevice(PbDeviceType, int, const string&) const; PbDeviceType GetTypeForFile(const string&) const; - const unordered_set& GetSectorSizes(PbDeviceType type) const; - const param_map& GetDefaultParams(PbDeviceType type) const; + unordered_set GetSectorSizes(PbDeviceType type) const; const auto& GetExtensionMapping() const { return extension_mapping; } private: unordered_map> sector_sizes; - unordered_map default_params; - unordered_map> extension_mapping; unordered_map> device_mapping; - - inline static const unordered_set EMPTY_SET; - inline static const param_map EMPTY_PARAM_MAP; }; diff --git a/cpp/devices/scsi_daynaport.cpp b/cpp/devices/scsi_daynaport.cpp index e710ab33..db46ea16 100644 --- a/cpp/devices/scsi_daynaport.cpp +++ b/cpp/devices/scsi_daynaport.cpp @@ -54,8 +54,8 @@ bool SCSIDaynaPort::Init(const param_map& params) // In the MacOS driver, it looks like the driver is doing two "READ" system calls. SetSendDelay(DAYNAPORT_READ_HEADER_SZ); - m_bTapEnable = m_tap.Init(GetParams()); - if (!m_bTapEnable) { + tap_enabled = tap.Init(GetParams()); + if (!tap_enabled) { // Not terminating on regular Linux PCs is helpful for testing #if !defined(__x86_64__) && !defined(__X86__) return false; @@ -73,7 +73,7 @@ bool SCSIDaynaPort::Init(const param_map& params) void SCSIDaynaPort::CleanUp() { - m_tap.CleanUp(); + tap.CleanUp(); } vector SCSIDaynaPort::InquiryInternal() const @@ -145,7 +145,7 @@ int SCSIDaynaPort::Read(cdb_t cdb, vector& buf, uint64_t) const // The first 2 bytes are reserved for the length of the packet // The next 4 bytes are reserved for a flag field //rx_packet_size = m_tap.Rx(response->data); - rx_packet_size = m_tap.Receive(&buf[DAYNAPORT_READ_HEADER_SZ]); + rx_packet_size = tap.Receive(&buf[DAYNAPORT_READ_HEADER_SZ]); // If we didn't receive anything, return size of 0 if (rx_packet_size <= 0) { @@ -194,7 +194,7 @@ int SCSIDaynaPort::Read(cdb_t cdb, vector& buf, uint64_t) const // If there are pending packets to be processed, we'll tell the host that the read // length was 0. - if (!m_tap.HasPendingPackets()) { + if (!tap.HasPendingPackets()) { response->length = 0; response->flags = read_data_flags_t::e_no_more_data; return DAYNAPORT_READ_HEADER_SZ; @@ -219,7 +219,7 @@ int SCSIDaynaPort::Read(cdb_t cdb, vector& buf, uint64_t) const size = 64; } SetInt16(buf, 0, size); - SetInt32(buf, 2, m_tap.HasPendingPackets() ? 0x10 : 0x00); + SetInt32(buf, 2, tap.HasPendingPackets() ? 0x10 : 0x00); // Return the packet size + 2 for the length + 4 for the flag field // The CRC was already appended by the ctapdriver @@ -256,13 +256,13 @@ bool SCSIDaynaPort::Write(cdb_t cdb, span buf) const { if (const int data_format = cdb[5]; data_format == 0x00) { const int data_length = GetInt16(cdb, 3); - m_tap.Send(buf.data(), data_length); + tap.Send(buf.data(), data_length); LogTrace("Transmitted " + to_string(data_length) + " byte(s) (00 format)"); } else if (data_format == 0x80) { // The data length is specified in the first 2 bytes of the payload const int data_length = buf[1] + ((static_cast(buf[0]) & 0xff) << 8); - m_tap.Send(&(buf.data()[4]), data_length); + tap.Send(&(buf.data()[4]), data_length); LogTrace("Transmitted " + to_string(data_length) + "byte(s) (80 format)"); } else { @@ -455,18 +455,18 @@ void SCSIDaynaPort::SetMcastAddr() const void SCSIDaynaPort::EnableInterface() const { if (GetController()->GetCmdByte(5) & 0x80) { - if (const string error = m_tap.IpLink(true); !error.empty()) { + if (const string error = tap.IpLink(true); !error.empty()) { LogWarn("Unable to enable the DaynaPort Interface: " + error); throw scsi_exception(sense_key::aborted_command); } - m_tap.Flush(); + tap.Flush(); LogInfo("The DaynaPort interface has been ENABLED"); } else { - if (const string error = m_tap.IpLink(false); !error.empty()) { + if (const string error = tap.IpLink(false); !error.empty()) { LogWarn("Unable to disable the DaynaPort Interface: " + error); throw scsi_exception(sense_key::aborted_command); diff --git a/cpp/devices/scsi_daynaport.h b/cpp/devices/scsi_daynaport.h index 45d0ea50..dc2ede6a 100644 --- a/cpp/devices/scsi_daynaport.h +++ b/cpp/devices/scsi_daynaport.h @@ -52,6 +52,8 @@ public: bool Init(const param_map&) override; void CleanUp() override; + param_map GetDefaultParams() const override { return tap.GetDefaultParams(); } + // Commands vector InquiryInternal() const override; int Read(cdb_t, vector&, uint64_t) const; @@ -116,8 +118,7 @@ private: .frames_lost = 0, }; - CTapDriver m_tap; + CTapDriver tap; - // TAP valid flag - bool m_bTapEnable = false; + bool tap_enabled = false; }; diff --git a/cpp/devices/scsi_host_bridge.cpp b/cpp/devices/scsi_host_bridge.cpp index 93ef1c0d..394441c1 100644 --- a/cpp/devices/scsi_host_bridge.cpp +++ b/cpp/devices/scsi_host_bridge.cpp @@ -42,14 +42,14 @@ bool SCSIBR::Init(const param_map& params) AddCommand(scsi_command::eCmdSendMessage10, [this] { SendMessage10(); }); #ifdef __linux__ - m_bTapEnable = tap.Init(GetParams()); - if (!m_bTapEnable){ + tap_enabled = tap.Init(GetParams()); + if (!tap_enabled){ return false; } #endif // Generate MAC Address - if (m_bTapEnable) { + if (tap_enabled) { tap.GetMacAddr(mac_addr.data()); mac_addr[5]++; } @@ -57,13 +57,13 @@ bool SCSIBR::Init(const param_map& params) // Packet reception flag OFF packet_enable = false; - SetReady(m_bTapEnable); + SetReady(tap_enabled); // Not terminating on regular Linux PCs is helpful for testing #if defined(__x86_64__) || defined(__X86__) return true; #else - return m_bTapEnable; + return tap_enabled; #endif } @@ -85,7 +85,7 @@ vector SCSIBR::InquiryInternal() const buf[36] = '0'; // TAP Enable - if (m_bTapEnable) { + if (tap_enabled) { buf[37] = '1'; } @@ -115,7 +115,7 @@ int SCSIBR::GetMessage10(cdb_t cdb, vector& buf) switch (type) { case 1: // Ethernet // Do not process if TAP is invalid - if (!m_bTapEnable) { + if (!tap_enabled) { return 0; } @@ -206,7 +206,7 @@ bool SCSIBR::ReadWrite(cdb_t cdb, vector& buf) switch (type) { case 1: // Ethernet // Do not process if TAP is invalid - if (!m_bTapEnable) { + if (!tap_enabled) { return false; } diff --git a/cpp/devices/scsi_host_bridge.h b/cpp/devices/scsi_host_bridge.h index f3a80096..b2cdf981 100644 --- a/cpp/devices/scsi_host_bridge.h +++ b/cpp/devices/scsi_host_bridge.h @@ -38,6 +38,8 @@ public: bool Init(const param_map&) override; void CleanUp() override; + param_map GetDefaultParams() const override { return tap.GetDefaultParams(); } + // Commands vector InquiryInternal() const override; int GetMessage10(cdb_t, vector&); @@ -55,7 +57,7 @@ private: void SendPacket(span, int) const; // Send a packet CTapDriver tap; // TAP driver - bool m_bTapEnable = false; // TAP valid flag + bool tap_enabled = false; // TAP valid flag array mac_addr = {}; // MAC Address int packet_len = 0; // Receive packet size array packet_buf; // Receive packet buffer diff --git a/cpp/devices/scsi_printer.cpp b/cpp/devices/scsi_printer.cpp index da4bff40..f4399985 100644 --- a/cpp/devices/scsi_printer.cpp +++ b/cpp/devices/scsi_printer.cpp @@ -73,6 +73,27 @@ bool SCSIPrinter::Init(const param_map& params) return true; } +void SCSIPrinter::CleanUp() +{ + PrimaryDevice::CleanUp(); + + if (out.is_open()) { + out.close(); + + error_code error; + remove(path(filename), error); + + filename = ""; + } +} + +param_map SCSIPrinter::GetDefaultParams() const +{ + return { + { "cmd", "lp -oraw %f" } + }; +} + void SCSIPrinter::TestUnitReady() { // The printer is always ready @@ -164,17 +185,3 @@ bool SCSIPrinter::WriteByteSequence(span buf) return !out.fail(); } - -void SCSIPrinter::CleanUp() -{ - PrimaryDevice::CleanUp(); - - if (out.is_open()) { - out.close(); - - error_code error; - remove(path(filename), error); - - filename = ""; - } -} diff --git a/cpp/devices/scsi_printer.h b/cpp/devices/scsi_printer.h index 6e626e54..e9f6c92d 100644 --- a/cpp/devices/scsi_printer.h +++ b/cpp/devices/scsi_printer.h @@ -33,6 +33,8 @@ public: bool Init(const param_map&) override; void CleanUp() override; + param_map GetDefaultParams() const override; + vector InquiryInternal() const override; bool WriteByteSequence(span) override; diff --git a/cpp/devices/scsihd_nec.h b/cpp/devices/scsihd_nec.h index 0bf7db36..eec3c1be 100644 --- a/cpp/devices/scsihd_nec.h +++ b/cpp/devices/scsihd_nec.h @@ -33,7 +33,7 @@ class SCSIHD_NEC : public SCSIHD //NOSONAR The inheritance hierarchy depth is ac { public: - explicit SCSIHD_NEC(int lun) : SCSIHD(lun, sector_sizes, false) {} + explicit SCSIHD_NEC(int lun) : SCSIHD(lun, { 512 }, false) {} ~SCSIHD_NEC() override = default; void Open() override; @@ -52,8 +52,6 @@ private: static int GetInt16LittleEndian(const uint8_t *); static int GetInt32LittleEndian(const uint8_t *); - static inline const unordered_set sector_sizes = { 512 }; - // Image file offset off_t image_offset = 0; diff --git a/cpp/piscsi/piscsi_response.cpp b/cpp/piscsi/piscsi_response.cpp index 6987f78f..16ac25b5 100644 --- a/cpp/piscsi/piscsi_response.cpp +++ b/cpp/piscsi/piscsi_response.cpp @@ -36,7 +36,7 @@ void PiscsiResponse::GetDeviceProperties(const Device& device, PbDevicePropertie properties.set_supports_params(device.SupportsParams()); if (device.SupportsParams()) { - for (const auto& [key, value] : device_factory.GetDefaultParams(device.GetType())) { + for (const auto& [key, value] : device.GetDefaultParams()) { auto& map = *properties.mutable_default_params(); map[key] = value; } diff --git a/cpp/test/device_factory_test.cpp b/cpp/test/device_factory_test.cpp index b37c9bc3..0c51c194 100644 --- a/cpp/test/device_factory_test.cpp +++ b/cpp/test/device_factory_test.cpp @@ -92,35 +92,6 @@ TEST(DeviceFactoryTest, GetExtensionMapping) EXPECT_EQ(SCCD, mapping["is1"]); } -TEST(DeviceFactoryTest, GetDefaultParams) -{ - DeviceFactory device_factory; - - param_map params = device_factory.GetDefaultParams(SCHD); - EXPECT_TRUE(params.empty()); - - params = device_factory.GetDefaultParams(SCRM); - EXPECT_TRUE(params.empty()); - - params = device_factory.GetDefaultParams(SCMO); - EXPECT_TRUE(params.empty()); - - params = device_factory.GetDefaultParams(SCCD); - EXPECT_TRUE(params.empty()); - - params = device_factory.GetDefaultParams(SCHS); - EXPECT_TRUE(params.empty()); - - params = device_factory.GetDefaultParams(SCBR); - EXPECT_EQ(2, params.size()); - - params = device_factory.GetDefaultParams(SCDP); - EXPECT_EQ(2, params.size()); - - params = device_factory.GetDefaultParams(SCLP); - EXPECT_EQ(1, params.size()); -} - TEST(DeviceFactoryTest, UnknownDeviceType) { DeviceFactory device_factory; diff --git a/cpp/test/device_test.cpp b/cpp/test/device_test.cpp index 0662b67e..60ad3b36 100644 --- a/cpp/test/device_test.cpp +++ b/cpp/test/device_test.cpp @@ -10,6 +10,14 @@ #include "mocks.h" #include "devices/device.h" +TEST(DeviceTest, GetDefaultParams) +{ + MockDevice device(0); + + const auto params = device.GetDefaultParams(); + EXPECT_TRUE(params.empty()); +} + TEST(DeviceTest, Properties) { const int LUN = 5; @@ -192,26 +200,6 @@ TEST(DeviceTest, GetPaddedName) EXPECT_EQ("V P R ", device.GetPaddedName()); } -TEST(DeviceTest, Params) -{ - MockDevice device(0); - param_map params; - params["key"] = "value"; - - EXPECT_EQ("", device.GetParam("key")); - - device.SetParams(params); - EXPECT_EQ("", device.GetParam("key")); - - param_map default_params; - default_params["key"] = "value"; - device.SetDefaultParams(default_params); - EXPECT_EQ("", device.GetParam("key")); - - device.SetParams(params); - EXPECT_EQ("value", device.GetParam("key")); -} - TEST(DeviceTest, StatusCode) { MockDevice device(0); diff --git a/cpp/test/piscsi_response_test.cpp b/cpp/test/piscsi_response_test.cpp index 19ebb49c..278880d7 100644 --- a/cpp/test/piscsi_response_test.cpp +++ b/cpp/test/piscsi_response_test.cpp @@ -53,7 +53,6 @@ void TestNonDiskDevice(PbDeviceType type, int default_param_count) EXPECT_EQ(0, device.block_size()); EXPECT_EQ(0, device.block_count()); EXPECT_EQ(default_param_count, device.properties().default_params().size()); - EXPECT_EQ(default_param_count, device.params().size()); EXPECT_FALSE(device.properties().supports_file()); if (default_param_count) { EXPECT_TRUE(device.properties().supports_params()); diff --git a/cpp/test/scsi_daynaport_test.cpp b/cpp/test/scsi_daynaport_test.cpp index 7b54a3f3..712b3b38 100644 --- a/cpp/test/scsi_daynaport_test.cpp +++ b/cpp/test/scsi_daynaport_test.cpp @@ -11,6 +11,13 @@ #include "shared/piscsi_exceptions.h" #include "devices/scsi_daynaport.h" +TEST(ScsiDaynaportTest, GetDefaultParams) +{ + const auto [controller, daynaport] = CreateDevice(SCDP); + const auto params = daynaport->GetDefaultParams(); + EXPECT_EQ(2, params.size()); +} + TEST(ScsiDaynaportTest, Inquiry) { TestInquiry::Inquiry(SCDP, device_type::processor, scsi_level::scsi_2, "Dayna SCSI/Link 1.4a", 0x20, false); diff --git a/cpp/test/scsi_host_bridge_test.cpp b/cpp/test/scsi_host_bridge_test.cpp index f0b2843f..117ab519 100644 --- a/cpp/test/scsi_host_bridge_test.cpp +++ b/cpp/test/scsi_host_bridge_test.cpp @@ -9,6 +9,13 @@ #include "mocks.h" +TEST(ScsiHostBridgeTest, GetDefaultParams) +{ + const auto [controller, bridge] = CreateDevice(SCBR); + const auto params = bridge->GetDefaultParams(); + EXPECT_EQ(2, params.size()); +} + TEST(ScsiHostBridgeTest, Inquiry) { TestInquiry::Inquiry(SCBR, device_type::communications, scsi_level::scsi_2, "PiSCSI RASCSI BRIDGE ", 0x27, false); diff --git a/cpp/test/scsi_printer_test.cpp b/cpp/test/scsi_printer_test.cpp index 7f29f4cf..36bfcd79 100644 --- a/cpp/test/scsi_printer_test.cpp +++ b/cpp/test/scsi_printer_test.cpp @@ -13,6 +13,13 @@ using namespace std; +TEST(ScsiPrinterTest, GetDefaultParams) +{ + const auto [controller, printer] = CreateDevice(SCLP); + const auto params = printer->GetDefaultParams(); + EXPECT_EQ(1, params.size()); +} + TEST(ScsiPrinterTest, Init) { auto [controller, printer] = CreateDevice(SCLP); From 8f45e4f49138265e675e7d6ac9a79e122063e33e Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Mon, 30 Oct 2023 11:34:07 +0100 Subject: [PATCH 42/87] Add options to only run INQUIRY and to scan the bus to scsidump (#1092) (#1261) * Add options to only run INQUIRY and to scan the bus to scsidump --- cpp/scsidump/scsidump_core.cpp | 301 ++++++++++++++++++---------- cpp/scsidump/scsidump_core.h | 65 ++++-- cpp/test/gpiobus_raspberry_test.cpp | 2 +- cpp/test/scsidump_test.cpp | 44 ++-- cpp/test/test_shared.cpp | 16 +- cpp/test/test_shared.h | 2 - doc/scsidump.1 | 8 + doc/scsidump_man_page.txt | 63 ++++-- 8 files changed, 325 insertions(+), 176 deletions(-) diff --git a/cpp/scsidump/scsidump_core.cpp b/cpp/scsidump/scsidump_core.cpp index 971d79b6..05737842 100644 --- a/cpp/scsidump/scsidump_core.cpp +++ b/cpp/scsidump/scsidump_core.cpp @@ -12,9 +12,9 @@ // TODO Evaluate CHECK CONDITION after sending a command // TODO Send IDENTIFY message in order to support LUNS > 7 +// TODO Get rid of some fields in favor of method arguments #include "scsidump/scsidump_core.h" -#include "hal/gpiobus.h" #include "hal/gpiobus_factory.h" #include "hal/systimer.h" #include "controllers/controller_manager.h" @@ -44,11 +44,11 @@ void ScsiDump::CleanUp() } } -void ScsiDump::KillHandler(int) +void ScsiDump::TerminationHandler(int) { CleanUp(); - exit(EXIT_SUCCESS); + // Process will terminate automatically } bool ScsiDump::Banner(span args) const @@ -56,7 +56,7 @@ bool ScsiDump::Banner(span args) const cout << piscsi_util::Banner("(Hard Disk Dump/Restore Utility)"); if (args.size() < 2 || string(args[1]) == "-h" || string(args[1]) == "--help") { - cout << "Usage: " << args[0] << " -t ID[:LUN] [-i BID] -f FILE [-v] [-r] [-s BUFFER_SIZE] [-p]\n" + cout << "Usage: " << args[0] << " -t ID[:LUN] [-i BID] -f FILE [-v] [-r] [-s BUFFER_SIZE] [-p] [-I] [-S]\n" << " ID is the target device ID (0-" << (ControllerManager::GetScsiIdMax() - 1) << ").\n" << " LUN is the optional target device LUN (0-" << (ControllerManager::GetScsiLunMax() -1 ) << ")." << " Default is 0.\n" @@ -67,6 +67,8 @@ bool ScsiDump::Banner(span args) const << " -v Enable verbose logging.\n" << " -r Restore instead of dump.\n" << " -p Generate .properties file to be used with the PiSCSI web interface. Only valid for dump mode.\n" + << " -I Display INQUIRY data of ID[:LUN].\n" + << " -S Scan SCSI bus for devices.\n" << flush; return false; @@ -77,11 +79,13 @@ bool ScsiDump::Banner(span args) const bool ScsiDump::Init() const { - // Interrupt handler setting - if (signal(SIGINT, KillHandler) == SIG_ERR || signal(SIGHUP, KillHandler) == SIG_ERR || - signal(SIGTERM, KillHandler) == SIG_ERR) { - return false; - } + // Signal handler for cleaning up + struct sigaction termination_handler; + termination_handler.sa_handler = TerminationHandler; + sigemptyset(&termination_handler.sa_mask); + termination_handler.sa_flags = 0; + sigaction(SIGTERM, &termination_handler, nullptr); + signal(SIGPIPE, SIG_IGN); bus = GPIOBUS_Factory::Create(BUS::mode_e::INITIATOR); @@ -95,7 +99,7 @@ void ScsiDump::ParseArguments(span args) int buffer_size = DEFAULT_BUFFER_SIZE; opterr = 0; - while ((opt = getopt(static_cast(args.size()), args.data(), "i:f:s:t:rvp")) != -1) { + while ((opt = getopt(static_cast(args.size()), args.data(), "i:f:s:t:rvpIS")) != -1) { switch (opt) { case 'i': if (!GetAsUnsignedInt(optarg, initiator_id) || initiator_id > 7) { @@ -107,6 +111,10 @@ void ScsiDump::ParseArguments(span args) filename = optarg; break; + case 'I': + inquiry = true; + break; + case 's': if (!GetAsUnsignedInt(optarg, buffer_size) || buffer_size < MINIMUM_BUFFER_SIZE) { throw parser_exception("Buffer size must be at least " + to_string(MINIMUM_BUFFER_SIZE / 1024) + " KiB"); @@ -114,15 +122,15 @@ void ScsiDump::ParseArguments(span args) break; - case 't': { - const string error = ProcessId(optarg, target_id, target_lun); - if (!error.empty()) { + case 'S': + scan_bus = true; + break; + + case 't': + if (const string error = ProcessId(optarg, target_id, target_lun); !error.empty()) { throw parser_exception(error); } - if (target_lun == -1) { - target_lun = 0; - } - } break; + break; case 'v': set_level(level::debug); @@ -141,32 +149,44 @@ void ScsiDump::ParseArguments(span args) } } + if (!scan_bus && !inquiry && filename.empty()) { + throw parser_exception("Missing filename"); + } + + if (!scan_bus && !inquiry && target_id == -1) { + throw parser_exception("Missing target ID"); + } + if (target_id == initiator_id) { throw parser_exception("Target ID and PiSCSI board ID must not be identical"); } - if (filename.empty()) { - throw parser_exception("Missing filename"); + if (target_lun == -1) { + target_lun = 0; + } + + if (scan_bus) { + inquiry = false; } buffer = vector(buffer_size); } -void ScsiDump::WaitPhase(phase_t phase) const +void ScsiDump::WaitForPhase(phase_t phase) const { spdlog::debug(string("Waiting for ") + BUS::GetPhaseStrRaw(phase) + " phase"); // Timeout (3000ms) const uint32_t now = SysTimer::GetTimerLow(); - while ((SysTimer::GetTimerLow() - now) < 3 * 1000 * 1000) { + while ((SysTimer::GetTimerLow() - now) < 3'000'000) { bus->Acquire(); if (bus->GetREQ() && bus->GetPhase() == phase) { return; } } - throw parser_exception("Expected " + string(BUS::GetPhaseStrRaw(phase)) + " phase, actual phase is " + - string(BUS::GetPhaseStrRaw(bus->GetPhase()))); + throw phase_exception("Expected " + string(BUS::GetPhaseStrRaw(phase)) + " phase, actual phase is " + + string(BUS::GetPhaseStrRaw(bus->GetPhase()))); } void ScsiDump::Selection() const @@ -189,7 +209,7 @@ void ScsiDump::Command(scsi_command cmd, vector& cdb) const Selection(); - WaitPhase(phase_t::command); + WaitForPhase(phase_t::command); cdb[0] = static_cast(cmd); cdb[1] = static_cast(static_cast(cdb[1]) | static_cast(target_lun << 5)); @@ -197,43 +217,43 @@ void ScsiDump::Command(scsi_command cmd, vector& cdb) const bus->SendHandShake(cdb.data(), static_cast(cdb.size()), BUS::SEND_NO_DELAY)) { BusFree(); - throw parser_exception(command_mapping.find(cmd)->second.second + string(" failed")); + throw phase_exception(command_mapping.find(cmd)->second.second + string(" failed")); } } void ScsiDump::DataIn(int length) { - WaitPhase(phase_t::datain); + WaitForPhase(phase_t::datain); if (!bus->ReceiveHandShake(buffer.data(), length)) { - throw parser_exception("DATA IN failed"); + throw phase_exception("DATA IN failed"); } } void ScsiDump::DataOut(int length) { - WaitPhase(phase_t::dataout); + WaitForPhase(phase_t::dataout); if (!bus->SendHandShake(buffer.data(), length, BUS::SEND_NO_DELAY)) { - throw parser_exception("DATA OUT failed"); + throw phase_exception("DATA OUT failed"); } } void ScsiDump::Status() const { - WaitPhase(phase_t::status); + WaitForPhase(phase_t::status); if (array buf; bus->ReceiveHandShake(buf.data(), 1) != 1) { - throw parser_exception("STATUS failed"); + throw phase_exception("STATUS failed"); } } void ScsiDump::MessageIn() const { - WaitPhase(phase_t::msgin); + WaitForPhase(phase_t::msgin); if (array buf; bus->ReceiveHandShake(buf.data(), 1) != 1) { - throw parser_exception("MESSAGE IN failed"); + throw phase_exception("MESSAGE IN failed"); } } @@ -388,7 +408,7 @@ void ScsiDump::WaitForBusy() const // Success if the target is busy if (!bus->GetBSY()) { - throw parser_exception("SELECTION failed"); + throw phase_exception("SELECTION failed"); } } @@ -398,28 +418,49 @@ int ScsiDump::run(span args) return EXIT_SUCCESS; } - if (!Init()) { - cerr << "Error: Initializing. Are you root?" << endl; + try { + ParseArguments(args); + } + catch (const parser_exception& e) { + cerr << "Error: " << e.what() << endl; + return EXIT_FAILURE; + } - // Probably not root - return EPERM; + if (getuid()) { + cerr << "Error: GPIO bus access requires root permissions. Are you running as root?" << endl; + return EXIT_FAILURE; + } + +#ifndef USE_SEL_EVENT_ENABLE + cerr << "Error: No PiSCSI hardware support" << endl; + return EXIT_FAILURE; +#endif + + if (!Init()) { + cerr << "Error: Can't initialize bus" << endl; + return EXIT_FAILURE; } try { - ParseArguments(args); + if (scan_bus) { + ScanBus(); + } + else if (inquiry) { + DisplayBoardId(); -#ifndef USE_SEL_EVENT_ENABLE - cerr << "Error: No PiSCSI hardware support" << endl; - return EXIT_FAILURE; -#endif + inquiry_info_t inq_info; + DisplayInquiry(inq_info, false); + } + else { + DumpRestore(); + } + } + catch (const phase_exception& e) { + cerr << "Error: " << e.what() << endl; - return DumpRestore(); - } catch (const parser_exception& e) { - cerr << "Error: " << e.what() << endl; + CleanUp(); - CleanUp(); - - return EXIT_FAILURE; + return EXIT_FAILURE; } CleanUp(); @@ -427,9 +468,92 @@ int ScsiDump::run(span args) return EXIT_SUCCESS; } +void ScsiDump::DisplayBoardId() const +{ + cout << DIVIDER << "\nPiSCSI board ID is " << initiator_id << "\n"; +} + +void ScsiDump::ScanBus() +{ + DisplayBoardId(); + + for (target_id = 0; target_id < ControllerManager::GetScsiIdMax(); target_id++) { + if (initiator_id == target_id) { + continue; + } + + for (target_lun = 0; target_lun < 8; target_lun++) { + inquiry_info_t inq_info; + try { + DisplayInquiry(inq_info, false); + } + catch(const phase_exception&) { + // Continue with next ID if there is no LUN 0 + if (!target_lun) { + break; + } + } + } + } +} + +bool ScsiDump::DisplayInquiry(ScsiDump::inquiry_info_t& inq_info, bool check_type) +{ + // Assert RST for 1 ms + bus->SetRST(true); + const timespec ts = {.tv_sec = 0, .tv_nsec = 1000 * 1000}; + nanosleep(&ts, nullptr); + bus->SetRST(false); + + cout << DIVIDER << "\nTarget device is " << target_id << ":" << target_lun << "\n" << flush; + + Inquiry(); + + const auto type = static_cast(buffer[0]); + if ((type & byte{0x1f}) == byte{0x1f}) { + // Requested LUN is not available + return false; + } + + array str = {}; + memcpy(str.data(), &buffer[8], 8); + inq_info.vendor = string(str.data()); + cout << "Vendor: " << inq_info.vendor << "\n"; + + str.fill(0); + memcpy(str.data(), &buffer[16], 16); + inq_info.product = string(str.data()); + cout << "Product: " << inq_info.product << "\n"; + + str.fill(0); + memcpy(str.data(), &buffer[32], 4); + inq_info.revision = string(str.data()); + cout << "Revision: " << inq_info.revision << "\n" << flush; + + if (const auto& t = DEVICE_TYPES.find(type & byte{0x1f}); t != DEVICE_TYPES.end()) { + cout << "Device Type: " << (*t).second << "\n"; + } + else { + cout << "Device Type: Unknown\n"; + } + + cout << "Removable: " << (((static_cast(buffer[1]) & byte{0x80}) == byte{0x80}) ? "Yes" : "No") << "\n"; + + if (check_type && type != static_cast(device_type::direct_access) && + type != static_cast(device_type::cd_rom) && type != static_cast(device_type::optical_memory)) { + cerr << "Invalid device type, supported types for dump/restore are DIRECT ACCESS, CD-ROM/DVD/BD and OPTICAL MEMORY" << endl; + return false; + } + + return true; +} + int ScsiDump::DumpRestore() { - const auto inq_info = GetDeviceInfo(); + inquiry_info_t inq_info; + if (!GetDeviceInfo(inq_info)) { + return EXIT_FAILURE; + } fstream fs; fs.open(filename, (restore ? ios::in : ios::out) | ios::binary); @@ -510,58 +634,28 @@ int ScsiDump::DumpRestore() const auto duration = chrono::duration_cast(stop_time - start_time).count(); - cout << divider_str << "\n"; + cout << DIVIDER << "\n"; cout << "Transfered : " << inq_info.capacity * inq_info.sector_size << " bytes [" << inq_info.capacity * inq_info.sector_size / 1024 / 1024 << "MiB]\n"; cout << "Total time: " << duration << " seconds (" << duration / 60 << " minutes\n"; cout << "Averate transfer rate: " << (inq_info.capacity * inq_info.sector_size / 8) / duration << " bytes per second (" << (inq_info.capacity * inq_info.sector_size / 8) / duration / 1024 << " KiB per second)\n"; - cout << divider_str << "\n"; + cout << DIVIDER << "\n"; if (properties_file && !restore) { - GeneratePropertiesFile(filename, inq_info); + inq_info.GeneratePropertiesFile(filename + ".properties"); } return EXIT_SUCCESS; } -ScsiDump::inquiry_info_t ScsiDump::GetDeviceInfo() +bool ScsiDump::GetDeviceInfo(inquiry_info_t& inq_info) { - // Assert RST for 1 ms - bus->SetRST(true); - const timespec ts = {.tv_sec = 0, .tv_nsec = 1000 * 1000}; - nanosleep(&ts, nullptr); - bus->SetRST(false); + DisplayBoardId(); - cout << divider_str << "\n"; - cout << "PiSCSI board ID: " << initiator_id << "\n"; - cout << divider_str << "\n" << flush; - cout << "Target device is " << target_id << ":" << target_lun << "\n"; - - Inquiry(); - - inquiry_info_t inq_info; - - // Display INQUIRY information - array str = {}; - memcpy(str.data(), &buffer[8], 8); - cout << "Vendor: " << str.data() << "\n"; - inq_info.vendor = string(str.data()); - - str.fill(0); - memcpy(str.data(), &buffer[16], 16); - cout << "Product: " << str.data() << "\n"; - inq_info.product = string(str.data()); - - str.fill(0); - memcpy(str.data(), &buffer[32], 4); - cout << "Revision: " << str.data() << "\n" << flush; - inq_info.revision = string(str.data()); - - if (const auto type = static_cast(buffer[0]); - type != device_type::direct_access && type != device_type::cd_rom && type != device_type::optical_memory) { - throw parser_exception("Invalid device type, supported types are DIRECT ACCESS, CD-ROM and OPTICAL MEMORY"); + if (!DisplayInquiry(inq_info, true)) { + return false; } TestUnitReady(); @@ -572,35 +666,28 @@ ScsiDump::inquiry_info_t ScsiDump::GetDeviceInfo() inq_info.capacity = capacity; inq_info.sector_size = sector_size; - cout << "Sectors: " << capacity << "\n" - << "Sector size: " << sector_size << " bytes\n" - << "Capacity: " << sector_size * capacity / 1024 / 1024 << " MiB (" << sector_size * capacity + cout << "Sectors: " << capacity << "\n" + << "Sector size: " << sector_size << " bytes\n" + << "Capacity: " << sector_size * capacity / 1024 / 1024 << " MiB (" << sector_size * capacity << " bytes)\n" - << divider_str << "\n\n" + << DIVIDER << "\n\n" << flush; - return inq_info; + return true; } -void ScsiDump::GeneratePropertiesFile(const string& filename, const inquiry_info_t& inq_info) +void ScsiDump::inquiry_info::GeneratePropertiesFile(const string& property_file) const { - const string prop_filename = filename + ".properties"; - stringstream prop_stream; + ofstream prop_stream(property_file); prop_stream << "{" << endl; - prop_stream << " \"vendor\": \"" << inq_info.vendor << "\"," << endl; - prop_stream << " \"product\": \"" << inq_info.product << "\"," << endl; - prop_stream << " \"revision\": \"" << inq_info.revision << "\"," << endl; - prop_stream << " \"block_size\": \"" << inq_info.sector_size << "\"," << endl; + prop_stream << " \"vendor\": \"" << vendor << "\"," << endl; + prop_stream << " \"product\": \"" << product << "\"," << endl; + prop_stream << " \"revision\": \"" << revision << "\"," << endl; + prop_stream << " \"block_size\": \"" << sector_size << "\"" << endl; prop_stream << "}" << endl; - FILE* fp = fopen(prop_filename.c_str(), "w"); - if (fp) { - fputs(prop_stream.str().c_str(), fp); - } else { - spdlog::warn("Unable to open output file '" + prop_filename + "'"); - return; + if (prop_stream.fail()) { + spdlog::warn("Unable to create properties file '" + property_file + "'"); } - - fclose(fp); } diff --git a/cpp/scsidump/scsidump_core.h b/cpp/scsidump/scsidump_core.h index 64a82fa8..58feb419 100644 --- a/cpp/scsidump/scsidump_core.h +++ b/cpp/scsidump/scsidump_core.h @@ -10,45 +10,52 @@ #pragma once #include "hal/bus.h" -#include "shared/scsi.h" #include #include #include #include +#include +#include using namespace std; +class phase_exception : public runtime_error +{ + using runtime_error::runtime_error; +}; + class ScsiDump { - static const int MINIMUM_BUFFER_SIZE = 1024 * 64; - static const int DEFAULT_BUFFER_SIZE = 1024 * 1024; public: + ScsiDump() = default; ~ScsiDump() = default; int run(const span); - struct inquiry_info_struct { + struct inquiry_info { string vendor; string product; string revision; uint32_t sector_size; uint64_t capacity; - }; - using inquiry_info_t = struct inquiry_info_struct; - protected: - // Protected for testability - static void GeneratePropertiesFile(const string& filename, const inquiry_info_t& inq_info); + void GeneratePropertiesFile(const string&) const; + }; + using inquiry_info_t = struct inquiry_info; private: + bool Banner(span) const; bool Init() const; void ParseArguments(span); + void DisplayBoardId() const; + void ScanBus(); + bool DisplayInquiry(inquiry_info_t&, bool); int DumpRestore(); - inquiry_info_t GetDeviceInfo(); - void WaitPhase(phase_t) const; + bool GetDeviceInfo(inquiry_info_t&); + void WaitForPhase(phase_t) const; void Selection() const; void Command(scsi_defs::scsi_command, vector&) const; void DataIn(int); @@ -65,7 +72,7 @@ class ScsiDump void WaitForBusy() const; static void CleanUp(); - static void KillHandler(int); + static void TerminationHandler(int); // A static instance is needed because of the signal handler static inline unique_ptr bus; @@ -80,9 +87,41 @@ class ScsiDump string filename; + bool inquiry = false; + + bool scan_bus = false; + bool restore = false; bool properties_file = false; - static inline const string divider_str = "----------------------------------------"; + static const int MINIMUM_BUFFER_SIZE = 1024 * 64; + static const int DEFAULT_BUFFER_SIZE = 1024 * 1024; + + static inline const string DIVIDER = "----------------------------------------"; + + static inline const unordered_map DEVICE_TYPES = { + { byte{0}, "Direct Access" }, + { byte{1}, "Sequential Access" }, + { byte{2}, "Printer" }, + { byte{3}, "Processor" }, + { byte{4}, "Write-Once" }, + { byte{5}, "CD-ROM/DVD/BD/DVD-RAM" }, + { byte{6}, "Scanner" }, + { byte{7}, "Optical Memory" }, + { byte{8}, "Media Changer" }, + { byte{9}, "Communications" }, + { byte{10}, "Graphic Arts Pre-Press" }, + { byte{11}, "Graphic Arts Pre-Press" }, + { byte{12}, "Storage Array Controller" }, + { byte{13}, "Enclosure Services" }, + { byte{14}, "Simplified Direct Access" }, + { byte{15}, "Optical Card Reader/Writer" }, + { byte{16}, "Bridge Controller" }, + { byte{17}, "Object-based Storage" }, + { byte{18}, "Automation/Drive Interface" }, + { byte{19}, "Security Manager" }, + { byte{20}, "Host Managed Zoned Block" }, + { byte{30}, "Well Known Logical Unit" } + }; }; diff --git a/cpp/test/gpiobus_raspberry_test.cpp b/cpp/test/gpiobus_raspberry_test.cpp index 6b597013..5a94d670 100644 --- a/cpp/test/gpiobus_raspberry_test.cpp +++ b/cpp/test/gpiobus_raspberry_test.cpp @@ -65,7 +65,7 @@ TEST(GpiobusRaspberry, GetDtRanges) EXPECT_EQ(0x20000000, GPIOBUS_Raspberry::bcm_host_get_peripheral_address()); DeleteTempFile("/proc/device-tree/soc/ranges"); - CleanUpAllTempFiles(); + remove_all(test_data_temp_path); } TEST(GpiobusRaspberry, GetDat) diff --git a/cpp/test/scsidump_test.cpp b/cpp/test/scsidump_test.cpp index 473fc7ac..2e055157 100644 --- a/cpp/test/scsidump_test.cpp +++ b/cpp/test/scsidump_test.cpp @@ -1,72 +1,66 @@ - //--------------------------------------------------------------------------- // // SCSI Target Emulator PiSCSI // for Raspberry Pi // // Copyright (C) 2022 akuker +// Copyright (C) 2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "mocks.h" +#include + #include "scsidump/scsidump_core.h" #include "test/test_shared.h" -#include -#include using namespace std; using namespace filesystem; -class TestableScsidump : public ScsiDump -{ - public: - static void PublicGeneratePropertiesFile(const string& filename, const inquiry_info_t& inq_info) - { - ScsiDump::GeneratePropertiesFile(filename, inq_info); - } -}; - TEST(ScsiDumpTest, GeneratePropertiesFile) { - // Basic test - const string prop_file_name = "test.properties"; - ScsiDump::inquiry_info_t test_data = { + // Basic test + auto filename = CreateTempFile(0); + ScsiDump::inquiry_info_t test_data = { .vendor = "PISCSI", .product = "TEST PRODUCT", .revision = "REV1", .sector_size = 1000, .capacity = 100}; - TestableScsidump::PublicGeneratePropertiesFile("test", test_data); + test_data.GeneratePropertiesFile(filename); string expected_str = "{\n" " \"vendor\": \"PISCSI\",\n" " \"product\": \"TEST PRODUCT\",\n" " \"revision\": \"REV1\",\n" - " \"block_size\": \"1000\",\n}" + " \"block_size\": \"1000\"\n}" "\n"; - EXPECT_EQ(ReadTempFileToString(prop_file_name), expected_str); + EXPECT_EQ(expected_str, ReadTempFileToString(filename)); // Long string test + filename = CreateTempFile(0); test_data = {.vendor = "01234567", .product = "0123456789ABCDEF", .revision = "0123", .sector_size = UINT32_MAX, .capacity = UINT64_MAX}; - TestableScsidump::PublicGeneratePropertiesFile("test", test_data); + test_data.GeneratePropertiesFile(filename); expected_str = "{\n" " \"vendor\": \"01234567\",\n" " \"product\": \"0123456789ABCDEF\",\n" " \"revision\": \"0123\",\n" - " \"block_size\": \"4294967295\",\n" + " \"block_size\": \"4294967295\"\n" "}\n"; - EXPECT_EQ(ReadTempFileToString(prop_file_name), expected_str); + EXPECT_EQ(expected_str, ReadTempFileToString(filename)); + remove(filename); // Empty data test + filename = CreateTempFile(0); test_data = {.vendor = "", .product = "", .revision = "", .sector_size = 0, .capacity = 0}; - TestableScsidump::PublicGeneratePropertiesFile("test", test_data); + test_data.GeneratePropertiesFile(filename); expected_str = "{\n" " \"vendor\": \"\",\n" " \"product\": \"\",\n" " \"revision\": \"\",\n" - " \"block_size\": \"0\",\n" + " \"block_size\": \"0\"\n" "}\n"; - EXPECT_EQ(ReadTempFileToString(prop_file_name), expected_str); + EXPECT_EQ(expected_str, ReadTempFileToString(filename)); + remove(filename); } diff --git a/cpp/test/test_shared.cpp b/cpp/test/test_shared.cpp index 0e913460..91e8c4f6 100644 --- a/cpp/test/test_shared.cpp +++ b/cpp/test/test_shared.cpp @@ -121,24 +121,20 @@ void CreateTempFileWithData(const string& filename, vector& data) fclose(fp); } +// TODO Move this code, it is not shared void DeleteTempFile(const string& filename) { - path temp_file = test_data_temp_path; - temp_file += path(filename); - remove(temp_file); -} - -void CleanUpAllTempFiles() -{ - remove_all(test_data_temp_path); + path temp_file = test_data_temp_path; + temp_file += path(filename); + remove(temp_file); } string ReadTempFileToString(const string& filename) { const path temp_file = test_data_temp_path / path(filename); - ifstream in_fs(temp_file); + ifstream in(temp_file); stringstream buffer; - buffer << in_fs.rdbuf(); + buffer << in.rdbuf(); return buffer.str(); } diff --git a/cpp/test/test_shared.h b/cpp/test/test_shared.h index 97d59606..8c87f294 100644 --- a/cpp/test/test_shared.h +++ b/cpp/test/test_shared.h @@ -35,8 +35,6 @@ path CreateTempFileWithData(span); void CreateTempFileWithData(const string&, vector&); void DeleteTempFile(const string&); -// Call this at the end of every test case to make sure things are cleaned up -void CleanUpAllTempFiles(); string ReadTempFileToString(const string& filename); diff --git a/doc/scsidump.1 b/doc/scsidump.1 index 0fe2cb52..4ec16147 100644 --- a/doc/scsidump.1 +++ b/doc/scsidump.1 @@ -10,6 +10,8 @@ scsidump \- SCSI disk dumping tool for PiSCSI [\fB\-r\fR] [\fB\-v\fR] [\fB\-p\fR] +[\fB\-I\fR] ID[:LUN] +[\fB\-S\fR] .SH DESCRIPTION .B scsidump has two modes of operation: dump and restore. These can be used with physical storage media, including hard drives and magneto optical drives. Dump mode can be used with read-only media such as CD/DVD drives. @@ -47,6 +49,12 @@ Enable verbose logging. .TP .BR \-p\fI Generate a .properties file that is compatible with the PiSCSI web interface. The output filename will match the image filename with ".properties" appended. The generated file should be moved to ~/.config/piscsi. +.TP +.BR \-I\fI " "\fIID[:LUN] +Display INQUIRY data of ID[:LUN]. +.TP +.BR \-S\fI +Scan SCSI bus for devices. .SH EXAMPLES Dump Mode: [SCSI Drive] ---> [PiSCSI host] diff --git a/doc/scsidump_man_page.txt b/doc/scsidump_man_page.txt index a39e64bb..70d63389 100644 --- a/doc/scsidump_man_page.txt +++ b/doc/scsidump_man_page.txt @@ -1,51 +1,78 @@ !! ------ THIS FILE IS AUTO_GENERATED! DO NOT MANUALLY UPDATE!!! -!! ------ The native file is scsidump.1. Re-run 'make docs' after updating - - -scsidump(1) General Commands Manual scsidump(1) +!! ------ The native file is scsidump.1. Re-run 'make docs' after updating\n\n +scsidump(1) General Commands Manual scsidump(1) NAME scsidump - SCSI disk dumping tool for PiSCSI SYNOPSIS - scsidump -t ID[:LUN] [-i BID] -f FILE [-s BUFFER_SIZE] [-r] [-v] [-p] + scsidump -t ID[:LUN] [-i BID] -f FILE [-s BUFFER_SIZE] [-r] [-v] [-p] + [-I] ID[:LUN] [-S] DESCRIPTION - scsidump has two modes of operation: dump and restore. These can be used with physical storage media, including hard drives and magneto optical drives. Dump mode can be used with read-only media such as CD/DVD drives. + scsidump has two modes of operation: dump and restore. These can be + used with physical storage media, including hard drives and magneto op‐ + tical drives. Dump mode can be used with read-only media such as CD/DVD + drives. - When operating in dump mode, scsidump will copy all data from a remote SCSI drive to an image on the local filesystem. If enabled, it will also generate a .properties file that can be used to more closely emulate the source drive. + When operating in dump mode, scsidump will copy all data from a remote + SCSI drive to an image on the local filesystem. If enabled, it will + also generate a .properties file that can be used to more closely emu‐ + late the source drive. - If you are operating in restore mode, scsidump will copy the data from a local binary image to a remote physical SCSI drive. The remote SCSI drive MUST be writable. + If you are operating in restore mode, scsidump will copy the data from + a local binary image to a remote physical SCSI drive. The remote SCSI + drive MUST be writable. NOTES - scsidump requires either a direct connection (one without transceivers) or a FULLSPEC PiSCSI/RaSCSI board. + scsidump requires either a direct connection (one without transceivers) + or a FULLSPEC PiSCSI/RaSCSI board. - If the generated drive image is intended to be used with PiSCSI, the drive image should be moved to ~/images (or the location specified to the piscsi service). + If the generated drive image is intended to be used with PiSCSI, the + drive image should be moved by the user to ~/images (or the location + specified to the piscsi service). OPTIONS -t ID[:LUN] - SCSI ID and optional LUN of the remote SCSI device. The remote SCSI device will be functioning as the "Target" device. + SCSI ID and optional LUN of the remote SCSI device. The remote + SCSI device will be functioning as the "Target" device. - -i BID SCSI ID of the PiSCSI device. If not specified, the PiSCSI device will use ID 7. The PiSCSI host will be functioning as the "Initiator" device. + -i BID SCSI ID of the PiSCSI device. If not specified, the PiSCSI de‐ + vice will use ID 7. The PiSCSI host will be functioning as the + "Initiator" device. -f FILE Path to the dump file. -s BUFFER_SIZE - The transfer buffer size, specified in bytes. Default is 1 MiB. This is specified in bytes with a minimum value of 65536 (64 KiB). + The transfer buffer size, specified in bytes. Default is 1 MiB. + This is specified in bytes with a minimum value of 65536 (64 + KiB). -r Run in restore mode. Defaults to dump mode if not specified. -v Enable verbose logging. - -p Generate a .properties file that is compatible with the PiSCSI web interface. The output filename will match the image filename with ".properties" appended. The generated file should be moved to ~/.config/piscsi. + -p Generate a .properties file that is compatible with the PiSCSI + web interface. The output filename will match the image filename + with ".properties" appended. The generated file should be moved + to ~/.config/piscsi. + + -I ID[:LUN] + Display INQUIRY data of ID[:LUN]. + + -S Scan SCSI bus for devices. EXAMPLES - Dump Mode: [SCSI Drive] ---> [PiSCSI host] Launch scsidump to dump an all data from SCSI ID 3 with block size 64 KiB, store it to the local filesystem as a drive image named outimage.hda, and generate the outimage.hda.properties file with the drive's INQUIRY - information: + Dump Mode: [SCSI Drive] ---> [PiSCSI host] Launch scsidump to dump an + all data from SCSI ID 3 with block size 64 KiB, store it to the local + filesystem as a drive image named outimage.hda, and generate the outim‐ + age.hda.properties file with the drive's INQUIRY information: scsidump -t 3 -f ./outimage.hda -s 65536 -p - Restore Mode: [PiSCSI host] ---> [SCSI Drive] Launch scsidump to restore/upload a drive image from the local file system to SCSI ID 0 with block size 1MiB: + Restore Mode: [PiSCSI host] ---> [SCSI Drive] Launch scsidump to re‐ + store/upload a drive image from the local file system to SCSI ID 0 with + block size 1MiB: scsidump -r -t 0 -f ./outimage.hda -s 1048576 SEE ALSO @@ -53,4 +80,4 @@ SEE ALSO Full documentation is available at: - scsidump(1) + scsidump(1) From b7cb23e391a37dd9beba2304ed8229175f236518 Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Mon, 30 Oct 2023 13:32:45 +0100 Subject: [PATCH 43/87] Add statistics and make scsictl accept generic key/value parameters (#1237/#1238) (#1262) * Add statistics and make scsictl accept generic key/value parameters --- cpp/devices/disk.cpp | 39 ++++++++++++++ cpp/devices/disk.h | 10 +++- cpp/devices/disk_cache.cpp | 45 +++++++++++++--- cpp/devices/disk_cache.h | 17 +++++- cpp/devices/disk_track.cpp | 8 ++- cpp/devices/disk_track.h | 5 +- cpp/devices/primary_device.h | 8 ++- cpp/devices/scsi_daynaport.cpp | 30 +++++++++-- cpp/devices/scsi_daynaport.h | 14 +++-- cpp/devices/scsi_printer.cpp | 53 +++++++++++++++++- cpp/devices/scsi_printer.h | 12 +++++ cpp/piscsi/piscsi_core.cpp | 10 ++-- cpp/piscsi/piscsi_response.cpp | 89 ++++++++++++++++++++++++++----- cpp/piscsi/piscsi_response.h | 10 +++- cpp/piscsi_interface.proto | 51 ++++++++++++++++-- cpp/scsictl/scsictl_commands.cpp | 57 ++++++++++++++++---- cpp/scsictl/scsictl_commands.h | 1 + cpp/scsictl/scsictl_core.cpp | 12 +++-- cpp/scsictl/scsictl_display.cpp | 32 +++++++++++ cpp/scsictl/scsictl_display.h | 1 + cpp/shared/protobuf_util.cpp | 26 ++++++--- cpp/shared/protobuf_util.h | 2 +- cpp/test/piscsi_response_test.cpp | 46 ++++++++++++---- cpp/test/protobuf_util_test.cpp | 25 ++++++--- doc/scsictl.1 | 38 +++++++------ doc/scsictl_man_page.txt | 21 +++++--- 26 files changed, 557 insertions(+), 105 deletions(-) diff --git a/cpp/devices/disk.cpp b/cpp/devices/disk.cpp index 8a32273e..bc8b53d0 100644 --- a/cpp/devices/disk.cpp +++ b/cpp/devices/disk.cpp @@ -273,6 +273,9 @@ bool Disk::Eject(bool force) // The image file for this drive is not in use anymore UnreserveFile(); + + sector_read_count = 0; + sector_write_count = 0; } return status; @@ -506,6 +509,8 @@ int Disk::Read(span buf, uint64_t block) throw scsi_exception(sense_key::medium_error, asc::read_fault); } + ++sector_read_count; + return GetSectorSizeInBytes(); } @@ -518,6 +523,8 @@ void Disk::Write(span buf, uint64_t block) if (!cache->WriteSector(buf, static_cast(block))) { throw scsi_exception(sense_key::medium_error, asc::write_fault); } + + ++sector_write_count; } void Disk::Seek() @@ -711,3 +718,35 @@ bool Disk::SetConfiguredSectorSize(const DeviceFactory& device_factory, uint32_t return true; } + +vector Disk::GetStatistics() const +{ + vector statistics = PrimaryDevice::GetStatistics(); + + // Enrich cache statistics with device information before adding them to device statistics + if (cache) { + for (auto& s : cache->GetStatistics(IsReadOnly())) { + s.set_id(GetId()); + s.set_unit(GetLun()); + statistics.push_back(s); + } + } + + PbStatistics s; + s.set_id(GetId()); + s.set_unit(GetLun()); + + s.set_category(PbStatisticsCategory::CATEGORY_INFO); + + s.set_key(SECTOR_READ_COUNT); + s.set_value(sector_read_count); + statistics.push_back(s); + + if (!IsReadOnly()) { + s.set_key(SECTOR_WRITE_COUNT); + s.set_value(sector_write_count); + statistics.push_back(s); + } + + return statistics; +} diff --git a/cpp/devices/disk.h b/cpp/devices/disk.h index 1b22262d..7364ad08 100644 --- a/cpp/devices/disk.h +++ b/cpp/devices/disk.h @@ -8,7 +8,6 @@ // XM6i // Copyright (C) 2010-2015 isaki@NetBSD.org // -// Imported sava's Anex86/T98Next image and MO format support patch. // Comments translated to english by akuker. // //--------------------------------------------------------------------------- @@ -16,6 +15,7 @@ #pragma once #include "shared/scsi.h" +#include "shared/piscsi_util.h" #include "device_factory.h" #include "disk_track.h" #include "disk_cache.h" @@ -42,6 +42,12 @@ class Disk : public StorageDevice, private ScsiBlockCommands // Sector size shift count (9=512, 10=1024, 11=2048, 12=4096) uint32_t size_shift_count = 0; + uint64_t sector_read_count = 0; + uint64_t sector_write_count = 0; + + inline static const string SECTOR_READ_COUNT = "sector_read_count"; + inline static const string SECTOR_WRITE_COUNT = "sector_write_count"; + public: using StorageDevice::StorageDevice; @@ -62,6 +68,8 @@ public: bool SetConfiguredSectorSize(const DeviceFactory&, uint32_t); void FlushCache() override; + vector GetStatistics() const override; + private: // Commands covered by the SCSI specifications (see https://www.t10.org/drafts.htm) diff --git a/cpp/devices/disk_cache.cpp b/cpp/devices/disk_cache.cpp index 23bd724a..7ab69c19 100644 --- a/cpp/devices/disk_cache.cpp +++ b/cpp/devices/disk_cache.cpp @@ -27,11 +27,11 @@ DiskCache::DiskCache(const string& path, int size, uint32_t blocks, off_t imgoff assert(imgoff >= 0); } -bool DiskCache::Save() const +bool DiskCache::Save() { // Save valid tracks return ranges::none_of(cache.begin(), cache.end(), [this](const cache_t& c) - { return c.disktrk != nullptr && !c.disktrk->Save(sec_path); }); + { return c.disktrk != nullptr && !c.disktrk->Save(sec_path, cache_miss_write_count); }); } shared_ptr DiskCache::GetTrack(uint32_t block) @@ -120,7 +120,7 @@ shared_ptr DiskCache::Assign(int track) } // Save this track - if (!cache[c].disktrk->Save(sec_path)) { + if (!cache[c].disktrk->Save(sec_path, cache_miss_write_count)) { return nullptr; } @@ -156,17 +156,16 @@ bool DiskCache::Load(int index, int track, shared_ptr disktrk) sectors = 0x100; } - // Create a disk track if (disktrk == nullptr) { disktrk = make_shared(); } - // Initialize disk track disktrk->Init(track, sec_size, sectors, cd_raw, imgoffset); // Try loading - if (!disktrk->Load(sec_path)) { - // Failure + if (!disktrk->Load(sec_path, cache_miss_read_count)) { + ++read_error_count; + return false; } @@ -190,3 +189,35 @@ void DiskCache::UpdateSerialNumber() } } +vector DiskCache::GetStatistics(bool is_read_only) const +{ + vector statistics; + + PbStatistics s; + + s.set_category(PbStatisticsCategory::CATEGORY_INFO); + + s.set_key(CACHE_MISS_READ_COUNT); + s.set_value(cache_miss_read_count); + statistics.push_back(s); + + if (!is_read_only) { + s.set_key(CACHE_MISS_WRITE_COUNT); + s.set_value(cache_miss_write_count); + statistics.push_back(s); + } + + s.set_category(PbStatisticsCategory::CATEGORY_ERROR); + + s.set_key(READ_ERROR_COUNT); + s.set_value(read_error_count); + statistics.push_back(s); + + if (!is_read_only) { + s.set_key(WRITE_ERROR_COUNT); + s.set_value(write_error_count); + statistics.push_back(s); + } + + return statistics; +} diff --git a/cpp/devices/disk_cache.h b/cpp/devices/disk_cache.h index 4400e27d..ec486edd 100644 --- a/cpp/devices/disk_cache.h +++ b/cpp/devices/disk_cache.h @@ -15,18 +15,30 @@ #pragma once +#include "generated/piscsi_interface.pb.h" #include #include #include #include using namespace std; +using namespace piscsi_interface; class DiskCache { // Number of tracks to cache static const int CACHE_MAX = 16; + uint64_t read_error_count = 0; + uint64_t write_error_count = 0; + uint64_t cache_miss_read_count = 0; + uint64_t cache_miss_write_count = 0; + + inline static const string READ_ERROR_COUNT = "read_error_count"; + inline static const string WRITE_ERROR_COUNT = "write_error_count"; + inline static const string CACHE_MISS_READ_COUNT = "cache_miss_read_count"; + inline static const string CACHE_MISS_WRITE_COUNT = "cache_miss_write_count"; + public: // Internal data definition @@ -40,11 +52,12 @@ public: void SetRawMode(bool b) { cd_raw = b; } // CD-ROM raw mode setting - // Access - bool Save() const; // Save and release all + bool Save(); // Save and release all bool ReadSector(span, uint32_t); // Sector Read bool WriteSector(span, uint32_t); // Sector Write + vector GetStatistics(bool) const; + private: // Internal Management diff --git a/cpp/devices/disk_track.cpp b/cpp/devices/disk_track.cpp index 35e1e56b..e2e2e01d 100644 --- a/cpp/devices/disk_track.cpp +++ b/cpp/devices/disk_track.cpp @@ -48,7 +48,7 @@ void DiskTrack::Init(int track, int size, int sectors, bool raw, off_t imgoff) dt.imgoffset = imgoff; } -bool DiskTrack::Load(const string& path) +bool DiskTrack::Load(const string& path, uint64_t& cache_miss_read_count) { // Not needed if already loaded if (dt.init) { @@ -56,6 +56,8 @@ bool DiskTrack::Load(const string& path) return true; } + ++cache_miss_read_count; + // Calculate offset (previous tracks are considered to hold 256 sectors) off_t offset = ((off_t)dt.track << 8); if (dt.raw) { @@ -138,7 +140,7 @@ bool DiskTrack::Load(const string& path) return true; } -bool DiskTrack::Save(const string& path) +bool DiskTrack::Save(const string& path, uint64_t& cache_miss_write_count) { // Not needed if not initialized if (!dt.init) { @@ -150,6 +152,8 @@ bool DiskTrack::Save(const string& path) return true; } + ++cache_miss_write_count; + // Need to write assert(dt.buffer); assert((dt.sectors > 0) && (dt.sectors <= 0x100)); diff --git a/cpp/devices/disk_track.h b/cpp/devices/disk_track.h index 91ca5e86..c61c2f29 100644 --- a/cpp/devices/disk_track.h +++ b/cpp/devices/disk_track.h @@ -50,10 +50,9 @@ private: friend class DiskCache; void Init(int track, int size, int sectors, bool raw = false, off_t imgoff = 0); - bool Load(const string& path); - bool Save(const string& path); + bool Load(const string& path, uint64_t&); + bool Save(const string& path, uint64_t&); - // Read / Write bool ReadSector(span, int) const; // Sector Read bool WriteSector(span buf, int); // Sector Write diff --git a/cpp/devices/primary_device.h b/cpp/devices/primary_device.h index 682a4f97..c30bc123 100644 --- a/cpp/devices/primary_device.h +++ b/cpp/devices/primary_device.h @@ -54,7 +54,13 @@ public: void Reset() override; virtual void FlushCache() { - // Devices with a cache have to implement this method + // Devices with a cache have to override this method + } + + virtual vector GetStatistics() const { + // Devices which provide statistics have to override this method + + return vector(); } protected: diff --git a/cpp/devices/scsi_daynaport.cpp b/cpp/devices/scsi_daynaport.cpp index db46ea16..a945bd17 100644 --- a/cpp/devices/scsi_daynaport.cpp +++ b/cpp/devices/scsi_daynaport.cpp @@ -119,7 +119,7 @@ vector SCSIDaynaPort::InquiryInternal() const // - The SCSI/Link apparently has about 6KB buffer space for packets. // //--------------------------------------------------------------------------- -int SCSIDaynaPort::Read(cdb_t cdb, vector& buf, uint64_t) const +int SCSIDaynaPort::Read(cdb_t cdb, vector& buf, uint64_t) { int rx_packet_size = 0; const auto response = (scsi_resp_read_t*)buf.data(); @@ -155,6 +155,8 @@ int SCSIDaynaPort::Read(cdb_t cdb, vector& buf, uint64_t) const return DAYNAPORT_READ_HEADER_SZ; } + byte_read_count += rx_packet_size * read_count; + LogTrace("Packet Size " + to_string(rx_packet_size) + ", read count: " + to_string(read_count)); // This is a very basic filter to prevent unnecessary packets from @@ -252,17 +254,19 @@ int SCSIDaynaPort::Read(cdb_t cdb, vector& buf, uint64_t) const // XX XX ... is the actual packet // //--------------------------------------------------------------------------- -bool SCSIDaynaPort::Write(cdb_t cdb, span buf) const +bool SCSIDaynaPort::Write(cdb_t cdb, span buf) { if (const int data_format = cdb[5]; data_format == 0x00) { const int data_length = GetInt16(cdb, 3); tap.Send(buf.data(), data_length); + byte_write_count += data_length; LogTrace("Transmitted " + to_string(data_length) + " byte(s) (00 format)"); } else if (data_format == 0x80) { // The data length is specified in the first 2 bytes of the payload const int data_length = buf[1] + ((static_cast(buf[0]) & 0xff) << 8); tap.Send(&(buf.data()[4]), data_length); + byte_write_count += data_length; LogTrace("Transmitted " + to_string(data_length) + "byte(s) (80 format)"); } else { @@ -305,7 +309,7 @@ void SCSIDaynaPort::TestUnitReady() EnterStatusPhase(); } -void SCSIDaynaPort::Read6() const +void SCSIDaynaPort::Read6() { // Get record number and block number const uint32_t record = GetInt24(GetController()->GetCmd(), 1) & 0x1fffff; @@ -478,3 +482,23 @@ void SCSIDaynaPort::EnableInterface() const EnterStatusPhase(); } +vector SCSIDaynaPort::GetStatistics() const +{ + vector statistics = PrimaryDevice::GetStatistics(); + + PbStatistics s; + s.set_id(GetId()); + s.set_unit(GetLun()); + + s.set_category(PbStatisticsCategory::CATEGORY_INFO); + + s.set_key(BYTE_READ_COUNT); + s.set_value(byte_read_count); + statistics.push_back(s); + + s.set_key(BYTE_WRITE_COUNT); + s.set_value(byte_write_count); + statistics.push_back(s); + + return statistics; +} diff --git a/cpp/devices/scsi_daynaport.h b/cpp/devices/scsi_daynaport.h index dc2ede6a..94f45551 100644 --- a/cpp/devices/scsi_daynaport.h +++ b/cpp/devices/scsi_daynaport.h @@ -44,6 +44,12 @@ //=========================================================================== class SCSIDaynaPort : public PrimaryDevice { + uint64_t byte_read_count = 0; + uint64_t byte_write_count = 0; + + inline static const string BYTE_READ_COUNT = "byte_read_count"; + inline static const string BYTE_WRITE_COUNT = "byte_write_count"; + public: explicit SCSIDaynaPort(int); @@ -56,19 +62,21 @@ public: // Commands vector InquiryInternal() const override; - int Read(cdb_t, vector&, uint64_t) const; - bool Write(cdb_t, span) const; + int Read(cdb_t, vector&, uint64_t); + bool Write(cdb_t, span); int RetrieveStats(cdb_t, vector&) const; void TestUnitReady() override; - void Read6() const; + void Read6(); void Write6() const; void RetrieveStatistics() const; void SetInterfaceMode() const; void SetMcastAddr() const; void EnableInterface() const; + vector GetStatistics() const override; + static const int DAYNAPORT_BUFFER_SIZE = 0x1000000; static const int CMD_SCSILINK_STATS = 0x09; diff --git a/cpp/devices/scsi_printer.cpp b/cpp/devices/scsi_printer.cpp index f4399985..393e053e 100644 --- a/cpp/devices/scsi_printer.cpp +++ b/cpp/devices/scsi_printer.cpp @@ -115,6 +115,8 @@ void SCSIPrinter::Print() LogError("Transfer buffer overflow: Buffer size is " + to_string(GetController()->GetBuffer().size()) + " bytes, " + to_string(length) + " bytes expected"); + ++print_error_count; + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } @@ -129,6 +131,8 @@ void SCSIPrinter::SynchronizeBuffer() if (!out.is_open()) { LogWarn("Nothing to print"); + ++print_warning_count; + throw scsi_exception(sense_key::aborted_command); } @@ -145,6 +149,8 @@ void SCSIPrinter::SynchronizeBuffer() if (system(cmd.c_str())) { LogError("Printing file '" + filename + "' failed, the printing system might not be configured"); + ++print_error_count; + CleanUp(); throw scsi_exception(sense_key::aborted_command); @@ -157,6 +163,8 @@ void SCSIPrinter::SynchronizeBuffer() bool SCSIPrinter::WriteByteSequence(span buf) { + byte_receive_count += buf.size(); + if (!out.is_open()) { vector f(file_template.begin(), file_template.end()); f.push_back(0); @@ -165,6 +173,9 @@ bool SCSIPrinter::WriteByteSequence(span buf) const int fd = mkstemp(f.data()); if (fd == -1) { LogError("Can't create printer output file for pattern '" + filename + "': " + strerror(errno)); + + ++print_error_count; + return false; } close(fd); @@ -173,6 +184,8 @@ bool SCSIPrinter::WriteByteSequence(span buf) out.open(filename, ios::binary); if (out.fail()) { + ++print_error_count; + throw scsi_exception(sense_key::aborted_command); } @@ -183,5 +196,43 @@ bool SCSIPrinter::WriteByteSequence(span buf) out.write((const char *)buf.data(), buf.size()); - return !out.fail(); + const bool status = out.fail(); + if (!status) { + ++print_error_count; + } + + return !status; +} + +vector SCSIPrinter::GetStatistics() const +{ + vector statistics = PrimaryDevice::GetStatistics(); + + PbStatistics s; + s.set_id(GetId()); + s.set_unit(GetLun()); + + s.set_category(PbStatisticsCategory::CATEGORY_INFO); + + s.set_key(FILE_PRINT_COUNT); + s.set_value(file_print_count); + statistics.push_back(s); + + s.set_key(BYTE_RECEIVE_COUNT); + s.set_value(byte_receive_count); + statistics.push_back(s); + + s.set_category(PbStatisticsCategory::CATEGORY_ERROR); + + s.set_key(PRINT_ERROR_COUNT); + s.set_value(print_error_count); + statistics.push_back(s); + + s.set_category(PbStatisticsCategory::CATEGORY_WARNING); + + s.set_key(PRINT_WARNING_COUNT); + s.set_value(print_warning_count); + statistics.push_back(s); + + return statistics; } diff --git a/cpp/devices/scsi_printer.h b/cpp/devices/scsi_printer.h index e9f6c92d..50ef94b9 100644 --- a/cpp/devices/scsi_printer.h +++ b/cpp/devices/scsi_printer.h @@ -21,10 +21,20 @@ using namespace std; class SCSIPrinter : public PrimaryDevice, private ScsiPrinterCommands { + uint64_t file_print_count = 0; + uint64_t byte_receive_count = 0; + uint64_t print_error_count = 0; + uint64_t print_warning_count = 0; + static const int NOT_RESERVED = -2; static constexpr const char *PRINTER_FILE_PATTERN = "/piscsi_sclp-XXXXXX"; + inline static const string FILE_PRINT_COUNT = "file_print_count"; + inline static const string BYTE_RECEIVE_COUNT = "byte_receive_count"; + inline static const string PRINT_ERROR_COUNT = "print_error_count"; + inline static const string PRINT_WARNING_COUNT = "print_warning_count"; + public: explicit SCSIPrinter(int); @@ -39,6 +49,8 @@ public: bool WriteByteSequence(span) override; + vector GetStatistics() const override; + private: void TestUnitReady() override; diff --git a/cpp/piscsi/piscsi_core.cpp b/cpp/piscsi/piscsi_core.cpp index e2bf056e..1be72793 100644 --- a/cpp/piscsi/piscsi_core.cpp +++ b/cpp/piscsi/piscsi_core.cpp @@ -357,9 +357,8 @@ bool Piscsi::ExecuteCommand(CommandContext& context) break; case SERVER_INFO: - response.GetServerInfo(*result.mutable_server_info(), controller_manager.GetAllDevices(), - executor->GetReservedIds(), piscsi_image.GetDefaultFolder(), - GetParam(command, "folder_pattern"), GetParam(command, "file_pattern"), piscsi_image.GetDepth()); + response.GetServerInfo(*result.mutable_server_info(), command, controller_manager.GetAllDevices(), + executor->GetReservedIds(), piscsi_image.GetDefaultFolder(), piscsi_image.GetDepth()); context.WriteSuccessResult(result); break; @@ -408,6 +407,11 @@ bool Piscsi::ExecuteCommand(CommandContext& context) context.WriteSuccessResult(result); break; + case STATISTICS_INFO: + response.GetStatisticsInfo(*result.mutable_statistics_info(), controller_manager.GetAllDevices()); + context.WriteSuccessResult(result); + break; + case OPERATION_INFO: response.GetOperationInfo(*result.mutable_operation_info(), piscsi_image.GetDepth()); context.WriteSuccessResult(result); diff --git a/cpp/piscsi/piscsi_response.cpp b/cpp/piscsi/piscsi_response.cpp index 16ac25b5..28e7e1a2 100644 --- a/cpp/piscsi/piscsi_response.cpp +++ b/cpp/piscsi/piscsi_response.cpp @@ -227,19 +227,62 @@ void PiscsiResponse::GetDevicesInfo(const unordered_set>& devices, - const unordered_set& reserved_ids, const string& default_folder, const string& folder_pattern, - const string& file_pattern, int scan_depth) const +void PiscsiResponse::GetServerInfo(PbServerInfo& server_info, const PbCommand& command, + const unordered_set>& devices, const unordered_set& reserved_ids, + const string& default_folder, int scan_depth) const { - GetVersionInfo(*server_info.mutable_version_info()); - GetLogLevelInfo(*server_info.mutable_log_level_info()); - GetDeviceTypesInfo(*server_info.mutable_device_types_info()); - GetAvailableImages(server_info, default_folder, folder_pattern, file_pattern, scan_depth); - GetNetworkInterfacesInfo(*server_info.mutable_network_interfaces_info()); - GetMappingInfo(*server_info.mutable_mapping_info()); - GetDevices(devices, server_info, default_folder); - GetReservedIds(*server_info.mutable_reserved_ids_info(), reserved_ids); - GetOperationInfo(*server_info.mutable_operation_info(), scan_depth); + const vector command_operations = Split(GetParam(command, "operations"), ','); + set> operations; + for (const string& operation : command_operations) { + string op; + ranges::transform(operation, back_inserter(op), ::toupper); + operations.insert(op); + } + + if (!operations.empty()) { + spdlog::trace("Requested operation(s): " + Join(operations, ",")); + } + + if (HasOperation(operations, PbOperation::VERSION_INFO)) { + GetVersionInfo(*server_info.mutable_version_info()); + } + + if (HasOperation(operations, PbOperation::LOG_LEVEL_INFO)) { + GetLogLevelInfo(*server_info.mutable_log_level_info()); + } + + if (HasOperation(operations, PbOperation::DEVICE_TYPES_INFO)) { + GetDeviceTypesInfo(*server_info.mutable_device_types_info()); + } + + if (HasOperation(operations, PbOperation::DEFAULT_IMAGE_FILES_INFO)) { + GetAvailableImages(server_info, default_folder, GetParam(command, "folder_pattern"), + GetParam(command, "file_pattern"), scan_depth); + } + + if (HasOperation(operations, PbOperation::NETWORK_INTERFACES_INFO)) { + GetNetworkInterfacesInfo(*server_info.mutable_network_interfaces_info()); + } + + if (HasOperation(operations, PbOperation::MAPPING_INFO)) { + GetMappingInfo(*server_info.mutable_mapping_info()); + } + + if (HasOperation(operations, PbOperation::STATISTICS_INFO)) { + GetStatisticsInfo(*server_info.mutable_statistics_info(), devices); + } + + if (HasOperation(operations, PbOperation::DEVICES_INFO)) { + GetDevices(devices, server_info, default_folder); + } + + if (HasOperation(operations, PbOperation::RESERVED_IDS_INFO)) { + GetReservedIds(*server_info.mutable_reserved_ids_info(), reserved_ids); + } + + if (HasOperation(operations, PbOperation::OPERATION_INFO)) { + GetOperationInfo(*server_info.mutable_operation_info(), scan_depth); + } } void PiscsiResponse::GetVersionInfo(PbVersionInfo& version_info) const @@ -272,6 +315,21 @@ void PiscsiResponse::GetMappingInfo(PbMappingInfo& mapping_info) const } } +void PiscsiResponse::GetStatisticsInfo(PbStatisticsInfo& statistics_info, + const unordered_set>& devices) const +{ + for (const auto& device : devices) { + for (const auto& statistics : device->GetStatistics()) { + auto s = statistics_info.add_statistics(); + s->set_id(statistics.id()); + s->set_unit(statistics.unit()); + s->set_category(statistics.category()); + s->set_key(statistics.key()); + s->set_value(statistics.value()); + } + } +} + void PiscsiResponse::GetOperationInfo(PbOperationInfo& operation_info, int depth) const { auto operation = CreateOperation(operation_info, ATTACH, "Attach device, device-specific parameters are required"); @@ -324,6 +382,8 @@ void PiscsiResponse::GetOperationInfo(PbOperationInfo& operation_info, int depth CreateOperation(operation_info, MAPPING_INFO, "Get mapping of extensions to device types"); + CreateOperation(operation_info, STATISTICS_INFO, "Get statistics"); + CreateOperation(operation_info, RESERVED_IDS_INFO, "Get list of reserved device IDs"); operation = CreateOperation(operation_info, DEFAULT_FOLDER, "Set default image file folder"); @@ -469,3 +529,8 @@ bool PiscsiResponse::FilterMatches(const string& input, string_view pattern_lowe return true; } + +bool PiscsiResponse::HasOperation(const set>& operations, PbOperation operation) +{ + return operations.empty() || operations.contains(PbOperation_Name(operation)); +} diff --git a/cpp/piscsi/piscsi_response.h b/cpp/piscsi/piscsi_response.h index 549ea905..b095416f 100644 --- a/cpp/piscsi/piscsi_response.h +++ b/cpp/piscsi/piscsi_response.h @@ -11,9 +11,11 @@ #include "devices/device_factory.h" #include "devices/primary_device.h" +#include "shared/piscsi_util.h" #include "generated/piscsi_interface.pb.h" #include #include +#include using namespace std; using namespace filesystem; @@ -33,17 +35,19 @@ public: void GetDevicesInfo(const unordered_set>&, PbResult&, const PbCommand&, const string&) const; void GetDeviceTypesInfo(PbDeviceTypesInfo&) const; void GetVersionInfo(PbVersionInfo&) const; - void GetServerInfo(PbServerInfo&, const unordered_set>&, const unordered_set&, - const string&, const string&, const string&, int) const; + void GetServerInfo(PbServerInfo&, const PbCommand&, const unordered_set>&, + const unordered_set&, const string&, int) const; void GetNetworkInterfacesInfo(PbNetworkInterfacesInfo&) const; void GetMappingInfo(PbMappingInfo&) const; void GetLogLevelInfo(PbLogLevelInfo&) const; + void GetStatisticsInfo(PbStatisticsInfo&, const unordered_set>&) const; void GetOperationInfo(PbOperationInfo&, int) const; private: inline static const vector EMPTY_VECTOR; + // TODO Try to get rid of this field by having the device instead of the factory providing the device data const DeviceFactory device_factory; void GetDeviceProperties(const Device&, PbDeviceProperties&) const; @@ -59,4 +63,6 @@ private: static bool ValidateImageFile(const path&); static bool FilterMatches(const string&, string_view); + + static bool HasOperation(const set>&, PbOperation); }; diff --git a/cpp/piscsi_interface.proto b/cpp/piscsi_interface.proto index 1a1be45c..77706644 100644 --- a/cpp/piscsi_interface.proto +++ b/cpp/piscsi_interface.proto @@ -82,9 +82,12 @@ enum PbOperation { // Make medium writable (not possible for read-only media) UNPROTECT = 9; - // Gets the server information (PbServerInfo). Calling this operation should be avoided because it - // may return a lot of data. More specific other operations should be used instead. + // Gets the server information (PbServerInfo). Calling this operation without a list of operations should + // be avoided because this may return a lot of data. More specific other operations should be used instead. // Parameters: + // "operations": Optional case insensitive comma-separated list of operation names to return data for, + // e.g. "version_info,log_level_info". Unknown operation names are ignored. If this parameter is missing + // the full set of data supported by PbServerInfo is returned. // "folder_pattern": Optional filter, only folder names containing the case-insensitive pattern are returned // "file_pattern": Optional filter, only filenames containing the case-insensitive pattern are returned SERVER_INFO = 10; @@ -190,6 +193,9 @@ enum PbOperation { // Get operation meta data (PbOperationInfo) OPERATION_INFO = 31; + + // Get statistics (PbStatisticsInfo) + STATISTICS_INFO = 32; } // The operation parameter meta data. The parameter data type is provided by the protobuf API. @@ -286,7 +292,7 @@ message PbImageFile { string name = 1; // The assumed device type, based on the filename extension PbDeviceType type = 2; - // The file size in bytes, 0 for block devices + // The file size in bytes, 0 for block devices in /dev uint64 size = 3; bool read_only = 4; } @@ -311,6 +317,41 @@ message PbNetworkInterfacesInfo { repeated string name = 1; } +// Statistics categories ordered by increasing severity +enum PbStatisticsCategory { + CATEGORY_NONE = 0; + CATEGORY_INFO = 1; + CATEGORY_WARNING = 2; + CATEGORY_ERROR = 3; + } + +message PbStatistics { + PbStatisticsCategory category = 1; + // The device ID and LUN for this statistics item. Both are -1 if the item is not device specific. + int32 id = 2; + int32 unit = 3; + // A symbolic unique item name, may be used for I18N. Supported values and their categories: + // "read_error_count" (ERROR, SCHD/SCRM/SCMO/SCCD) + // "write_error_count" (ERROR, SCHD/SCRM/SCMO) + // "cache_miss_read_count" (INFO, SCHD/SCRM/SCMO/SCCD) + // "cache_miss_write_count" (INFO, SCHD/SCRM/SCMO) + // "sector_read_count" (INFO, SCHD/SCRM/SCMO/SCCD) + // "sector_write_count" (INFO, SCHD/SCRM/SCMO) + // "byte_read_count" (INFO, SCDP) + // "byte_write_count" (INFO, SCDP) + // "print_error_count" (ERROR, SCLP) + // "print_warning_count" (WARNING, SCLP) + // "file_print_count" (INFO, SCLP) + // "byte_receive_count" (INFO, SCLP) + string key = 4; + uint64 value = 5; +} + +// The information on collected statistics +message PbStatisticsInfo { + repeated PbStatistics statistics = 1; +} + // The device definition, sent from the client to the server message PbDeviceDefinition { int32 id = 1; @@ -407,6 +448,8 @@ message PbResult { PbReservedIdsInfo reserved_ids_info = 12; // The result of an OPERATION_INFO command PbOperationInfo operation_info = 13; + // The result of a STATISTICS_INFO command + PbStatisticsInfo statistics_info = 15; } } @@ -430,4 +473,6 @@ message PbServerInfo { PbDevicesInfo devices_info = 8; // The operation meta data PbOperationInfo operation_info = 9; + // The statistics + PbStatisticsInfo statistics_info = 10; } diff --git a/cpp/scsictl/scsictl_commands.cpp b/cpp/scsictl/scsictl_commands.cpp index 8cb55f60..53dea40b 100644 --- a/cpp/scsictl/scsictl_commands.cpp +++ b/cpp/scsictl/scsictl_commands.cpp @@ -81,6 +81,9 @@ bool ScsictlCommands::Execute(string_view log_level, string_view default_folder, case MAPPING_INFO: return CommandMappingInfo(); + case STATISTICS_INFO: + return CommandStatisticsInfo(); + case OPERATION_INFO: return CommandOperationInfo(); @@ -245,16 +248,43 @@ bool ScsictlCommands::CommandServerInfo() PbServerInfo server_info = result.server_info(); - cout << scsictl_display.DisplayVersionInfo(server_info.version_info()); - cout << scsictl_display.DisplayLogLevelInfo(server_info.log_level_info()); - cout << scsictl_display.DisplayImageFilesInfo(server_info.image_files_info()); - cout << scsictl_display.DisplayMappingInfo(server_info.mapping_info()); - cout << scsictl_display.DisplayNetworkInterfaces(server_info.network_interfaces_info()); - cout << scsictl_display.DisplayDeviceTypesInfo(server_info.device_types_info()); - cout << scsictl_display.DisplayReservedIdsInfo(server_info.reserved_ids_info()); - cout << scsictl_display.DisplayOperationInfo(server_info.operation_info()); + if (server_info.has_version_info()) { + cout << scsictl_display.DisplayVersionInfo(server_info.version_info()); + } - if (server_info.devices_info().devices_size()) { + if (server_info.has_log_level_info()) { + cout << scsictl_display.DisplayLogLevelInfo(server_info.log_level_info()); + } + + if (server_info.has_image_files_info()) { + cout << scsictl_display.DisplayImageFilesInfo(server_info.image_files_info()); + } + + if (server_info.has_mapping_info()) { + cout << scsictl_display.DisplayMappingInfo(server_info.mapping_info()); + } + + if (server_info.has_network_interfaces_info()) { + cout << scsictl_display.DisplayNetworkInterfaces(server_info.network_interfaces_info()); + } + + if (server_info.has_device_types_info()) { + cout << scsictl_display.DisplayDeviceTypesInfo(server_info.device_types_info()); + } + + if (server_info.has_reserved_ids_info()) { + cout << scsictl_display.DisplayReservedIdsInfo(server_info.reserved_ids_info()); + } + + if (server_info.has_statistics_info()) { + cout << scsictl_display.DisplayStatisticsInfo(server_info.statistics_info()); + } + + if (server_info.has_operation_info()) { + cout << scsictl_display.DisplayOperationInfo(server_info.operation_info()); + } + + if (server_info.has_devices_info() && server_info.devices_info().devices_size()) { vector sorted_devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() }; ranges::sort(sorted_devices, [](const auto& a, const auto& b) { return a.id() < b.id() || a.unit() < b.unit(); }); @@ -326,6 +356,15 @@ bool ScsictlCommands::CommandMappingInfo() return true; } +bool ScsictlCommands::CommandStatisticsInfo() +{ + SendCommand(); + + cout << scsictl_display.DisplayStatisticsInfo(result.statistics_info()) << flush; + + return true; +} + bool ScsictlCommands::CommandOperationInfo() { SendCommand(); diff --git a/cpp/scsictl/scsictl_commands.h b/cpp/scsictl/scsictl_commands.h index 11aa51fa..4477a14c 100644 --- a/cpp/scsictl/scsictl_commands.h +++ b/cpp/scsictl/scsictl_commands.h @@ -48,6 +48,7 @@ private: bool CommandLogLevelInfo(); bool CommandReservedIdsInfo(); bool CommandMappingInfo(); + bool CommandStatisticsInfo(); bool CommandOperationInfo(); bool SendCommand(); bool EvaluateParams(string_view, const string&, const string&); diff --git a/cpp/scsictl/scsictl_core.cpp b/cpp/scsictl/scsictl_core.cpp index ff24d455..9efad57a 100644 --- a/cpp/scsictl/scsictl_core.cpp +++ b/cpp/scsictl/scsictl_core.cpp @@ -35,7 +35,7 @@ void ScsiCtl::Banner(const vector& args) const << "[-F IMAGE_FOLDER] [-L LOG_LEVEL] [-h HOST] [-p PORT] [-r RESERVED_IDS] " << "[-C FILENAME:FILESIZE] [-d FILENAME] [-w FILENAME] [-R CURRENT_NAME:NEW_NAME] " << "[-x CURRENT_NAME:NEW_NAME] [-z LOCALE] " - << "[-e] [-E FILENAME] [-D] [-I] [-l] [-m] [o] [-O] [-P] [-s] [-v] [-V] [-y] [-X]\n" + << "[-e] [-E FILENAME] [-D] [-I] [-l] [-m] [o] [-O] [-P] [-s] [-S] [-v] [-V] [-y] [-X]\n" << " where ID[:LUN] ID := {0-" << (ControllerManager::GetScsiIdMax() - 1) << "}," << " LUN := {0-" << (ControllerManager::GetScsiLunMax() - 1) << "}, default is 0\n" << " CMD := {attach|detach|insert|eject|protect|unprotect|show}\n" @@ -82,7 +82,7 @@ int ScsiCtl::run(const vector& args) const opterr = 1; int opt; while ((opt = getopt(static_cast(args.size()), args.data(), - "e::lmos::vDINOTVXa:b:c:d:f:h:i:n:p:r:t:x:z:C:E:F:L:P::R:")) != -1) { + "e::lmos::vDINOSTVXa:b:c:d:f:h:i:n:p:r:t:x:z:C:E:F:L:P::R:")) != -1) { switch (opt) { case 'i': if (const string error = SetIdAndLun(*device, optarg); !error.empty()) { @@ -130,7 +130,7 @@ int ScsiCtl::run(const vector& args) const case 'e': command.set_operation(DEFAULT_IMAGE_FILES_INFO); if (optarg) { - SetPatternParams(command, optarg); + SetCommandParams(command, optarg); } break; @@ -208,10 +208,14 @@ int ScsiCtl::run(const vector& args) const case 's': command.set_operation(SERVER_INFO); if (optarg) { - SetPatternParams(command, optarg); + SetCommandParams(command, optarg); } break; + case 'S': + command.set_operation(STATISTICS_INFO); + break; + case 'v': cout << "scsictl version: " << piscsi_get_version_string() << '\n'; exit(EXIT_SUCCESS); diff --git a/cpp/scsictl/scsictl_display.cpp b/cpp/scsictl/scsictl_display.cpp index b71782c6..7250910a 100644 --- a/cpp/scsictl/scsictl_display.cpp +++ b/cpp/scsictl/scsictl_display.cpp @@ -231,6 +231,38 @@ string ScsictlDisplay::DisplayMappingInfo(const PbMappingInfo& mapping_info) con return s.str(); } +string ScsictlDisplay::DisplayStatisticsInfo(const PbStatisticsInfo& statistics_info) const +{ + ostringstream s; + + s << "Statistics:\n"; + + // Sort by ascending ID, LUN and key and by descending category + vector sorted_statistics = { statistics_info.statistics().begin(), statistics_info.statistics().end() }; + ranges::sort(sorted_statistics, [] (const PbStatistics& a, const PbStatistics& b) { + if (a.category() > b.category()) return true; + if (a.category() < b.category()) return false; + if (a.id() < b.id()) return true; + if (a.id() > b.id()) return false; + if (a.unit() < b.unit()) return true; + if (a.unit() > b.unit()) return false; + return a.key() < b.key(); + }); + + PbStatisticsCategory prev_category = PbStatisticsCategory::CATEGORY_NONE; + for (const auto& statistics : sorted_statistics) { + if (statistics.category() != prev_category) { + // Strip leading "CATEGORY_" + s << " " << PbStatisticsCategory_Name(statistics.category()).substr(9) << '\n'; + prev_category = statistics.category(); + } + + s << " " << statistics.id() << ":" << statistics.unit() << " " << statistics.key() << ": " << statistics.value() << '\n'; + } + + return s.str(); +} + string ScsictlDisplay::DisplayOperationInfo(const PbOperationInfo& operation_info) const { ostringstream s; diff --git a/cpp/scsictl/scsictl_display.h b/cpp/scsictl/scsictl_display.h index 1caddf48..94ebcf93 100644 --- a/cpp/scsictl/scsictl_display.h +++ b/cpp/scsictl/scsictl_display.h @@ -32,6 +32,7 @@ public: string DisplayImageFilesInfo(const PbImageFilesInfo&) const; string DisplayNetworkInterfaces(const PbNetworkInterfacesInfo&) const; string DisplayMappingInfo(const PbMappingInfo&) const; + string DisplayStatisticsInfo(const PbStatisticsInfo&) const; string DisplayOperationInfo(const PbOperationInfo&) const; private: diff --git a/cpp/shared/protobuf_util.cpp b/cpp/shared/protobuf_util.cpp index 012a5011..17239bd0 100644 --- a/cpp/shared/protobuf_util.cpp +++ b/cpp/shared/protobuf_util.cpp @@ -42,21 +42,33 @@ void protobuf_util::ParseParameters(PbDeviceDefinition& device, const string& pa } } -void protobuf_util::SetPatternParams(PbCommand& command, const string& patterns) +void protobuf_util::SetCommandParams(PbCommand& command, const string& params) { string folder_pattern; string file_pattern; + string operations; - if (const auto& components = Split(patterns, ':', 2); components.size() == 2) { - folder_pattern = components[0]; - file_pattern = components[1]; - } - else { - file_pattern = patterns; + switch (const auto& components = Split(params, COMPONENT_SEPARATOR, 3); components.size()) { + case 3: + operations = components[2]; + [[fallthrough]]; + + case 2: + folder_pattern = components[0]; + file_pattern = components[1]; + break; + + case 1: + file_pattern = components[0]; + break; + + default: + break; } SetParam(command, "folder_pattern", folder_pattern); SetParam(command, "file_pattern", file_pattern); + SetParam(command, "operations", operations); } void protobuf_util::SetProductData(PbDeviceDefinition& device, const string& data) diff --git a/cpp/shared/protobuf_util.h b/cpp/shared/protobuf_util.h index 0e5b2a78..cc155915 100644 --- a/cpp/shared/protobuf_util.h +++ b/cpp/shared/protobuf_util.h @@ -38,7 +38,7 @@ namespace protobuf_util } void ParseParameters(PbDeviceDefinition&, const string&); - void SetPatternParams(PbCommand&, const string&); + void SetCommandParams(PbCommand&, const string&); void SetProductData(PbDeviceDefinition&, const string&); string SetIdAndLun(PbDeviceDefinition&, const string&); string ListDevices(const vector&); diff --git a/cpp/test/piscsi_response_test.cpp b/cpp/test/piscsi_response_test.cpp index 278880d7..5fe6ac1a 100644 --- a/cpp/test/piscsi_response_test.cpp +++ b/cpp/test/piscsi_response_test.cpp @@ -9,6 +9,7 @@ #include "mocks.h" #include "shared/piscsi_version.h" +#include "shared/protobuf_util.h" #include "controllers/controller_manager.h" #include "devices/device_factory.h" #include "generated/piscsi_interface.pb.h" @@ -16,6 +17,7 @@ #include using namespace piscsi_interface; +using namespace protobuf_util; TEST(PiscsiResponseTest, Operation_Count) { @@ -178,15 +180,41 @@ TEST(PiscsiResponseTest, GetServerInfo) const unordered_set> devices; const unordered_set ids = { 1, 3 }; - PbServerInfo info; - response.GetServerInfo(info, devices, ids, "default_folder", "", "", 1234); - EXPECT_EQ(piscsi_major_version, info.version_info().major_version()); - EXPECT_EQ(piscsi_minor_version, info.version_info().minor_version()); - EXPECT_EQ(piscsi_patch_version, info.version_info().patch_version()); - EXPECT_EQ(level::level_string_views[get_level()], info.log_level_info().current_log_level()); - EXPECT_EQ("default_folder", info.image_files_info().default_image_folder()); - EXPECT_EQ(1234, info.image_files_info().depth()); - EXPECT_EQ(2, info.reserved_ids_info().ids().size()); + PbCommand command; + PbServerInfo info1; + response.GetServerInfo(info1, command, devices, ids, "default_folder", 1234); + EXPECT_TRUE(info1.has_version_info()); + EXPECT_TRUE(info1.has_log_level_info()); + EXPECT_TRUE(info1.has_device_types_info()); + EXPECT_TRUE(info1.has_image_files_info()); + EXPECT_TRUE(info1.has_network_interfaces_info()); + EXPECT_TRUE(info1.has_mapping_info()); + EXPECT_TRUE(info1.has_statistics_info()); + EXPECT_FALSE(info1.has_devices_info()); + EXPECT_TRUE(info1.has_reserved_ids_info()); + EXPECT_TRUE(info1.has_operation_info()); + + EXPECT_EQ(piscsi_major_version, info1.version_info().major_version()); + EXPECT_EQ(piscsi_minor_version, info1.version_info().minor_version()); + EXPECT_EQ(piscsi_patch_version, info1.version_info().patch_version()); + EXPECT_EQ(level::level_string_views[get_level()], info1.log_level_info().current_log_level()); + EXPECT_EQ("default_folder", info1.image_files_info().default_image_folder()); + EXPECT_EQ(1234, info1.image_files_info().depth()); + EXPECT_EQ(2, info1.reserved_ids_info().ids().size()); + + SetParam(command, "operations", "log_level_info,mapping_info"); + PbServerInfo info2; + response.GetServerInfo(info2, command, devices, ids, "default_folder", 1234); + EXPECT_FALSE(info2.has_version_info()); + EXPECT_TRUE(info2.has_log_level_info()); + EXPECT_FALSE(info2.has_device_types_info()); + EXPECT_FALSE(info2.has_image_files_info()); + EXPECT_FALSE(info2.has_network_interfaces_info()); + EXPECT_TRUE(info2.has_mapping_info()); + EXPECT_FALSE(info2.has_statistics_info()); + EXPECT_FALSE(info2.has_devices_info()); + EXPECT_FALSE(info2.has_reserved_ids_info()); + EXPECT_FALSE(info2.has_operation_info()); } TEST(PiscsiResponseTest, GetVersionInfo) diff --git a/cpp/test/protobuf_util_test.cpp b/cpp/test/protobuf_util_test.cpp index 1c4a06e6..2793a657 100644 --- a/cpp/test/protobuf_util_test.cpp +++ b/cpp/test/protobuf_util_test.cpp @@ -53,27 +53,38 @@ TEST(ProtobufUtil, ParseParameters) TestSpecialDevice("services"); } -TEST(ProtobufUtil, SetPatternParams) +TEST(ProtobufUtil, SetCommandParams) { PbCommand command1; - SetPatternParams(command1, "file"); + SetCommandParams(command1, "file"); EXPECT_EQ("", GetParam(command1, "folder_pattern")); EXPECT_EQ("file", GetParam(command1, "file_pattern")); PbCommand command2; - SetPatternParams(command2, ":file"); + SetCommandParams(command2, ":file"); EXPECT_EQ("", GetParam(command2, "folder_pattern")); EXPECT_EQ("file", GetParam(command2, "file_pattern")); PbCommand command3; - SetPatternParams(command3, "folder:"); - EXPECT_EQ("folder", GetParam(command3, "folder_pattern")); - EXPECT_EQ("", GetParam(command3, "file_pattern")); + SetCommandParams(command3, "file:"); + EXPECT_EQ("file", GetParam(command3, "file_pattern")); + EXPECT_EQ("", GetParam(command3, "folder_pattern")); PbCommand command4; - SetPatternParams(command4, "folder:file"); + SetCommandParams(command4, "folder:file"); EXPECT_EQ("folder", GetParam(command4, "folder_pattern")); EXPECT_EQ("file", GetParam(command4, "file_pattern")); + + PbCommand command5; + SetCommandParams(command5, "folder:file:"); + EXPECT_EQ("folder", GetParam(command5, "folder_pattern")); + EXPECT_EQ("file", GetParam(command5, "file_pattern")); + + PbCommand command6; + SetCommandParams(command6, "folder:file:operations"); + EXPECT_EQ("folder", GetParam(command6, "folder_pattern")); + EXPECT_EQ("file", GetParam(command6, "file_pattern")); + EXPECT_EQ("operations", GetParam(command6, "operations")); } TEST(ProtobufUtil, ListDevices) diff --git a/doc/scsictl.1 b/doc/scsictl.1 index 188c6930..3df74fba 100644 --- a/doc/scsictl.1 +++ b/doc/scsictl.1 @@ -7,30 +7,31 @@ scsictl \- Sends management commands to the piscsi process \fB\-l\fR | \fB\-m\fR | \fB\-o\fR | -\fB\-s\fR | \fB\-v\fR | \fB\-D\fR | \fB\-I\fR | \fB\-L\fR | \fB\-O\fR | \fB\-P\fR | +\fB\-S\fR | \fB\-T\fR | \fB\-V\fR | \fB\-X\fR | -[\fB\-C\fR \fIFILENAME:FILESIZE\fR] -[\fB\-E\fR \fIFILENAME\fR] -[\fB\-F\fR \fIIMAGE_FOLDER\fR] -[\fB\-R\fR \fICURRENT_NAME:NEW_NAME\fR] -[\fB\-c\fR \fICMD\fR] -[\fB\-f\fR \fIFILE|PARAM\fR] -[\fB\-g\fR \fILOG_LEVEL\fR] -[\fB\-h\fR \fIHOST\fR] -[\fB\-i\fR \fIID[:LUN]\fR -[\fB\-n\fR \fINAME\fR] -[\fB\-p\fR \fIPORT\fR] -[\fB\-r\fR \fIRESERVED_IDS\fR] -[\fB\-t\fR \fITYPE\fR] -[\fB\-x\fR \fICURRENT_NAME:NEW_NAME\fR] +[\fB\-C\fR \fIFILENAME:FILESIZE\fR] | +[\fB\-E\fR \fIFILENAME\fR] | +[\fB\-F\fR \fIIMAGE_FOLDER\fR] | +[\fB\-R\fR \fICURRENT_NAME:NEW_NAME\fR] | +[\fB\-c\fR \fICMD\fR] | +[\fB\-f\fR \fIFILE|PARAM\fR] | +[\fB\-g\fR \fILOG_LEVEL\fR] | +[\fB\-h\fR \fIHOST\fR] | +[\fB\-i\fR \fIID[:LUN]\fR] | +[\fB\-n\fR \fINAME\fR] | +[\fB\-p\fR \fIPORT\fR] | +[\fB\-r\fR \fIRESERVED_IDS\fR] | +[\fB\-s\fR \fI[FOLDER_PATTERN:FILE_PATTERN:OPERATIONS]\fR] | +[\fB\-t\fR \fITYPE\fR] | +[\fB\-x\fR \fICURRENT_NAME:NEW_NAME\fR] | [\fB\-z\fR \fILOCALE\fR] .SH DESCRIPTION .B scsictl @@ -38,7 +39,7 @@ sends commands to the piscsi process to make configuration adjustments at runtim Either the -i or -l option should be specified at one time. Not both. -You do NOT need root privileges to use scsictl. +You do NOT need root privileges to use scsictl. scsictl also runs on non-Pi Linux platforms. Note: The command and type arguments are case insensitive. Only the first letter of the command/type is evaluated by the tool. @@ -95,9 +96,12 @@ The piscsi port to connect to, default is 6868. .BR \-r\fI " " \fIRESERVED_IDS Comma-separated list of IDs to reserve. Pass an empty list in order to not reserve anything. .TP -.BR \-s\fI +.BR \-s\fI " " \fI[FOLDER_PATTERN:FILE_PATTERN:OPERATIONS] Display server-side settings like available images or supported device types. .TP +.BR \-S\fI +Display statistics. +.TP .BR \-T\fI Display all device types and their properties. .TP diff --git a/doc/scsictl_man_page.txt b/doc/scsictl_man_page.txt index 1b5108fd..f98b7208 100644 --- a/doc/scsictl_man_page.txt +++ b/doc/scsictl_man_page.txt @@ -6,19 +6,21 @@ NAME scsictl - Sends management commands to the piscsi process SYNOPSIS - scsictl -e | -l | -m | -o | -s | -v | -D | -I | -L | -O | -P | -T | -V - | -X | [-C FILENAME:FILESIZE] [-E FILENAME] [-F IMAGE_FOLDER] [-R CUR‐ - RENT_NAME:NEW_NAME] [-c CMD] [-f FILE|PARAM] [-g LOG_LEVEL] [-h HOST] - [-i ID[:LUN] [-n NAME] [-p PORT] [-r RESERVED_IDS] [-t TYPE] [-x CUR‐ - RENT_NAME:NEW_NAME] [-z LOCALE] + scsictl -e | -l | -m | -o | -v | -D | -I | -L | -O | -P | -S | -T | -V + | -X | [-C FILENAME:FILESIZE] | [-E FILENAME] | [-F IMAGE_FOLDER] | [-R + CURRENT_NAME:NEW_NAME] | [-c CMD] | [-f FILE|PARAM] | [-g LOG_LEVEL] | + [-h HOST] | [-i ID[:LUN]] | [-n NAME] | [-p PORT] | [-r RESERVED_IDS] | + [-s [FOLDER_PATTERN:FILE_PATTERN:OPERATIONS]] | [-t TYPE] | [-x CUR‐ + RENT_NAME:NEW_NAME] | [-z LOCALE] DESCRIPTION - scsictl sends commands to the piscsi process to make configuration ad‐ + scsictl sends commands to the piscsi process to make configuration ad‐ justments at runtime or to check the status of the devices. Either the -i or -l option should be specified at one time. Not both. - You do NOT need root privileges to use scsictl. + You do NOT need root privileges to use scsictl. scsictl also runs on + non-Pi Linux platforms. Note: The command and type arguments are case insensitive. Only the first letter of the command/type is evaluated by the tool. @@ -74,9 +76,12 @@ OPTIONS Comma-separated list of IDs to reserve. Pass an empty list in order to not reserve anything. - -s Display server-side settings like available images or supported + -s [FOLDER_PATTERN:FILE_PATTERN:OPERATIONS] + Display server-side settings like available images or supported device types. + -S Display statistics. + -T Display all device types and their properties. -v Display the piscsi server version. From 8bd06ea5cd9326349d2271d1a440bd1c7605f057 Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Mon, 30 Oct 2023 13:57:46 +0100 Subject: [PATCH 44/87] Improve how commands are internally executed (#1247) * Improve how commands are internally executed * Use const CommandContext for execution * Update error handling * Fix SonarQube issues * Remove duplicate code * Use mutex instead of atomic_bool * Add hasher * Add param_map * Do not log unknown operations as an error for backward/foward compatibility --- cpp/controllers/scsi_controller.cpp | 11 +- cpp/piscsi/command_context.cpp | 11 +- cpp/piscsi/command_context.h | 2 +- cpp/piscsi/localizer.cpp | 12 +-- cpp/piscsi/piscsi_core.cpp | 157 +++++++++++++++++++--------- cpp/piscsi/piscsi_core.h | 13 +-- cpp/piscsi/piscsi_executor.cpp | 96 ++--------------- cpp/piscsi/piscsi_executor.h | 14 +-- cpp/piscsi/piscsi_image.cpp | 4 + cpp/test/mocks.h | 5 - cpp/test/piscsi_executor_test.cpp | 96 +++-------------- cpp/test/piscsi_response_test.cpp | 1 + cpp/test/test_shared.cpp | 1 - 13 files changed, 163 insertions(+), 260 deletions(-) diff --git a/cpp/controllers/scsi_controller.cpp b/cpp/controllers/scsi_controller.cpp index e6d1f45c..26bb553e 100644 --- a/cpp/controllers/scsi_controller.cpp +++ b/cpp/controllers/scsi_controller.cpp @@ -411,8 +411,8 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status) if (sense_key != sense_key::no_sense || asc != asc::no_additional_sense_information) { stringstream s; - s << setfill('0') << setw(2) << hex << "Error status: Sense Key $" << static_cast(sense_key) - << ", ASC $" << static_cast(asc); + s << setfill('0') << hex << "Error status: Sense Key $" << setw(2) << static_cast(sense_key) + << ", ASC $" << setw(2) << static_cast(asc); LogDebug(s.str()); // Set Sense Key and ASC for a subsequent REQUEST SENSE @@ -422,8 +422,6 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status) SetStatus(status); SetMessage(0x00); - LogTrace("Error (to status phase)"); - Status(); } @@ -478,24 +476,19 @@ void ScsiController::Send() case phase_t::msgin: // Completed sending response to extended message of IDENTIFY message if (scsi.atnmsg) { - // flag off scsi.atnmsg = false; - // command phase Command(); } else { - // Bus free phase BusFree(); } break; case phase_t::datain: - // status phase Status(); break; case phase_t::status: - // Message in phase SetLength(1); SetBlocks(1); GetBuffer()[0] = (uint8_t)GetMessage(); diff --git a/cpp/piscsi/command_context.cpp b/cpp/piscsi/command_context.cpp index 5b381d9f..ab4eeac4 100644 --- a/cpp/piscsi/command_context.cpp +++ b/cpp/piscsi/command_context.cpp @@ -43,10 +43,11 @@ void CommandContext::WriteResult(const PbResult& result) const } } -void CommandContext::WriteSuccessResult(PbResult& result) const +bool CommandContext::WriteSuccessResult(PbResult& result) const { result.set_status(true); WriteResult(result); + return true; } bool CommandContext::ReturnLocalizedError(LocalizationKey key, const string& arg1, const string& arg2, @@ -59,7 +60,13 @@ bool CommandContext::ReturnLocalizedError(LocalizationKey key, PbErrorCode error const string& arg2, const string& arg3) const { // For the logfile always use English - spdlog::error(localizer.Localize(key, "en", arg1, arg2, arg3)); + // Do not log unknown operations as an error for backward/foward compatibility with old/new clients + if (error_code == PbErrorCode::UNKNOWN_OPERATION) { + spdlog::trace(localizer.Localize(key, "en", arg1, arg2, arg3)); + } + else { + spdlog::error(localizer.Localize(key, "en", arg1, arg2, arg3)); + } return ReturnStatus(false, localizer.Localize(key, locale, arg1, arg2, arg3), error_code, false); } diff --git a/cpp/piscsi/command_context.h b/cpp/piscsi/command_context.h index dcbb2d20..25c4dbb8 100644 --- a/cpp/piscsi/command_context.h +++ b/cpp/piscsi/command_context.h @@ -29,7 +29,7 @@ public: void SetDefaultFolder(string_view f) { default_folder = f; } bool ReadCommand(); void WriteResult(const PbResult&) const; - void WriteSuccessResult(PbResult&) const; + bool WriteSuccessResult(PbResult&) const; const PbCommand& GetCommand() const { return command; } bool ReturnLocalizedError(LocalizationKey, const string& = "", const string& = "", const string& = "") const; diff --git a/cpp/piscsi/localizer.cpp b/cpp/piscsi/localizer.cpp index 17c92034..1ac8f5f7 100644 --- a/cpp/piscsi/localizer.cpp +++ b/cpp/piscsi/localizer.cpp @@ -22,12 +22,12 @@ Localizer::Localizer() Add(LocalizationKey::ERROR_AUTHENTICATION, "es", "Fallo de autentificación"); Add(LocalizationKey::ERROR_AUTHENTICATION, "zh", "认证失败"); - Add(LocalizationKey::ERROR_OPERATION, "en", "Unknown operation"); - Add(LocalizationKey::ERROR_OPERATION, "de", "Unbekannte Operation"); - Add(LocalizationKey::ERROR_OPERATION, "sv", "Okänd operation"); - Add(LocalizationKey::ERROR_OPERATION, "fr", "Opération inconnue"); - Add(LocalizationKey::ERROR_OPERATION, "es", "Operación desconocida"); - Add(LocalizationKey::ERROR_OPERATION, "zh", "未知操作"); + Add(LocalizationKey::ERROR_OPERATION, "en", "Unknown operation: %1"); + Add(LocalizationKey::ERROR_OPERATION, "de", "Unbekannte Operation: %1"); + Add(LocalizationKey::ERROR_OPERATION, "sv", "Okänd operation: %1"); + Add(LocalizationKey::ERROR_OPERATION, "fr", "Opération inconnue: %1"); + Add(LocalizationKey::ERROR_OPERATION, "es", "Operación desconocida: %1"); + Add(LocalizationKey::ERROR_OPERATION, "zh", "未知操作: %1"); Add(LocalizationKey::ERROR_LOG_LEVEL, "en", "Invalid log level '%1'"); Add(LocalizationKey::ERROR_LOG_LEVEL, "de", "Ungültiger Log-Level '%1'"); diff --git a/cpp/piscsi/piscsi_core.cpp b/cpp/piscsi/piscsi_core.cpp index 1be72793..553e2297 100644 --- a/cpp/piscsi/piscsi_core.cpp +++ b/cpp/piscsi/piscsi_core.cpp @@ -33,6 +33,7 @@ using namespace std; using namespace filesystem; +using namespace spdlog; using namespace piscsi_interface; using namespace piscsi_util; using namespace protobuf_util; @@ -71,7 +72,7 @@ bool Piscsi::InitBus() return false; } - executor = make_unique(piscsi_image, *bus, controller_manager); + executor = make_unique(*bus, controller_manager); return true; } @@ -84,7 +85,12 @@ void Piscsi::CleanUp() executor->DetachAll(); - bus->Cleanup(); + // TODO Check why there are rare cases where bus is NULL on a remote interface shutdown + // even though it is never set to NULL anywhere + assert(bus); + if (bus) { + bus->Cleanup(); + } } void Piscsi::ReadAccessToken(const path& filename) @@ -307,27 +313,26 @@ bool Piscsi::SetLogLevel(const string& log_level) const return true; } -bool Piscsi::ExecuteCommand(CommandContext& context) +bool Piscsi::ExecuteCommand(const CommandContext& context) { - context.SetDefaultFolder(piscsi_image.GetDefaultFolder()); - const PbCommand& command = context.GetCommand(); + const PbOperation operation = command.operation(); if (!access_token.empty() && access_token != GetParam(command, "token")) { return context.ReturnLocalizedError(LocalizationKey::ERROR_AUTHENTICATION, UNAUTHORIZED); } - if (!PbOperation_IsValid(command.operation())) { - spdlog::error("Received unknown command with operation opcode " + to_string(command.operation())); + if (!PbOperation_IsValid(operation)) { + spdlog::trace("Ignored unknown command with operation opcode " + to_string(operation)); - return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION, UNKNOWN_OPERATION); + return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION, UNKNOWN_OPERATION, to_string(operation)); } - spdlog::trace("Received " + PbOperation_Name(command.operation()) + " command"); + spdlog::trace("Received " + PbOperation_Name(operation) + " command"); PbResult result; - switch(command.operation()) { + switch(operation) { case LOG_LEVEL: if (const string log_level = GetParam(command, "level"); !SetLogLevel(log_level)) { context.ReturnLocalizedError(LocalizationKey::ERROR_LOG_LEVEL, log_level); @@ -353,8 +358,7 @@ bool Piscsi::ExecuteCommand(CommandContext& context) case DEVICE_TYPES_INFO: response.GetDeviceTypesInfo(*result.mutable_device_types_info()); - context.WriteSuccessResult(result); - break; + return context.WriteSuccessResult(result); case SERVER_INFO: response.GetServerInfo(*result.mutable_server_info(), command, controller_manager.GetAllDevices(), @@ -364,19 +368,16 @@ bool Piscsi::ExecuteCommand(CommandContext& context) case VERSION_INFO: response.GetVersionInfo(*result.mutable_version_info()); - context.WriteSuccessResult(result); - break; + return context.WriteSuccessResult(result); case LOG_LEVEL_INFO: response.GetLogLevelInfo(*result.mutable_log_level_info()); - context.WriteSuccessResult(result); - break; + return context.WriteSuccessResult(result); case DEFAULT_IMAGE_FILES_INFO: response.GetImageFilesInfo(*result.mutable_image_files_info(), piscsi_image.GetDefaultFolder(), GetParam(command, "folder_pattern"), GetParam(command, "file_pattern"), piscsi_image.GetDepth()); - context.WriteSuccessResult(result); - break; + return context.WriteSuccessResult(result); case IMAGE_FILE_INFO: if (string filename = GetParam(command, "file"); filename.empty()) { @@ -399,13 +400,11 @@ bool Piscsi::ExecuteCommand(CommandContext& context) case NETWORK_INTERFACES_INFO: response.GetNetworkInterfacesInfo(*result.mutable_network_interfaces_info()); - context.WriteSuccessResult(result); - break; + return context.WriteSuccessResult(result); case MAPPING_INFO: response.GetMappingInfo(*result.mutable_mapping_info()); - context.WriteSuccessResult(result); - break; + return context.WriteSuccessResult(result); case STATISTICS_INFO: response.GetStatisticsInfo(*result.mutable_statistics_info(), controller_manager.GetAllDevices()); @@ -414,45 +413,65 @@ bool Piscsi::ExecuteCommand(CommandContext& context) case OPERATION_INFO: response.GetOperationInfo(*result.mutable_operation_info(), piscsi_image.GetDepth()); - context.WriteSuccessResult(result); - break; + return context.WriteSuccessResult(result); case RESERVED_IDS_INFO: response.GetReservedIds(*result.mutable_reserved_ids_info(), executor->GetReservedIds()); - context.WriteSuccessResult(result); - break; + return context.WriteSuccessResult(result); case SHUT_DOWN: - if (executor->ShutDown(context, GetParam(command, "mode"))) { - TerminationHandler(0); - } - break; + return ShutDown(context, GetParam(command, "mode")); case NO_OPERATION: - context.ReturnSuccessStatus(); - break; + return context.ReturnSuccessStatus(); - // TODO The image operations below can most likely directly be executed without calling the executor, - // because they do not require the target to be idle case CREATE_IMAGE: + return piscsi_image.CreateImage(context); + case DELETE_IMAGE: + return piscsi_image.DeleteImage(context); + case RENAME_IMAGE: + return piscsi_image.RenameImage(context); + case COPY_IMAGE: + return piscsi_image.CopyImage(context); + case PROTECT_IMAGE: case UNPROTECT_IMAGE: + return piscsi_image.SetImagePermissions(context); + case RESERVE_IDS: return executor->ProcessCmd(context); - // The remaining commands can only be executed when the target is idle - // TODO What happens when the target becomes active while the command is still being executed? - // A field 'mutex locker' can probably avoid SCSI commands and ProcessCmd() being executed at the same time default: - // TODO Find a better way to wait - const timespec ts = { .tv_sec = 0, .tv_nsec = 500'000'000}; - while (target_is_active) { - nanosleep(&ts, nullptr); + // The remaining commands may only be executed when the target is idle + if (!ExecuteWithLock(context)) { + return false; } - return executor->ProcessCmd(context); + + return HandleDeviceListChange(context, operation); + } + + return true; +} + +bool Piscsi::ExecuteWithLock(const CommandContext& context) +{ + scoped_lock lock(execution_locker); + return executor->ProcessCmd(context); +} + +bool Piscsi::HandleDeviceListChange(const CommandContext& context, PbOperation operation) const +{ + // ATTACH and DETACH return the resulting device list + if (operation == ATTACH || operation == DETACH) { + // A command with an empty device list is required here in order to return data for all devices + PbCommand command; + PbResult result; + response.GetDevicesInfo(controller_manager.GetAllDevices(), result, command, piscsi_image.GetDefaultFolder()); + context.WriteResult(result); + return result.status(); } return true; @@ -489,8 +508,10 @@ int Piscsi::run(span args) return EXIT_FAILURE; } - if (const string error = service.Init([this] (CommandContext& context) { return ExecuteCommand(context); }, port); - !error.empty()) { + if (const string error = service.Init([this] (CommandContext& context) { + context.SetDefaultFolder(piscsi_image.GetDefaultFolder()); + return ExecuteCommand(context); + }, port); !error.empty()) { cerr << "Error: " << error << endl; CleanUp(); @@ -584,7 +605,7 @@ void Piscsi::Process() // Only process the SCSI command if the bus is not busy and no other device responded if (IsNotBusy() && bus->GetSEL()) { - target_is_active = true; + scoped_lock lock(execution_locker); // Process command on the responsible controller based on the current initiator and target ID if (const auto shutdown_mode = controller_manager.ProcessOnController(bus->GetDAT()); @@ -592,23 +613,54 @@ void Piscsi::Process() // When the bus is free PiSCSI or the Pi may be shut down. ShutDown(shutdown_mode); } - - target_is_active = false; } } } -void Piscsi::ShutDown(AbstractController::piscsi_shutdown_mode shutdown_mode) -{ - CleanUp(); +// Shutdown on a remote interface command +bool Piscsi::ShutDown(const CommandContext& context, const string& m) { + if (m.empty()) { + return context.ReturnLocalizedError(LocalizationKey::ERROR_SHUTDOWN_MODE_MISSING); + } + AbstractController::piscsi_shutdown_mode mode = AbstractController::piscsi_shutdown_mode::NONE; + if (m == "rascsi") { + mode = AbstractController::piscsi_shutdown_mode::STOP_PISCSI; + } + else if (m == "system") { + mode = AbstractController::piscsi_shutdown_mode::STOP_PI; + } + else if (m == "reboot") { + mode = AbstractController::piscsi_shutdown_mode::RESTART_PI; + } + else { + return context.ReturnLocalizedError(LocalizationKey::ERROR_SHUTDOWN_MODE_INVALID, m); + } + + // Shutdown modes other than rascsi require root permissions + if (mode != AbstractController::piscsi_shutdown_mode::STOP_PISCSI && getuid()) { + return context.ReturnLocalizedError(LocalizationKey::ERROR_SHUTDOWN_PERMISSION); + } + + // Report success now because after a shutdown nothing can be reported anymore + PbResult result; + context.WriteSuccessResult(result); + + return ShutDown(mode); +} + +// Shutdown on a SCSI command +bool Piscsi::ShutDown(AbstractController::piscsi_shutdown_mode shutdown_mode) +{ switch(shutdown_mode) { case AbstractController::piscsi_shutdown_mode::STOP_PISCSI: spdlog::info("PiSCSI shutdown requested"); - break; + CleanUp(); + return true; case AbstractController::piscsi_shutdown_mode::STOP_PI: spdlog::info("Raspberry Pi shutdown requested"); + CleanUp(); if (system("init 0") == -1) { spdlog::error("Raspberry Pi shutdown failed"); } @@ -616,6 +668,7 @@ void Piscsi::ShutDown(AbstractController::piscsi_shutdown_mode shutdown_mode) case AbstractController::piscsi_shutdown_mode::RESTART_PI: spdlog::info("Raspberry Pi restart requested"); + CleanUp(); if (system("init 6") == -1) { spdlog::error("Raspberry Pi restart failed"); } @@ -625,6 +678,8 @@ void Piscsi::ShutDown(AbstractController::piscsi_shutdown_mode shutdown_mode) assert(false); break; } + + return false; } bool Piscsi::IsNotBusy() const diff --git a/cpp/piscsi/piscsi_core.h b/cpp/piscsi/piscsi_core.h index fea8dd73..1560851d 100644 --- a/cpp/piscsi/piscsi_core.h +++ b/cpp/piscsi/piscsi_core.h @@ -10,7 +10,6 @@ #pragma once #include "controllers/controller_manager.h" -#include "controllers/abstract_controller.h" #include "piscsi/command_context.h" #include "piscsi/piscsi_service.h" #include "piscsi/piscsi_image.h" @@ -20,7 +19,7 @@ #include "spdlog/sinks/stdout_color_sinks.h" #include #include -#include +#include using namespace std; @@ -49,9 +48,12 @@ private: void Process(); bool IsNotBusy() const; - void ShutDown(AbstractController::piscsi_shutdown_mode); + bool ShutDown(AbstractController::piscsi_shutdown_mode); + bool ShutDown(const CommandContext&, const string&); - bool ExecuteCommand(CommandContext&); + bool ExecuteCommand(const CommandContext&); + bool ExecuteWithLock(const CommandContext&); + bool HandleDeviceListChange(const CommandContext&, PbOperation) const; bool SetLogLevel(const string&) const; @@ -59,8 +61,7 @@ private: static PbDeviceType ParseDeviceType(const string&); - // Processing flag - atomic_bool target_is_active; + mutex execution_locker; string access_token; diff --git a/cpp/piscsi/piscsi_executor.cpp b/cpp/piscsi/piscsi_executor.cpp index c5564e01..fcb43db8 100644 --- a/cpp/piscsi/piscsi_executor.cpp +++ b/cpp/piscsi/piscsi_executor.cpp @@ -10,11 +10,8 @@ #include "shared/piscsi_util.h" #include "shared/protobuf_util.h" #include "shared/piscsi_exceptions.h" -#include "controllers/scsi_controller.h" #include "devices/device_factory.h" -#include "devices/primary_device.h" #include "devices/disk.h" -#include "piscsi_image.h" #include "localizer.h" #include "command_context.h" #include "piscsi_executor.h" @@ -82,7 +79,7 @@ bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDev break; default: - return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION); + return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION, to_string(operation)); } return true; @@ -92,36 +89,20 @@ bool PiscsiExecutor::ProcessCmd(const CommandContext& context) { const PbCommand& command = context.GetCommand(); + // Handle commands that are not device-specific switch (command.operation()) { case DETACH_ALL: DetachAll(); return context.ReturnSuccessStatus(); case RESERVE_IDS: { - const string ids = GetParam(command, "ids"); - if (const string error = SetReservedIds(ids); !error.empty()) { + if (const string error = SetReservedIds(GetParam(command, "ids")); !error.empty()) { return context.ReturnErrorStatus(error); } return context.ReturnSuccessStatus(); } - case CREATE_IMAGE: - return piscsi_image.CreateImage(context); - - case DELETE_IMAGE: - return piscsi_image.DeleteImage(context); - - case RENAME_IMAGE: - return piscsi_image.RenameImage(context); - - case COPY_IMAGE: - return piscsi_image.CopyImage(context); - - case PROTECT_IMAGE: - case UNPROTECT_IMAGE: - return piscsi_image.SetImagePermissions(context); - default: // This is a device-specific command handled below break; @@ -129,10 +110,11 @@ bool PiscsiExecutor::ProcessCmd(const CommandContext& context) // Remember the list of reserved files during the dry run const auto& reserved_files = StorageDevice::GetReservedFiles(); - const bool reserved = ranges::find_if_not(context.GetCommand().devices(), [&] (const auto& device) + const bool isDryRunError = ranges::find_if_not(command.devices(), [&] (const auto& device) { return ProcessDeviceCmd(context, device, true); }) != command.devices().end(); StorageDevice::SetReservedFiles(reserved_files); - if (reserved) { + + if (isDryRunError) { return false; } @@ -145,16 +127,6 @@ bool PiscsiExecutor::ProcessCmd(const CommandContext& context) return false; } - // ATTACH and DETACH return the device list - if (command.operation() == ATTACH || command.operation() == DETACH) { - // A new command with an empty device list is required here in order to return data for all devices - PbCommand cmd; - PbResult result; - piscsi_response.GetDevicesInfo(controller_manager.GetAllDevices(), result, cmd, context.GetDefaultFolder()); - context.WriteResult(result); - return true; - } - return context.ReturnSuccessStatus(); } @@ -336,12 +308,11 @@ bool PiscsiExecutor::Insert(const CommandContext& context, const PbDeviceDefinit spdlog::info("Insert " + string(pb_device.protected_() ? "protected " : "") + "file '" + filename + "' requested into " + device->GetIdentifier()); - // TODO It may be better to add PrimaryDevice::Insert for all device-specific insert operations - auto storage_device = dynamic_pointer_cast(device); - if (!SetSectorSize(context, storage_device, pb_device.block_size())) { + if (!SetSectorSize(context, device, pb_device.block_size())) { return false; } + auto storage_device = dynamic_pointer_cast(device); if (!ValidateImageFile(context, *storage_device, filename)) { return false; } @@ -391,57 +362,6 @@ void PiscsiExecutor::DetachAll() spdlog::info("Detached all devices"); } -bool PiscsiExecutor::ShutDown(const CommandContext& context, const string& mode) { - if (mode.empty()) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_SHUTDOWN_MODE_MISSING); - } - - PbResult result; - result.set_status(true); - - // The PiSCSI shutdown mode is "rascsi" instead of "piscsi" for backwards compatibility - if (mode == "rascsi") { - spdlog::info("PiSCSI shutdown requested"); - - context.WriteResult(result); - - return true; - } - - if (mode != "system" && mode != "reboot") { - return context.ReturnLocalizedError(LocalizationKey::ERROR_SHUTDOWN_MODE_INVALID, mode); - } - - // The root user has UID 0 - if (getuid()) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_SHUTDOWN_PERMISSION); - } - - if (mode == "system") { - spdlog::info("System shutdown requested"); - - DetachAll(); - - context.WriteResult(result); - - if (system("init 0") == -1) { - spdlog::error("System shutdown failed"); - } - } - else if (mode == "reboot") { - spdlog::info("System reboot requested"); - - DetachAll(); - - context.WriteResult(result); - - if (system("init 6") == -1) { - spdlog::error("System reboot failed"); - } - } - - return false; -} string PiscsiExecutor::SetReservedIds(string_view ids) { diff --git a/cpp/piscsi/piscsi_executor.h b/cpp/piscsi/piscsi_executor.h index efa5916e..bae4fe60 100644 --- a/cpp/piscsi/piscsi_executor.h +++ b/cpp/piscsi/piscsi_executor.h @@ -11,25 +11,22 @@ #include "hal/bus.h" #include "controllers/controller_manager.h" -#include "piscsi/piscsi_response.h" #include -class PiscsiImage; class DeviceFactory; class PrimaryDevice; class StorageDevice; class CommandContext; -using namespace spdlog; - class PiscsiExecutor { public: - PiscsiExecutor(PiscsiImage& piscsi_image, BUS& bus, ControllerManager& controller_manager) - : piscsi_image(piscsi_image), bus(bus), controller_manager(controller_manager) {} + PiscsiExecutor(BUS& bus, ControllerManager& controller_manager) : bus(bus), controller_manager(controller_manager) {} ~PiscsiExecutor() = default; + // TODO At least some of these methods should be private, currently they are directly called by the unit tests + auto GetReservedIds() const { return reserved_ids; } bool ProcessDeviceCmd(const CommandContext&, const PbDeviceDefinition&, bool); @@ -43,7 +40,6 @@ public: bool Insert(const CommandContext&, const PbDeviceDefinition&, const shared_ptr&, bool) const; bool Detach(const CommandContext&, PrimaryDevice&, bool); void DetachAll(); - bool ShutDown(const CommandContext&, const string&); string SetReservedIds(string_view); bool ValidateImageFile(const CommandContext&, StorageDevice&, const string&) const; string PrintCommand(const PbCommand&, const PbDeviceDefinition&) const; @@ -60,10 +56,6 @@ private: static bool CheckForReservedFile(const CommandContext&, const string&); - const PiscsiResponse piscsi_response; - - PiscsiImage& piscsi_image; - BUS& bus; ControllerManager& controller_manager; diff --git a/cpp/piscsi/piscsi_image.cpp b/cpp/piscsi/piscsi_image.cpp index 4c7dcc42..8e569e04 100644 --- a/cpp/piscsi/piscsi_image.cpp +++ b/cpp/piscsi/piscsi_image.cpp @@ -276,6 +276,10 @@ bool PiscsiImage::SetImagePermissions(const CommandContext& context) const const bool protect = context.GetCommand().operation() == PROTECT_IMAGE; + if (protect && !IsReservedFile(context, full_filename, "protect")) { + return false; + } + try { permissions(path(full_filename), protect ? perms::owner_read | perms::group_read | perms::others_read : diff --git a/cpp/test/mocks.h b/cpp/test/mocks.h index 9b315392..fcf45cd5 100644 --- a/cpp/test/mocks.h +++ b/cpp/test/mocks.h @@ -14,15 +14,10 @@ #include "test_shared.h" #include "hal/bus.h" #include "controllers/scsi_controller.h" -#include "devices/primary_device.h" -#include "devices/storage_device.h" -#include "devices/disk.h" -#include "devices/scsihd.h" #include "devices/scsihd_nec.h" #include "devices/scsicd.h" #include "devices/scsimo.h" #include "devices/host_services.h" -#include "piscsi/command_context.h" #include "piscsi/piscsi_executor.h" #include diff --git a/cpp/test/piscsi_executor_test.cpp b/cpp/test/piscsi_executor_test.cpp index a4a917af..d6557be4 100644 --- a/cpp/test/piscsi_executor_test.cpp +++ b/cpp/test/piscsi_executor_test.cpp @@ -15,7 +15,6 @@ #include "generated/piscsi_interface.pb.h" #include "piscsi/command_context.h" #include "piscsi/piscsi_response.h" -#include "piscsi/piscsi_image.h" #include "piscsi/piscsi_executor.h" #include @@ -31,8 +30,7 @@ TEST(PiscsiExecutorTest, ProcessDeviceCmd) auto bus = make_shared(); ControllerManager controller_manager; MockAbstractController controller(bus, ID); - PiscsiImage piscsi_image; - auto executor = make_shared(piscsi_image, *bus, controller_manager); + auto executor = make_shared(*bus, controller_manager); PbDeviceDefinition definition; PbCommand command; CommandContext context(command, "", ""); @@ -115,8 +113,7 @@ TEST(PiscsiExecutorTest, ProcessCmd) auto bus = make_shared(); ControllerManager controller_manager; MockAbstractController controller(bus, 0); - PiscsiImage piscsi_image; - auto executor = make_shared(piscsi_image, *bus, controller_manager); + auto executor = make_shared(*bus, controller_manager); PbCommand command_detach_all; command_detach_all.set_operation(DETACH_ALL); @@ -167,35 +164,6 @@ TEST(PiscsiExecutorTest, ProcessCmd) device2->set_unit(1); CommandContext context_attach2(command_attach2, "", ""); EXPECT_FALSE(executor->ProcessCmd(context_attach2)) << "LUN 0 is missing"; - - // The operations below must fail because of missing parameters. - // The respective functionality is tested in piscsi_image_test.cpp. - - PbCommand command; - - command.set_operation(CREATE_IMAGE); - CommandContext context_create_image(command, "", ""); - EXPECT_FALSE(executor->ProcessCmd(context_create_image)); - - command.set_operation(DELETE_IMAGE); - CommandContext context_delete_image(command, "", ""); - EXPECT_FALSE(executor->ProcessCmd(context_delete_image)); - - command.set_operation(RENAME_IMAGE); - CommandContext context_rename_image(command, "", ""); - EXPECT_FALSE(executor->ProcessCmd(context_rename_image)); - - command.set_operation(COPY_IMAGE); - CommandContext context_copy_image(command, "", ""); - EXPECT_FALSE(executor->ProcessCmd(context_copy_image)); - - command.set_operation(PROTECT_IMAGE); - CommandContext context_protect_image(command, "", ""); - EXPECT_FALSE(executor->ProcessCmd(context_protect_image)); - - command.set_operation(UNPROTECT_IMAGE); - CommandContext context_unprotect_image(command, "", ""); - EXPECT_FALSE(executor->ProcessCmd(context_unprotect_image)); } TEST(PiscsiExecutorTest, Attach) @@ -206,8 +174,7 @@ TEST(PiscsiExecutorTest, Attach) DeviceFactory device_factory; auto bus = make_shared(); ControllerManager controller_manager; - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PiscsiExecutor executor(*bus, controller_manager); PbDeviceDefinition definition; PbCommand command; CommandContext context(command, "", ""); @@ -290,8 +257,7 @@ TEST(PiscsiExecutorTest, Insert) auto bus = make_shared(); ControllerManager controller_manager; auto [controller, device] = CreateDevice(SCHD); - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PiscsiExecutor executor(*bus, controller_manager); PbDeviceDefinition definition; PbCommand command; CommandContext context(command, "", ""); @@ -348,8 +314,7 @@ TEST(PiscsiExecutorTest, Detach) DeviceFactory device_factory; auto bus = make_shared(); ControllerManager controller_manager; - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PiscsiExecutor executor(*bus, controller_manager); PbCommand command; CommandContext context(command, "", ""); @@ -375,8 +340,7 @@ TEST(PiscsiExecutorTest, DetachAll) DeviceFactory device_factory; auto bus = make_shared(); ControllerManager controller_manager; - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PiscsiExecutor executor(*bus, controller_manager); auto device = device_factory.CreateDevice(SCHS, 0, ""); EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device)); @@ -388,31 +352,12 @@ TEST(PiscsiExecutorTest, DetachAll) EXPECT_TRUE(controller_manager.GetAllDevices().empty()); } -TEST(PiscsiExecutorTest, ShutDown) -{ - auto bus = make_shared(); - ControllerManager controller_manager; - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *bus, controller_manager); - - PbCommand command; - command.set_operation(SHUT_DOWN); - CommandContext context(command, "", ""); - EXPECT_FALSE(executor.ShutDown(context, "")); - EXPECT_FALSE(executor.ShutDown(context, "xyz")); - - EXPECT_FALSE(executor.ShutDown(context, "system")) << "Only available for the root user"; - EXPECT_FALSE(executor.ShutDown(context, "reboot")) << "Only available for the root user"; - EXPECT_TRUE(executor.ShutDown(context, "rascsi")); -} - TEST(PiscsiExecutorTest, SetReservedIds) { DeviceFactory device_factory; auto bus = make_shared(); ControllerManager controller_manager; - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PiscsiExecutor executor(*bus, controller_manager); string error = executor.SetReservedIds("xyz"); EXPECT_FALSE(error.empty()); @@ -451,8 +396,7 @@ TEST(PiscsiExecutorTest, ValidateImageFile) DeviceFactory device_factory; auto bus = make_shared(); ControllerManager controller_manager; - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PiscsiExecutor executor(*bus, controller_manager); PbCommand command; CommandContext context(command, "", ""); @@ -466,8 +410,7 @@ TEST(PiscsiExecutorTest, PrintCommand) { auto bus = make_shared(); ControllerManager controller_manager; - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PiscsiExecutor executor(*bus, controller_manager); PbDeviceDefinition definition; PbCommand command; @@ -493,8 +436,7 @@ TEST(PiscsiExecutorTest, EnsureLun0) DeviceFactory device_factory; auto bus = make_shared(); ControllerManager controller_manager; - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PiscsiExecutor executor(*bus, controller_manager); PbCommand command; auto device1 = command.add_devices(); @@ -521,8 +463,7 @@ TEST(PiscsiExecutorTest, VerifyExistingIdAndLun) DeviceFactory device_factory; auto bus = make_shared(); ControllerManager controller_manager; - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PiscsiExecutor executor(*bus, controller_manager); PbCommand command; CommandContext context(command, "", ""); @@ -537,8 +478,7 @@ TEST(PiscsiExecutorTest, CreateDevice) { auto bus = make_shared(); ControllerManager controller_manager; - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PiscsiExecutor executor(*bus, controller_manager); PbCommand command; CommandContext context(command, "", ""); @@ -555,8 +495,7 @@ TEST(PiscsiExecutorTest, SetSectorSize) { auto bus = make_shared(); ControllerManager controller_manager; - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PiscsiExecutor executor(*bus, controller_manager); PbCommand command; CommandContext context(command, "", ""); @@ -575,8 +514,7 @@ TEST(PiscsiExecutorTest, ValidateOperationAgainstDevice) { auto bus = make_shared(); ControllerManager controller_manager; - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PiscsiExecutor executor(*bus, controller_manager); PbCommand command; CommandContext context(command, "", ""); @@ -629,8 +567,7 @@ TEST(PiscsiExecutorTest, ValidateIdAndLun) { auto bus = make_shared(); ControllerManager controller_manager; - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PiscsiExecutor executor(*bus, controller_manager); PbCommand command; CommandContext context(command, "", ""); @@ -646,8 +583,7 @@ TEST(PiscsiExecutorTest, SetProductData) { auto bus = make_shared(); ControllerManager controller_manager; - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PiscsiExecutor executor(*bus, controller_manager); PbCommand command; CommandContext context(command, "", ""); PbDeviceDefinition definition; diff --git a/cpp/test/piscsi_response_test.cpp b/cpp/test/piscsi_response_test.cpp index 5fe6ac1a..510c2637 100644 --- a/cpp/test/piscsi_response_test.cpp +++ b/cpp/test/piscsi_response_test.cpp @@ -17,6 +17,7 @@ #include using namespace piscsi_interface; +using namespace spdlog; using namespace protobuf_util; TEST(PiscsiResponseTest, Operation_Count) diff --git a/cpp/test/test_shared.cpp b/cpp/test/test_shared.cpp index 91e8c4f6..3ab8ad29 100644 --- a/cpp/test/test_shared.cpp +++ b/cpp/test/test_shared.cpp @@ -11,7 +11,6 @@ #include "mocks.h" #include "shared/piscsi_exceptions.h" #include "shared/piscsi_version.h" -#include #include #include #include From 7bbcf59c76f1aa68e7cbb71bbb5ff9a543d853cf Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Tue, 31 Oct 2023 09:02:28 +0100 Subject: [PATCH 45/87] scsictl shall accept generic key/value pairs for options that take parameters (#1240) (#1274) * scsictl accepts generic key/value pairs for options that take parameters --- cpp/scsictl/scsictl_core.cpp | 7 +++++-- cpp/shared/protobuf_util.cpp | 23 ++++++++++++++++++++++- cpp/shared/protobuf_util.h | 3 ++- cpp/test/protobuf_util_test.cpp | 14 ++++++++++++++ 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/cpp/scsictl/scsictl_core.cpp b/cpp/scsictl/scsictl_core.cpp index 9efad57a..6f6b8860 100644 --- a/cpp/scsictl/scsictl_core.cpp +++ b/cpp/scsictl/scsictl_core.cpp @@ -207,8 +207,11 @@ int ScsiCtl::run(const vector& args) const case 's': command.set_operation(SERVER_INFO); - if (optarg) { - SetCommandParams(command, optarg); + if (optarg) { + if (const string error = SetCommandParams(command, optarg); !error.empty()) { + cerr << "Error: " << error << endl; + exit(EXIT_FAILURE); + } } break; diff --git a/cpp/shared/protobuf_util.cpp b/cpp/shared/protobuf_util.cpp index 17239bd0..e4c1b9b0 100644 --- a/cpp/shared/protobuf_util.cpp +++ b/cpp/shared/protobuf_util.cpp @@ -42,8 +42,12 @@ void protobuf_util::ParseParameters(PbDeviceDefinition& device, const string& pa } } -void protobuf_util::SetCommandParams(PbCommand& command, const string& params) +string protobuf_util::SetCommandParams(PbCommand& command, const string& params) { + if (params.find(KEY_VALUE_SEPARATOR) != string::npos) { + return SetFromGenericParams(command, params); + } + string folder_pattern; string file_pattern; string operations; @@ -69,6 +73,23 @@ void protobuf_util::SetCommandParams(PbCommand& command, const string& params) SetParam(command, "folder_pattern", folder_pattern); SetParam(command, "file_pattern", file_pattern); SetParam(command, "operations", operations); + + return ""; +} + +string protobuf_util::SetFromGenericParams(PbCommand& command, const string& params) +{ + for (const string& key_value : Split(params, COMPONENT_SEPARATOR)) { + const auto& param = Split(key_value, KEY_VALUE_SEPARATOR, 2); + if (param.size() > 1 && !param[0].empty()) { + SetParam(command, param[0], param[1]); + } + else { + return "Parameter '" + key_value + "' has to be a key/value pair"; + } + } + + return ""; } void protobuf_util::SetProductData(PbDeviceDefinition& device, const string& data) diff --git a/cpp/shared/protobuf_util.h b/cpp/shared/protobuf_util.h index cc155915..f07b0322 100644 --- a/cpp/shared/protobuf_util.h +++ b/cpp/shared/protobuf_util.h @@ -38,7 +38,8 @@ namespace protobuf_util } void ParseParameters(PbDeviceDefinition&, const string&); - void SetCommandParams(PbCommand&, const string&); + string SetCommandParams(PbCommand&, const string&); + string SetFromGenericParams(PbCommand&, const string&); void SetProductData(PbDeviceDefinition&, const string&); string SetIdAndLun(PbDeviceDefinition&, const string&); string ListDevices(const vector&); diff --git a/cpp/test/protobuf_util_test.cpp b/cpp/test/protobuf_util_test.cpp index 2793a657..c6c9d4d8 100644 --- a/cpp/test/protobuf_util_test.cpp +++ b/cpp/test/protobuf_util_test.cpp @@ -87,6 +87,20 @@ TEST(ProtobufUtil, SetCommandParams) EXPECT_EQ("operations", GetParam(command6, "operations")); } +TEST(ProtobufUtil, SetFromGenericParams) +{ + PbCommand command1; + EXPECT_TRUE(SetFromGenericParams(command1, "operations=mapping_info:folder_pattern=pattern").empty()); + EXPECT_EQ("mapping_info", GetParam(command1, "operations")); + EXPECT_EQ("pattern", GetParam(command1, "folder_pattern")); + + PbCommand command2; + EXPECT_FALSE(SetFromGenericParams(command2, "=mapping_info").empty()); + + PbCommand command3; + EXPECT_FALSE(SetFromGenericParams(command3, "=").empty()); +} + TEST(ProtobufUtil, ListDevices) { vector devices; From 029cf06c72f4dcb91d3e3b4d650c44b41ff4e64a Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Tue, 31 Oct 2023 14:54:04 -0700 Subject: [PATCH 46/87] Web UI: Upload to tmp file name then rename if successful (#1272) * Upload to tmp file name then rename if successful * Move the dropzone.js operations back into web.py * Move list_images() from file commands into piscsi commands (it was the only class method in that package that calls the protobuf interface) * Remove now-redundant helptext --- python/common/src/piscsi/file_cmds.py | 98 +------------------ python/common/src/piscsi/piscsi_cmds.py | 98 ++++++++++++++++++- .../ctrlboard/src/ctrlboard_menu_builder.py | 2 +- python/web/src/templates/upload.html | 1 - python/web/src/web.py | 48 +++++++-- python/web/src/web_utils.py | 39 +------- 6 files changed, 141 insertions(+), 145 deletions(-) diff --git a/python/common/src/piscsi/file_cmds.py b/python/common/src/piscsi/file_cmds.py index a64298ae..9f696c59 100644 --- a/python/common/src/piscsi/file_cmds.py +++ b/python/common/src/piscsi/file_cmds.py @@ -5,7 +5,6 @@ Module for methods reading from and writing to the file system import logging import asyncio from os import walk, path -from functools import lru_cache from pathlib import PurePath, Path from zipfile import ZipFile, is_zipfile from subprocess import run, Popen, PIPE, CalledProcessError, TimeoutExpired @@ -17,18 +16,15 @@ from re import search import requests -import piscsi_interface_pb2 as proto from piscsi.common_settings import ( CFG_DIR, CONFIG_FILE_SUFFIX, PROPERTIES_SUFFIX, - ARCHIVE_FILE_SUFFIXES, RESERVATIONS, SHELL_ERROR, ) from piscsi.piscsi_cmds import PiscsiCmds from piscsi.return_codes import ReturnCodes -from piscsi.socket_cmds import SocketCmds from util import unarchiver FILE_READ_ERROR = "Unhandled exception when reading file: %s" @@ -41,18 +37,8 @@ class FileCmds: class for methods reading from and writing to the file system """ - def __init__(self, sock_cmd: SocketCmds, piscsi: PiscsiCmds, token=None, locale=None): - self.sock_cmd = sock_cmd + def __init__(self, piscsi: PiscsiCmds): self.piscsi = piscsi - self.token = token - self.locale = locale - - def send_pb_command(self, command): - if logging.getLogger().isEnabledFor(logging.DEBUG): - # TODO: Uncouple/move to common dependency - logging.debug(self.piscsi.format_pb_command(command)) - - return self.sock_cmd.send_pb_command(command.SerializeToString()) # noinspection PyMethodMayBeStatic def list_config_files(self): @@ -87,76 +73,6 @@ class FileCmds: subdir_list.sort() return subdir_list - def list_images(self): - """ - Sends a IMAGE_FILES_INFO command to the server - Returns a (dict) with (bool) status, (str) msg, and (list) of (dict)s files - - """ - command = proto.PbCommand() - command.operation = proto.PbOperation.DEFAULT_IMAGE_FILES_INFO - command.params["token"] = self.token - command.params["locale"] = self.locale - - data = self.send_pb_command(command) - result = proto.PbResult() - result.ParseFromString(data) - - server_info = self.piscsi.get_server_info() - files = [] - for file in result.image_files_info.image_files: - prop_file_path = Path(CFG_DIR) / f"{file.name}.{PROPERTIES_SUFFIX}" - # Add properties meta data for the image, if matching prop file is found - if prop_file_path.exists(): - process = self.read_drive_properties(prop_file_path) - prop = process["conf"] - else: - prop = 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) - files.append( - { - "name": file.name, - "size": file.size, - "size_mb": size_mb, - "detected_type": dtype, - "prop": prop, - "archive_contents": archive_contents, - } - ) - - return {"status": result.status, "msg": result.msg, "files": files} - # noinspection PyMethodMayBeStatic def delete_file(self, file_path): """ @@ -892,15 +808,3 @@ 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) as error: - logging.error(str(error)) - raise diff --git a/python/common/src/piscsi/piscsi_cmds.py b/python/common/src/piscsi/piscsi_cmds.py index 2f7b16fc..408f8b76 100644 --- a/python/common/src/piscsi/piscsi_cmds.py +++ b/python/common/src/piscsi/piscsi_cmds.py @@ -2,10 +2,21 @@ Module for commands sent to the PiSCSI backend service. """ +import logging +from pathlib import PurePath, Path +from functools import lru_cache + import piscsi_interface_pb2 as proto from piscsi.return_codes import ReturnCodes from piscsi.socket_cmds import SocketCmds -import logging + +from piscsi.common_settings import ( + CFG_DIR, + PROPERTIES_SUFFIX, + ARCHIVE_FILE_SUFFIXES, +) + +from util import unarchiver class PiscsiCmds: @@ -24,6 +35,79 @@ class PiscsiCmds: return self.sock_cmd.send_pb_command(command.SerializeToString()) + def list_images(self): + """ + Sends a IMAGE_FILES_INFO command to the server + Returns a (dict) with (bool) status, (str) msg, and (list) of (dict)s files + """ + from piscsi.file_cmds import FileCmds + + self.file_cmd = FileCmds(piscsi=self) + + command = proto.PbCommand() + command.operation = proto.PbOperation.DEFAULT_IMAGE_FILES_INFO + command.params["token"] = self.token + command.params["locale"] = self.locale + + data = self.send_pb_command(command) + result = proto.PbResult() + result.ParseFromString(data) + + server_info = self.get_server_info() + files = [] + for file in result.image_files_info.image_files: + prop_file_path = Path(CFG_DIR) / f"{file.name}.{PROPERTIES_SUFFIX}" + # Add properties meta data for the image, if matching prop file is found + if prop_file_path.exists(): + process = self.file_cmd.read_drive_properties(prop_file_path) + prop = process["conf"] + else: + prop = 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) + files.append( + { + "name": file.name, + "size": file.size, + "size_mb": size_mb, + "detected_type": dtype, + "prop": prop, + "archive_contents": archive_contents, + } + ) + + return {"status": result.status, "msg": result.msg, "files": files} + def get_server_info(self): """ Sends a SERVER_INFO command to the server. @@ -521,3 +605,15 @@ class PiscsiCmds: message += f", device: {formatted_device}" return message + + # 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) as error: + logging.error(str(error)) + raise diff --git a/python/ctrlboard/src/ctrlboard_menu_builder.py b/python/ctrlboard/src/ctrlboard_menu_builder.py index 25075f1d..cfacbc23 100644 --- a/python/ctrlboard/src/ctrlboard_menu_builder.py +++ b/python/ctrlboard/src/ctrlboard_menu_builder.py @@ -142,7 +142,7 @@ class CtrlBoardMenuBuilder(MenuBuilder): def create_images_menu(self, context_object=None): """Creates a sub menu showing all the available images""" menu = Menu(CtrlBoardMenuBuilder.IMAGES_MENU) - images_info = self.file_cmd.list_images() + images_info = self.piscsi_cmd.list_images() menu.add_entry("Return", {"context": self.IMAGES_MENU, "action": self.ACTION_RETURN}) images = images_info["files"] sorted_images = sorted(images, key=lambda d: d["name"]) diff --git a/python/web/src/templates/upload.html b/python/web/src/templates/upload.html index c572469e..a2be4a5d 100644 --- a/python/web/src/templates/upload.html +++ b/python/web/src/templates/upload.html @@ -4,7 +4,6 @@

{{ _("Upload File from Local Computer") }}

  • {{ _("The largest file size accepted in this form is %(max_file_size)s MiB. Use other file transfer means for larger files.", max_file_size=max_file_size) }}
  • -
  • {{ _("You have to manually clean up partially uploaded files, as a result of cancelling the upload or closing this page.") }}
  • {{ _("Disk Images") }} = {{ env["image_dir"] }}
  • {% if file_server_dir_exists %}
  • {{ _("Shared Files") }} = {{ FILE_SERVER_DIR }}
  • diff --git a/python/web/src/web.py b/python/web/src/web.py index d11a3934..865ef0d2 100644 --- a/python/web/src/web.py +++ b/python/web/src/web.py @@ -8,11 +8,13 @@ import argparse from pathlib import Path, PurePath from functools import wraps from grp import getgrall - +from os import path import bjoern + from piscsi.return_codes import ReturnCodes from simplepam import authenticate from flask_babel import Babel, Locale, refresh, _ +from werkzeug.utils import secure_filename from flask import ( Flask, @@ -55,7 +57,6 @@ from web_utils import ( auth_active, is_bridge_configured, is_safe_path, - upload_with_dropzonejs, browser_supports_modern_themes, ) from settings import ( @@ -225,9 +226,8 @@ def index(): devices = piscsi_cmd.list_devices() device_types = map_device_types_and_names(piscsi_cmd.get_device_types()["device_types"]) - image_files = file_cmd.list_images() + image_files = piscsi_cmd.list_images() config_files = file_cmd.list_config_files() - ip_addr, host = sys_cmd.get_ip_and_host() formatted_image_files = format_image_list( image_files["files"], Path(server_info["image_dir"]).name, device_types ) @@ -315,7 +315,7 @@ def drive_list(): return response( template="drives.html", page_title=_("PiSCSI Create Drive"), - files=file_cmd.list_images()["files"], + files=piscsi_cmd.list_images()["files"], drive_properties=format_drive_properties(APP.config["PISCSI_DRIVE_PROPERTIES"]), ) @@ -1035,7 +1035,41 @@ def upload_file(): else: return make_response(_("Unknown destination"), 403) - return upload_with_dropzonejs(destination_dir) + log = logging.getLogger("pydrop") + file_object = request.files["file"] + file_name = secure_filename(file_object.filename) + tmp_file_name = "__tmp_" + file_name + + save_path = path.join(destination_dir, file_name) + tmp_save_path = path.join(destination_dir, tmp_file_name) + current_chunk = int(request.form["dzchunkindex"]) + + # Makes sure not to overwrite an existing file, + # but continues writing to a file transfer in progress + if path.exists(save_path) and current_chunk == 0: + return make_response(_("The file already exists!"), 400) + + try: + with open(tmp_save_path, "ab") as save: + save.seek(int(request.form["dzchunkbyteoffset"])) + save.write(file_object.stream.read()) + except OSError: + log.exception("Could not write to file") + return make_response(_("Unable to write the file to disk!"), 500) + + total_chunks = int(request.form["dztotalchunkcount"]) + + if current_chunk + 1 == total_chunks: + # Validate the resulting file size after writing the last chunk + if path.getsize(tmp_save_path) != int(request.form["dztotalfilesize"]): + log.error("File size mismatch between the original file and transferred file.") + return make_response(_("Transferred file corrupted!"), 500) + + process = file_cmd.rename_file(Path(tmp_save_path), Path(save_path)) + if not process["status"]: + return make_response(_("Unable to rename temporary file!"), 500) + + return make_response(_("File upload successful!"), 200) @APP.route("/files/create", methods=["POST"]) @@ -1487,7 +1521,7 @@ if __name__ == "__main__": sock_cmd = SocketCmdsFlask(host=arguments.backend_host, port=arguments.backend_port) piscsi_cmd = PiscsiCmds(sock_cmd=sock_cmd, token=APP.config["PISCSI_TOKEN"]) - file_cmd = FileCmds(sock_cmd=sock_cmd, piscsi=piscsi_cmd, token=APP.config["PISCSI_TOKEN"]) + file_cmd = FileCmds(piscsi=piscsi_cmd) sys_cmd = SysCmds() if not piscsi_cmd.is_token_auth()["status"] and not APP.config["PISCSI_TOKEN"]: diff --git a/python/web/src/web_utils.py b/python/web/src/web_utils.py index e8acd00a..b89b1545 100644 --- a/python/web/src/web_utils.py +++ b/python/web/src/web_utils.py @@ -4,12 +4,11 @@ Module for PiSCSI Web Interface utility methods import logging from grp import getgrall -from os import path from pathlib import Path from ua_parser import user_agent_parser from re import findall -from flask import request, make_response, abort +from flask import request, abort from flask_babel import _ from werkzeug.utils import secure_filename @@ -325,42 +324,6 @@ def is_safe_path(file_name): return {"status": True, "msg": ""} -def upload_with_dropzonejs(image_dir): - """ - Takes (str) image_dir which is the path to the image dir to store files. - Opens a stream to transfer a file via the embedded dropzonejs library. - """ - log = logging.getLogger("pydrop") - file_object = request.files["file"] - file_name = secure_filename(file_object.filename) - - save_path = path.join(image_dir, file_name) - current_chunk = int(request.form["dzchunkindex"]) - - # Makes sure not to overwrite an existing file, - # but continues writing to a file transfer in progress - if path.exists(save_path) and current_chunk == 0: - return make_response(_("The file already exists!"), 400) - - try: - with open(save_path, "ab") as save: - save.seek(int(request.form["dzchunkbyteoffset"])) - save.write(file_object.stream.read()) - except OSError: - log.exception("Could not write to file") - return make_response(_("Unable to write the file to disk!"), 500) - - total_chunks = int(request.form["dztotalchunkcount"]) - - if current_chunk + 1 == total_chunks: - # Validate the resulting file size after writing the last chunk - if path.getsize(save_path) != int(request.form["dztotalfilesize"]): - log.error("File size mismatch between the original file and transferred file.") - return make_response(_("Transferred file corrupted!"), 500) - - return make_response(_("File upload successful!"), 200) - - def browser_supports_modern_themes(): """ Determines if the browser supports the HTML/CSS/JS features used in non-legacy themes. From 8cb41054099061182602325750ac64b204cada93 Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Wed, 1 Nov 2023 12:53:05 +0100 Subject: [PATCH 47/87] Fix SonarQube issues (#1276) * Fix SonarQube issues * Fix error handling when target ID for INQUIRY is missing --- cpp/controllers/controller_manager.cpp | 5 ++++- cpp/controllers/phase_handler.cpp | 22 ++++++++++++++++++++++ cpp/controllers/phase_handler.h | 13 +++---------- cpp/scsidump/scsidump_core.cpp | 4 ++-- cpp/test/phase_handler_test.cpp | 1 + cpp/test/scsi_controller_test.cpp | 1 + 6 files changed, 33 insertions(+), 13 deletions(-) create mode 100644 cpp/controllers/phase_handler.cpp diff --git a/cpp/controllers/controller_manager.cpp b/cpp/controllers/controller_manager.cpp index d7d2a540..3b55833d 100644 --- a/cpp/controllers/controller_manager.cpp +++ b/cpp/controllers/controller_manager.cpp @@ -15,7 +15,10 @@ using namespace std; shared_ptr ControllerManager::CreateScsiController(BUS& bus, int id) const { - return make_shared(bus, id); + auto controller = make_shared(bus, id); + controller->Init(); + + return controller; } bool ControllerManager::AttachToController(BUS& bus, int id, shared_ptr device) diff --git a/cpp/controllers/phase_handler.cpp b/cpp/controllers/phase_handler.cpp new file mode 100644 index 00000000..664bd095 --- /dev/null +++ b/cpp/controllers/phase_handler.cpp @@ -0,0 +1,22 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator PiSCSI +// for Raspberry Pi +// +// Copyright (C) 2023 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include "phase_handler.h" + +void PhaseHandler::Init() +{ + phase_executors[phase_t::busfree] = [this] () { BusFree(); }; + phase_executors[phase_t::selection] = [this] () { Selection(); }; + phase_executors[phase_t::dataout] = [this] () { DataOut(); }; + phase_executors[phase_t::datain] = [this] () { DataIn(); }; + phase_executors[phase_t::command] = [this] () { Command(); }; + phase_executors[phase_t::status] = [this] () { Status(); }; + phase_executors[phase_t::msgout] = [this] () { MsgOut(); }; + phase_executors[phase_t::msgin] = [this] () { MsgIn(); }; +} diff --git a/cpp/controllers/phase_handler.h b/cpp/controllers/phase_handler.h index a7c52035..949c122d 100644 --- a/cpp/controllers/phase_handler.h +++ b/cpp/controllers/phase_handler.h @@ -25,6 +25,8 @@ public: PhaseHandler() = default; virtual ~PhaseHandler() = default; + void Init(); + virtual void BusFree() = 0; virtual void Selection() = 0; virtual void Command() = 0; @@ -61,14 +63,5 @@ protected: private: - const unordered_map> phase_executors = { - { phase_t::busfree, [this] () { BusFree(); } }, - { phase_t::selection, [this] () { Selection(); } }, - { phase_t::dataout, [this] () { DataOut(); } }, - { phase_t::datain, [this] () { DataIn(); } }, - { phase_t::command, [this] () { Command(); } }, - { phase_t::status, [this] () { Status(); } }, - { phase_t::msgout, [this] () { MsgOut(); } }, - { phase_t::msgin, [this] () { MsgIn(); } }, - }; + unordered_map> phase_executors; }; diff --git a/cpp/scsidump/scsidump_core.cpp b/cpp/scsidump/scsidump_core.cpp index 05737842..41c1dabd 100644 --- a/cpp/scsidump/scsidump_core.cpp +++ b/cpp/scsidump/scsidump_core.cpp @@ -153,7 +153,7 @@ void ScsiDump::ParseArguments(span args) throw parser_exception("Missing filename"); } - if (!scan_bus && !inquiry && target_id == -1) { + if (!scan_bus && target_id == -1) { throw parser_exception("Missing target ID"); } @@ -638,7 +638,7 @@ int ScsiDump::DumpRestore() cout << "Transfered : " << inq_info.capacity * inq_info.sector_size << " bytes [" << inq_info.capacity * inq_info.sector_size / 1024 / 1024 << "MiB]\n"; cout << "Total time: " << duration << " seconds (" << duration / 60 << " minutes\n"; - cout << "Averate transfer rate: " << (inq_info.capacity * inq_info.sector_size / 8) / duration + cout << "Average transfer rate: " << (inq_info.capacity * inq_info.sector_size / 8) / duration << " bytes per second (" << (inq_info.capacity * inq_info.sector_size / 8) / duration / 1024 << " KiB per second)\n"; cout << DIVIDER << "\n"; diff --git a/cpp/test/phase_handler_test.cpp b/cpp/test/phase_handler_test.cpp index 70532b54..fc063d99 100644 --- a/cpp/test/phase_handler_test.cpp +++ b/cpp/test/phase_handler_test.cpp @@ -98,6 +98,7 @@ TEST(PhaseHandlerTest, Phases) TEST(PhaseHandlerTest, ProcessPhase) { MockPhaseHandler handler; + handler.Init(); handler.SetPhase(phase_t::selection); EXPECT_CALL(handler, Selection); diff --git a/cpp/test/scsi_controller_test.cpp b/cpp/test/scsi_controller_test.cpp index 4060bc3a..ff726479 100644 --- a/cpp/test/scsi_controller_test.cpp +++ b/cpp/test/scsi_controller_test.cpp @@ -31,6 +31,7 @@ TEST(ScsiControllerTest, Process) { auto bus = make_shared>(); MockScsiController controller(bus, 0); + controller.Init(); controller.SetPhase(phase_t::reserved); ON_CALL(*bus, GetRST).WillByDefault(Return(true)); From 37b9110c99911a756b4af8e3768fd82302651733 Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Wed, 1 Nov 2023 21:41:24 +0900 Subject: [PATCH 48/87] When Python code changes, build C++ and run static analysis --- .github/workflows/cpp.yml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml index 91e2ae5d..90f32085 100644 --- a/.github/workflows/cpp.yml +++ b/.github/workflows/cpp.yml @@ -1,23 +1,21 @@ -name: C++ Tests/Analysis +name: C++ Tests; Full Static Analysis on: workflow_dispatch: push: - paths: - - 'cpp/**' - - '.github/workflows/cpp.yml' + branches: + - 'develop' + - 'main' pull_request: paths: - 'cpp/**' + - 'python/**' - '.github/workflows/cpp.yml' types: - assigned - opened - synchronize - reopened - branches: - - 'develop' - - 'main' env: APT_PACKAGES: libspdlog-dev libpcap-dev libevdev2 libev-dev protobuf-compiler libgtest-dev libgmock-dev @@ -118,4 +116,4 @@ jobs: --define sonar.coverage.exclusions="cpp/**/test/**" --define sonar.cpd.exclusions="cpp/**/test/**" --define sonar.inclusions="cpp/**,python/**" - --define sonar.python.version=3.7,3.9 + --define sonar.python.version=3.9,3.11 From 063e8ed32b355f7a7a5911593fb7b1bf50f8a0fe Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Wed, 1 Nov 2023 22:28:53 +0900 Subject: [PATCH 49/87] Static analysis fixes for Python codebase (#1277) * Don't log names of internal symbols that can be used for attacks * Add integrity check to dropzone.js library * Better a11y of web form labels * Safer handling of file download paths * Don't invert boolean check * Make backend auth check a flask abort * Clean up indentation to remove unwanted tabs * Run workflow either on PR events, or branch pushes, not both --- .github/workflows/web.yml | 11 +--- python/common/src/piscsi/file_cmds.py | 14 ++--- .../ctrlboard_menu_update_event_handler.py | 3 +- .../ctrlboard/src/ctrlboard_menu_builder.py | 4 +- python/web/src/return_code_mapper.py | 2 +- .../web/src/static/themes/classic/style.css | 4 ++ python/web/src/static/themes/modern/style.css | 4 ++ python/web/src/templates/base.html | 62 +++++++++---------- python/web/src/templates/index.html | 2 + python/web/src/templates/upload.html | 13 +++- python/web/src/web.py | 19 +++--- python/web/tests/api/test_files.py | 2 +- 12 files changed, 79 insertions(+), 61 deletions(-) diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml index d22c4906..ce76fab7 100644 --- a/.github/workflows/web.yml +++ b/.github/workflows/web.yml @@ -3,11 +3,9 @@ name: Web Tests/Analysis on: workflow_dispatch: push: - paths: - - 'python/web/**' - - 'python/common/**' - - '.github/workflows/web.yml' - - 'easyinstall.sh' + branches: + - 'develop' + - 'main' pull_request: paths: - 'python/web/**' @@ -19,9 +17,6 @@ on: - opened - synchronize - reopened - branches: - - 'develop' - - 'main' jobs: backend_checks: diff --git a/python/common/src/piscsi/file_cmds.py b/python/common/src/piscsi/file_cmds.py index 9f696c59..ee8e2b37 100644 --- a/python/common/src/piscsi/file_cmds.py +++ b/python/common/src/piscsi/file_cmds.py @@ -64,7 +64,7 @@ class FileCmds: excluded_dirs = ("Network Trash Folder", "Temporary Items", "TheVolumeSettingsFolder") for root, dirs, _files in walk(directory, topdown=True): # Strip out dirs that begin with . - dirs[:] = [d for d in dirs if not d[0] == "."] + dirs[:] = [d for d in dirs if d[0] != "."] for dir in dirs: if dir not in excluded_dirs: dirpath = path.join(root, dir) @@ -488,12 +488,12 @@ class FileCmds: iso_filename = Path(server_info["image_dir"]) / f"{file_name}.iso" with TemporaryDirectory() as tmp_dir: - req_proc = self.download_to_dir(quote(url, safe=URL_SAFE), tmp_dir, file_name) + tmp_full_path = Path(tmp_dir) / file_name + req_proc = self.download_to_dir(quote(url, safe=URL_SAFE), tmp_full_path) logging.info("Downloaded %s to %s", file_name, tmp_dir) if not req_proc["status"]: return {"status": False, "msg": req_proc["msg"]} - tmp_full_path = Path(tmp_dir) / file_name if is_zipfile(tmp_full_path): if "XtraStuf.mac" in str(ZipFile(str(tmp_full_path)).namelist()): logging.info( @@ -565,9 +565,9 @@ class FileCmds: } # noinspection PyMethodMayBeStatic - def download_to_dir(self, url, save_dir, file_name): + def download_to_dir(self, url, target_path): """ - Takes (str) url, (str) save_dir, (str) file_name + Takes (str) url, (Path) target_path Returns (dict) with (bool) status and (str) msg """ logging.info("Making a request to download %s", url) @@ -580,7 +580,7 @@ class FileCmds: ) as req: req.raise_for_status() try: - with open(f"{save_dir}/{file_name}", "wb") as download: + with open(str(target_path), "wb") as download: for chunk in req.iter_content(chunk_size=8192): download.write(chunk) except FileNotFoundError as error: @@ -593,7 +593,7 @@ class FileCmds: logging.info("Response content-type: %s", req.headers["content-type"]) logging.info("Response status code: %s", req.status_code) - parameters = {"file_name": file_name, "save_dir": save_dir} + parameters = {"target_path": str(target_path)} return { "status": True, "return_code": ReturnCodes.DOWNLOADTODIR_SUCCESS, diff --git a/python/ctrlboard/src/ctrlboard_event_handler/ctrlboard_menu_update_event_handler.py b/python/ctrlboard/src/ctrlboard_event_handler/ctrlboard_menu_update_event_handler.py index 6b48d9f7..768e996f 100644 --- a/python/ctrlboard/src/ctrlboard_event_handler/ctrlboard_menu_update_event_handler.py +++ b/python/ctrlboard/src/ctrlboard_event_handler/ctrlboard_menu_update_event_handler.py @@ -107,8 +107,7 @@ class CtrlBoardMenuUpdateEventHandler(Observer): except AttributeError: log = logging.getLogger(__name__) log.error( - "Handler function [%s] not found or returned an error. Skipping.", - str(handler_function_name), + "Handler function not found or returned an error. Skipping.", ) # noinspection PyUnusedLocal diff --git a/python/ctrlboard/src/ctrlboard_menu_builder.py b/python/ctrlboard/src/ctrlboard_menu_builder.py index cfacbc23..3f322131 100644 --- a/python/ctrlboard/src/ctrlboard_menu_builder.py +++ b/python/ctrlboard/src/ctrlboard_menu_builder.py @@ -8,7 +8,7 @@ from piscsi.piscsi_cmds import PiscsiCmds class CtrlBoardMenuBuilder(MenuBuilder): - """Class fgor building the control board UI specific menus""" + """Class for building the control board UI specific menus""" SCSI_ID_MENU = "scsi_id_menu" ACTION_MENU = "action_menu" @@ -48,7 +48,7 @@ class CtrlBoardMenuBuilder(MenuBuilder): return self.create_device_info_menu(context_object) log = logging.getLogger(__name__) - log.warning("Provided menu name [%s] cannot be built!", name) + log.error("Provided menu name cannot be built!") return self.create_scsi_id_list_menu(context_object) diff --git a/python/web/src/return_code_mapper.py b/python/web/src/return_code_mapper.py index 95025d9b..dd5e43b9 100644 --- a/python/web/src/return_code_mapper.py +++ b/python/web/src/return_code_mapper.py @@ -23,7 +23,7 @@ class ReturnCodeMapper: ReturnCodes.DOWNLOADFILETOISO_SUCCESS: _("Created CD-ROM ISO image with arguments \"%(value)s\""), ReturnCodes.DOWNLOADTODIR_SUCCESS: - _("%(file_name)s downloaded to %(save_dir)s"), + _("Downloaded file to %(target_path)s"), ReturnCodes.WRITEFILE_SUCCESS: _("File created: %(target_path)s"), ReturnCodes.WRITEFILE_COULD_NOT_WRITE: diff --git a/python/web/src/static/themes/classic/style.css b/python/web/src/static/themes/classic/style.css index 3c8b754a..fa9d62e9 100644 --- a/python/web/src/static/themes/classic/style.css +++ b/python/web/src/static/themes/classic/style.css @@ -203,3 +203,7 @@ div.throttle-notice > div a { div.throttle-notice > div a:hover { text-decoration: underline; } + +label.hidden { + display: none; +} diff --git a/python/web/src/static/themes/modern/style.css b/python/web/src/static/themes/modern/style.css index a21a91ed..f91e9752 100644 --- a/python/web/src/static/themes/modern/style.css +++ b/python/web/src/static/themes/modern/style.css @@ -62,6 +62,10 @@ div.notice { color: #fff; } +label.hidden { + display: none; +} + /* ------------------------------------------------------------------------------ Tables diff --git a/python/web/src/templates/base.html b/python/web/src/templates/base.html index 38cbe09b..6d61e93e 100644 --- a/python/web/src/templates/base.html +++ b/python/web/src/templates/base.html @@ -113,46 +113,46 @@
- {% if env["netatalk_configured"] %} - {{ _("Mac AFP file sharing is enabled.") }} - {% if env["webmin_configured"] %} - - {{ ("Server administration") }} - - {% endif %} - {% else %} - {{ _("Mac AFP file sharing is disabled.") }} - {% endif %} + {% if env["netatalk_configured"] %} + {{ _("Mac AFP file sharing is enabled.") }} + {% if env["webmin_configured"] %} + + {{ ("Server administration") }} + + {% endif %} + {% else %} + {{ _("Mac AFP file sharing is disabled.") }} + {% endif %}
- {% if env["samba_configured"] %} - {{ _("Windows SMB file sharing is enabled.") }} - {% if env["webmin_configured"] %} - - {{ ("Server administration") }} - - {% endif %} - {% else %} - {{ _("Windows SMB file sharing is disabled.") }} - {% endif %} + {% if env["samba_configured"] %} + {{ _("Windows SMB file sharing is enabled.") }} + {% if env["webmin_configured"] %} + + {{ ("Server administration") }} + + {% endif %} + {% else %} + {{ _("Windows SMB file sharing is disabled.") }} + {% endif %}
- {% if env["ftp_configured"] %} - {{ _("FTP file sharing is enabled.") }} - {% else %} - {{ _("FTP file sharing is disabled.") }} - {% endif %} + {% if env["ftp_configured"] %} + {{ _("FTP file sharing is enabled.") }} + {% else %} + {{ _("FTP file sharing is disabled.") }} + {% endif %}
- {% if env["macproxy_configured"] %} - {{ _("Macproxy is running at %(ip_addr)s (default port 5000)", ip_addr=env['ip_addr']) }} - {% else %} - {{ _("Macproxy is disabled.") }} - {% endif %} + {% if env["macproxy_configured"] %} + {{ _("Macproxy is running at %(ip_addr)s (default port 5000)", ip_addr=env['ip_addr']) }} + {% else %} + {{ _("Macproxy is disabled.") }} + {% endif %}
- {{ _("PiSCSI software version:") }} {{ env["version"] }} + {{ _("PiSCSI software version:") }} {{ env["version"] }}
{{ _("Hardware and OS:") }} {{ env["running_env"]["env"] }} diff --git a/python/web/src/templates/index.html b/python/web/src/templates/index.html index fb3a5b32..fc7565ff 100644 --- a/python/web/src/templates/index.html +++ b/python/web/src/templates/index.html @@ -396,6 +396,7 @@ + + + + + - +