From c59a300a9734efe9d8e997159814f0f4fa2b8aa6 Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Sat, 11 Nov 2023 23:17:47 +0900 Subject: [PATCH 01/36] Update version for next development version --- cpp/shared/piscsi_version.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/shared/piscsi_version.cpp b/cpp/shared/piscsi_version.cpp index 05c297fd..cbc124d5 100644 --- a/cpp/shared/piscsi_version.cpp +++ b/cpp/shared/piscsi_version.cpp @@ -13,7 +13,7 @@ // The following should be updated for each release const int piscsi_major_version = 23; // Last two digits of year -const int piscsi_minor_version = 10; // Month +const int piscsi_minor_version = 11; // Month const int piscsi_patch_version = -1; // Patch number - increment for each update using namespace std; From 23e528626797905a4addb28cd85a54fc3052380a Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Mon, 13 Nov 2023 08:40:03 +0100 Subject: [PATCH 02/36] Fix BSY pin handling in initiator mode (#1312) * In initiator mode configure BSY as an output pin when BSY is set --- cpp/hal/gpiobus_raspberry.cpp | 42 +++++++++++++++++------------------ 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/cpp/hal/gpiobus_raspberry.cpp b/cpp/hal/gpiobus_raspberry.cpp index 5b6f77f7..44e09dfe 100644 --- a/cpp/hal/gpiobus_raspberry.cpp +++ b/cpp/hal/gpiobus_raspberry.cpp @@ -410,32 +410,30 @@ void GPIOBUS_Raspberry::SetBSY(bool ast) // Set BSY signal SetSignal(PIN_BSY, ast); - if (actmode == mode_e::TARGET) { - if (ast) { - // Turn on ACTIVE signal - SetControl(PIN_ACT, ACT_ON); + if (ast) { + // Turn on ACTIVE signal + SetControl(PIN_ACT, ACT_ON); - // Set Target signal to output - SetControl(PIN_TAD, TAD_OUT); + // Set Target signal to output + SetControl(PIN_TAD, TAD_OUT); - SetMode(PIN_BSY, OUT); - SetMode(PIN_MSG, OUT); - SetMode(PIN_CD, OUT); - SetMode(PIN_REQ, OUT); - SetMode(PIN_IO, OUT); - } else { - // Turn off the ACTIVE signal - SetControl(PIN_ACT, ACT_OFF); + SetMode(PIN_BSY, OUT); + SetMode(PIN_MSG, OUT); + SetMode(PIN_CD, OUT); + SetMode(PIN_REQ, OUT); + SetMode(PIN_IO, OUT); + } else { + // Turn off the ACTIVE signal + SetControl(PIN_ACT, ACT_OFF); - // Set the target signal to input - SetControl(PIN_TAD, TAD_IN); + // Set the target signal to input + SetControl(PIN_TAD, TAD_IN); - SetMode(PIN_BSY, IN); - SetMode(PIN_MSG, IN); - SetMode(PIN_CD, IN); - SetMode(PIN_REQ, IN); - SetMode(PIN_IO, IN); - } + SetMode(PIN_BSY, IN); + SetMode(PIN_MSG, IN); + SetMode(PIN_CD, IN); + SetMode(PIN_REQ, IN); + SetMode(PIN_IO, IN); } } From 67fd85b175b75f7263747580870e781497c8dd92 Mon Sep 17 00:00:00 2001 From: Benjamin Krein Date: Mon, 13 Nov 2023 17:52:05 -0500 Subject: [PATCH 03/36] reset Python venv if RESET_VENV is set --- docker/README.md | 15 +++++++++++---- docker/docker-compose.yml | 1 + python/web/start.sh | 6 ++++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/docker/README.md b/docker/README.md index 736ac6ef..df0f7e7b 100644 --- a/docker/README.md +++ b/docker/README.md @@ -40,10 +40,11 @@ The following environment variables are available when using Docker Compose: | `WEB_HTTP_PORT` | 8080 | | `WEB_HTTPS_PORT` | 8443 | | `WEB_LOG_LEVEL` | info | -| `BACKEND_HOST` | backend | -| `BACKEND_PORT` | 6868 | -| `BACKEND_PASSWORD` | *[None]* | -| `BACKEND_LOG_LEVEL` | debug | +| `BACKEND_HOST` | backend | +| `BACKEND_PORT` | 6868 | +| `BACKEND_PASSWORD` | *[None]* | +| `BACKEND_LOG_LEVEL` | debug | +| `RESET_VENV` | *[None]* | **Examples:** @@ -57,6 +58,12 @@ Start the web UI with the log level set to debug: WEB_LOG_LEVEL=debug docker compose up ``` +Force resetting & reinstalling Python web `venv` directory: + +``` +RESET_VENV=1 docker compose up +``` + ## Volumes When using Docker Compose the following volumes will be mounted automatically: diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index e3ea35a2..d18b8883 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -36,6 +36,7 @@ services: - "127.0.0.1:${WEB_HTTPS_PORT:-8443}:443" environment: - BACKEND_PASSWORD=${BACKEND_PASSWORD:-} + - RESET_VENV=${RESET_VENV:-} init: true command: [ "--backend-host=${BACKEND_HOST:-backend}", diff --git a/python/web/start.sh b/python/web/start.sh index 44a6ebed..09a3b1b7 100755 --- a/python/web/start.sh +++ b/python/web/start.sh @@ -36,6 +36,12 @@ if [ $ERROR = 1 ] ; then exit 1 fi +# Force rebuild the venv if RESET_VENV is set to any non-empty value +if [[ "$RESET_VENV" ]]; then + echo "Force-removing old venv" + sudo rm -rf venv +fi + # Test for two known broken venv states if test -e venv; then GOOD_VENV=true From 1f3e3e1245cd64577772509482dd0eb6e5d62b96 Mon Sep 17 00:00:00 2001 From: Benjamin Krein Date: Mon, 13 Nov 2023 19:16:04 -0500 Subject: [PATCH 04/36] automate interaction for webmin install --- easyinstall.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/easyinstall.sh b/easyinstall.sh index 21b1d5a7..c605980b 100755 --- a/easyinstall.sh +++ b/easyinstall.sh @@ -967,9 +967,9 @@ function installWebmin() { echo "Installing packages..." sudo apt-get install curl libcgi-session-perl --no-install-recommends --assume-yes Date: Mon, 13 Nov 2023 19:30:06 -0500 Subject: [PATCH 05/36] config webmin at /webmin path in nginx --- easyinstall.sh | 4 ++++ python/web/service-infra/nginx-default.conf | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/easyinstall.sh b/easyinstall.sh index 21b1d5a7..a6383357 100755 --- a/easyinstall.sh +++ b/easyinstall.sh @@ -986,6 +986,10 @@ function installWebmin() { sudo sed -i 's@/usr/sbin@/usr/local/sbin@' "$WEBMIN_MODULE_CONFIG" fi rm netatalk2-wbm.tgz || true + + # Configure Webmin to be accessible from a '/webmin' URL path + echo "webprefix=/webmin" | sudo tee -a /etc/webmin/config + echo "webprefixnoredir=1" | sudo tee -a /etc/webmin/config } # updates configuration files and installs packages needed for the OLED screen script diff --git a/python/web/service-infra/nginx-default.conf b/python/web/service-infra/nginx-default.conf index 575bca56..ec76431d 100644 --- a/python/web/service-infra/nginx-default.conf +++ b/python/web/service-infra/nginx-default.conf @@ -18,6 +18,12 @@ server { proxy_pass http://127.0.0.1:8080; } + # Configure Webmin to be accessed via '/webmin' URL path + # NOTE: Use of 'https' here is required as is the trailing '/' + location /webmin { + proxy_pass https://127.0.0.1:10000/; + } + # Large files client_max_body_size 0; proxy_read_timeout 1000; From f7bc77d978bf700abc6d80fdb2da8f20b935e950 Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Tue, 14 Nov 2023 15:13:45 +0100 Subject: [PATCH 06/36] Add initial IDE setup (#1326) --- cpp/.clang-format | 12 -- cpp/.gitignore | 3 - cpp/.vscode/launch.json | 53 -------- cpp/.vscode/tasks.json | 19 --- cpp/launch_sudo.sh | 3 - ide_setup/README | 5 + ide_setup/eclipse_code_formatter.xml | 193 +++++++++++++++++++++++++++ 7 files changed, 198 insertions(+), 90 deletions(-) delete mode 100644 cpp/.clang-format delete mode 100644 cpp/.vscode/launch.json delete mode 100644 cpp/.vscode/tasks.json delete mode 100755 cpp/launch_sudo.sh create mode 100644 ide_setup/README create mode 100644 ide_setup/eclipse_code_formatter.xml diff --git a/cpp/.clang-format b/cpp/.clang-format deleted file mode 100644 index 7e73f97c..00000000 --- a/cpp/.clang-format +++ /dev/null @@ -1,12 +0,0 @@ -BasedOnStyle: Microsoft -IndentWidth: 4 -AlwaysBreakAfterReturnType: None -AllowShortFunctionsOnASingleLine: Empty -KeepEmptyLinesAtTheStartOfBlocks: false -BreakBeforeBraces: Linux -AlignEscapedNewlines: Left -AlignTrailingComments: True -AllowShortEnumsOnASingleLine: True -AlignConsecutiveAssignments: Consecutive -ColumnLimit: 120 -PointerAlignment: Left \ No newline at end of file diff --git a/cpp/.gitignore b/cpp/.gitignore index 4efdbaa3..6f970d2d 100644 --- a/cpp/.gitignore +++ b/cpp/.gitignore @@ -12,6 +12,3 @@ obj bin coverage generated -.project -.cproject -.settings diff --git a/cpp/.vscode/launch.json b/cpp/.vscode/launch.json deleted file mode 100644 index f3a2f763..00000000 --- a/cpp/.vscode/launch.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "rascsi (gdb) Launch", - "type": "cppdbg", - "request": "launch", - "program": "${workspaceFolder}/bin/fullspec/rascsi", - "args": [], - "stopAtEntry": true, - "cwd": "${workspaceFolder}", - "environment": [], - "externalConsole": false, - "MIMode": "gdb", - "targetArchitecture": "arm", - "miDebuggerPath": "${workspaceFolder}/launch_sudo.sh", - "setupCommands": [ - { - "description": "Enable pretty-printing for gdb", - "text": "-enable-pretty-printing", - "ignoreFailures": true - } - ] - }, - { - "name": "rascsi_test (gdb) Launch", - "type": "cppdbg", - "request": "launch", - "program": "${workspaceFolder}/bin/fullspec/rascsi_test", - "args": [], - "stopAtEntry": true, - "cwd": "${fileDirname}", - "environment": [], - "externalConsole": false, - "MIMode": "gdb", - "setupCommands": [ - { - "description": "Enable pretty-printing for gdb", - "text": "-enable-pretty-printing", - "ignoreFailures": true - }, - { - "description": "Set Disassembly Flavor to Intel", - "text": "-gdb-set disassembly-flavor intel", - "ignoreFailures": true - } - ] - }, - ] -} \ No newline at end of file diff --git a/cpp/.vscode/tasks.json b/cpp/.vscode/tasks.json deleted file mode 100644 index 6105ee09..00000000 --- a/cpp/.vscode/tasks.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "type": "shell", - "label": "g++ build active file", - "command": "make", - "args": ["all", "DEBUG=1", "-j4"], - "options": { - "cwd": "${workspaceFolder}/" - }, - "problemMatcher": ["$gcc"], - "group": { - "kind": "build", - "isDefault": true - } - } - ] - } diff --git a/cpp/launch_sudo.sh b/cpp/launch_sudo.sh deleted file mode 100755 index 6ce283c1..00000000 --- a/cpp/launch_sudo.sh +++ /dev/null @@ -1,3 +0,0 @@ -# This is used for debugging. VisualStudio code will call this file when launching -# the debugger, instead of directly calling GDB. That way we can add the pkexec -sudo /usr/bin/gdb "$@" \ No newline at end of file diff --git a/ide_setup/README b/ide_setup/README new file mode 100644 index 00000000..3b0d2b6a --- /dev/null +++ b/ide_setup/README @@ -0,0 +1,5 @@ +The Eclipse code formatter configuration shall be used together with +Eclipse CDT in order to unify the formatting of the C++ code. Ensure to keep +your formatting rules up to date. + +This formatter can also be imported into Intellij IDEA. diff --git a/ide_setup/eclipse_code_formatter.xml b/ide_setup/eclipse_code_formatter.xml new file mode 100644 index 00000000..13b13d59 --- /dev/null +++ b/ide_setup/eclipse_code_formatter.xml @@ -0,0 +1,193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From f6d00a0e96d8856cafcd809324ebf48d90a0ff90 Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Tue, 14 Nov 2023 15:16:23 +0100 Subject: [PATCH 07/36] Fix error count calculation (#1356) --- cpp/devices/scsi_printer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/devices/scsi_printer.cpp b/cpp/devices/scsi_printer.cpp index 393e053e..dcb9188b 100644 --- a/cpp/devices/scsi_printer.cpp +++ b/cpp/devices/scsi_printer.cpp @@ -197,7 +197,7 @@ bool SCSIPrinter::WriteByteSequence(span buf) out.write((const char *)buf.data(), buf.size()); const bool status = out.fail(); - if (!status) { + if (status) { ++print_error_count; } From 71780449ff891a59eb0b0e3d53bf89032f29fe53 Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Tue, 14 Nov 2023 15:32:46 +0100 Subject: [PATCH 08/36] Move sector sizes lists from DeviceFactory to the respective devices (#1323) --- cpp/devices/device_factory.cpp | 52 ++++------------------- cpp/devices/device_factory.h | 29 +++++++++---- cpp/devices/disk.cpp | 6 +-- cpp/devices/disk.h | 14 +++---- cpp/devices/scsicd.cpp | 5 +-- cpp/devices/scsicd.h | 2 +- cpp/devices/scsihd.cpp | 6 +-- cpp/devices/scsihd.h | 2 +- cpp/devices/scsihd_nec.h | 2 +- cpp/devices/scsimo.cpp | 4 +- cpp/devices/scsimo.h | 2 +- cpp/piscsi/piscsi_core.cpp | 1 - cpp/piscsi/piscsi_core.h | 2 +- cpp/piscsi/piscsi_executor.cpp | 3 +- cpp/piscsi/piscsi_executor.h | 3 +- cpp/piscsi/piscsi_response.cpp | 69 ++++++++++++++++--------------- cpp/piscsi/piscsi_response.h | 7 ++-- cpp/test/device_factory_test.cpp | 35 ---------------- cpp/test/disk_test.cpp | 13 ++---- cpp/test/mocks.h | 12 ++++-- cpp/test/piscsi_executor_test.cpp | 10 +++-- cpp/test/scsicd_test.cpp | 24 +++++++---- cpp/test/scsihd_test.cpp | 27 ++++++++---- cpp/test/scsimo_test.cpp | 21 ++++++++-- 24 files changed, 160 insertions(+), 191 deletions(-) diff --git a/cpp/devices/device_factory.cpp b/cpp/devices/device_factory.cpp index f951d645..011a496b 100644 --- a/cpp/devices/device_factory.cpp +++ b/cpp/devices/device_factory.cpp @@ -7,7 +7,7 @@ // //--------------------------------------------------------------------------- -#include "shared/network_util.h" +#include "shared/piscsi_util.h" #include "scsihd.h" #include "scsihd_nec.h" #include "scsimo.h" @@ -20,39 +20,14 @@ using namespace std; using namespace piscsi_util; -using namespace network_util; - -DeviceFactory::DeviceFactory() -{ - sector_sizes[SCHD] = { 512, 1024, 2048, 4096 }; - sector_sizes[SCRM] = { 512, 1024, 2048, 4096 }; - sector_sizes[SCMO] = { 512, 1024, 2048, 4096 }; - sector_sizes[SCCD] = { 512, 2048}; - - extension_mapping["hd1"] = SCHD; - extension_mapping["hds"] = SCHD; - extension_mapping["hda"] = SCHD; - extension_mapping["hdn"] = SCHD; - extension_mapping["hdi"] = SCHD; - extension_mapping["nhd"] = SCHD; - extension_mapping["hdr"] = SCRM; - extension_mapping["mos"] = SCMO; - extension_mapping["iso"] = SCCD; - extension_mapping["is1"] = SCCD; - - device_mapping["bridge"] = SCBR; - device_mapping["daynaport"] = SCDP; - device_mapping["printer"] = SCLP; - device_mapping["services"] = SCHS; -} PbDeviceType DeviceFactory::GetTypeForFile(const string& filename) const { - if (const auto& it = extension_mapping.find(GetExtensionLowerCase(filename)); it != extension_mapping.end()) { + if (const auto& it = EXTENSION_MAPPING.find(GetExtensionLowerCase(filename)); it != EXTENSION_MAPPING.end()) { return it->second; } - if (const auto& it = device_mapping.find(filename); it != device_mapping.end()) { + if (const auto& it = DEVICE_MAPPING.find(filename); it != DEVICE_MAPPING.end()) { return it->second; } @@ -75,8 +50,7 @@ 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(type)->second, false, - ext == "hd1" ? scsi_level::scsi_1_ccs : scsi_level::scsi_2); + device = make_shared(lun, false, ext == "hd1" ? scsi_level::scsi_1_ccs : scsi_level::scsi_2); // Some Apple tools require a particular drive identification if (ext == "hda") { @@ -88,17 +62,17 @@ shared_ptr DeviceFactory::CreateDevice(PbDeviceType type, int lun } case SCRM: - device = make_shared(lun, sector_sizes.find(type)->second, true); + device = make_shared(lun, true, scsi_level::scsi_2); device->SetProduct("SCSI HD (REM.)"); break; case SCMO: - device = make_shared(lun, sector_sizes.find(type)->second); + device = make_shared(lun); device->SetProduct("SCSI MO"); break; case SCCD: - device = make_shared(lun, sector_sizes.find(type)->second, + device = make_shared(lun, GetExtensionLowerCase(filename) == "is1" ? scsi_level::scsi_1_ccs : scsi_level::scsi_2); device->SetProduct("SCSI CD-ROM"); break; @@ -135,15 +109,3 @@ shared_ptr DeviceFactory::CreateDevice(PbDeviceType type, int lun return device; } - -// 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); - 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 fef1c17d..7072980c 100644 --- a/cpp/devices/device_factory.h +++ b/cpp/devices/device_factory.h @@ -11,9 +11,7 @@ #pragma once -#include "shared/piscsi_util.h" #include -#include #include #include "generated/piscsi_interface.pb.h" @@ -27,19 +25,32 @@ class DeviceFactory public: - DeviceFactory(); + DeviceFactory() = default; ~DeviceFactory() = default; shared_ptr CreateDevice(PbDeviceType, int, const string&) const; PbDeviceType GetTypeForFile(const string&) const; - unordered_set GetSectorSizes(PbDeviceType type) const; - const auto& GetExtensionMapping() const { return extension_mapping; } + const auto& GetExtensionMapping() const { return EXTENSION_MAPPING; } private: - unordered_map> sector_sizes; + const inline static unordered_map> EXTENSION_MAPPING = { + { "hd1", SCHD }, + { "hds", SCHD }, + { "hda", SCHD }, + { "hdn", SCHD }, + { "hdi", SCHD }, + { "nhd", SCHD }, + { "hdr", SCRM }, + { "mos", SCMO }, + { "is1", SCCD }, + { "iso", SCCD } + }; - unordered_map> extension_mapping; - - unordered_map> device_mapping; + const inline static unordered_map> DEVICE_MAPPING = { + { "bridge", SCBR }, + { "daynaport", SCDP }, + { "printer", SCLP }, + { "services", SCHS } + }; }; diff --git a/cpp/devices/disk.cpp b/cpp/devices/disk.cpp index bc8b53d0..7ae8f275 100644 --- a/cpp/devices/disk.cpp +++ b/cpp/devices/disk.cpp @@ -695,7 +695,7 @@ uint32_t Disk::GetSectorSizeInBytes() const void Disk::SetSectorSizeInBytes(uint32_t size_in_bytes) { - if (DeviceFactory device_factory; !device_factory.GetSectorSizes(GetType()).contains(size_in_bytes)) { + if (!GetSupportedSectorSizes().contains(size_in_bytes)) { throw io_exception("Invalid sector size of " + to_string(size_in_bytes) + " byte(s)"); } @@ -708,9 +708,9 @@ uint32_t Disk::GetConfiguredSectorSize() const return configured_sector_size; } -bool Disk::SetConfiguredSectorSize(const DeviceFactory& device_factory, uint32_t configured_size) +bool Disk::SetConfiguredSectorSize(uint32_t configured_size) { - if (!device_factory.GetSectorSizes(GetType()).contains(configured_size)) { + if (!supported_sector_sizes.contains(configured_size)) { return false; } diff --git a/cpp/devices/disk.h b/cpp/devices/disk.h index 7364ad08..6bb54d66 100644 --- a/cpp/devices/disk.h +++ b/cpp/devices/disk.h @@ -16,7 +16,6 @@ #include "shared/scsi.h" #include "shared/piscsi_util.h" -#include "device_factory.h" #include "disk_track.h" #include "disk_cache.h" #include "interfaces/scsi_block_commands.h" @@ -35,8 +34,7 @@ class Disk : public StorageDevice, private ScsiBlockCommands unique_ptr cache; - // The supported configurable sector sizes, empty if not configurable - unordered_set sector_sizes; + unordered_set supported_sector_sizes; uint32_t configured_sector_size = 0; // Sector size shift count (9=512, 10=1024, 11=2048, 12=4096) @@ -50,7 +48,9 @@ class Disk : public StorageDevice, private ScsiBlockCommands public: - using StorageDevice::StorageDevice; + Disk(PbDeviceType type, int lun, const unordered_set& s) + : StorageDevice(type, lun), supported_sector_sizes(s) {} + ~Disk() override = default; bool Init(const param_map&) override; void CleanUp() override; @@ -64,8 +64,9 @@ public: virtual int Read(span , uint64_t); uint32_t GetSectorSizeInBytes() const; - bool IsSectorSizeConfigurable() const { return !sector_sizes.empty(); } - bool SetConfiguredSectorSize(const DeviceFactory&, uint32_t); + bool IsSectorSizeConfigurable() const { return supported_sector_sizes.size() > 1; } + const auto& GetSupportedSectorSizes() const { return supported_sector_sizes; } + bool SetConfiguredSectorSize(uint32_t); void FlushCache() override; vector GetStatistics() const override; @@ -119,7 +120,6 @@ protected: void AddCachePage(map>&, bool) const; unordered_set GetSectorSizes() const; - void SetSectorSizes(const unordered_set& sizes) { sector_sizes = sizes; } void SetSectorSizeInBytes(uint32_t); uint32_t GetSectorSizeShiftCount() const { return size_shift_count; } void SetSectorSizeShiftCount(uint32_t count) { size_shift_count = count; } diff --git a/cpp/devices/scsicd.cpp b/cpp/devices/scsicd.cpp index 200c36c3..27ab23a3 100644 --- a/cpp/devices/scsicd.cpp +++ b/cpp/devices/scsicd.cpp @@ -21,11 +21,8 @@ using namespace scsi_defs; using namespace scsi_command_util; -SCSICD::SCSICD(int lun, const unordered_set& sector_sizes, scsi_defs::scsi_level level) - : Disk(SCCD, lun), scsi_level(level) +SCSICD::SCSICD(int lun, scsi_defs::scsi_level level) : Disk(SCCD, lun, { 512, 2048 }), scsi_level(level) { - SetSectorSizes(sector_sizes); - SetReadOnly(true); SetRemovable(true); SetLockable(true); diff --git a/cpp/devices/scsicd.h b/cpp/devices/scsicd.h index db2620de..c2aaf4e3 100644 --- a/cpp/devices/scsicd.h +++ b/cpp/devices/scsicd.h @@ -25,7 +25,7 @@ class SCSICD : public Disk, private ScsiMmcCommands { public: - SCSICD(int, const unordered_set&, scsi_defs::scsi_level = scsi_level::scsi_2); + SCSICD(int, scsi_defs::scsi_level = scsi_level::scsi_2); ~SCSICD() override = default; bool Init(const param_map&) override; diff --git a/cpp/devices/scsihd.cpp b/cpp/devices/scsihd.cpp index 316c7575..09c6645a 100644 --- a/cpp/devices/scsihd.cpp +++ b/cpp/devices/scsihd.cpp @@ -19,11 +19,9 @@ using namespace scsi_command_util; -SCSIHD::SCSIHD(int lun, const unordered_set& sector_sizes, bool removable, scsi_defs::scsi_level level) - : Disk(removable ? SCRM : SCHD, lun), scsi_level(level) +SCSIHD::SCSIHD(int lun, bool removable, scsi_defs::scsi_level level, const unordered_set& sector_sizes) + : Disk(removable ? SCRM : SCHD, lun, sector_sizes), scsi_level(level) { - SetSectorSizes(sector_sizes); - SetProtectable(true); SetRemovable(removable); SetLockable(removable); diff --git a/cpp/devices/scsihd.h b/cpp/devices/scsihd.h index 8216ae2a..28a6119d 100644 --- a/cpp/devices/scsihd.h +++ b/cpp/devices/scsihd.h @@ -28,7 +28,7 @@ class SCSIHD : public Disk public: - SCSIHD(int, const unordered_set&, bool, scsi_defs::scsi_level = scsi_level::scsi_2); + SCSIHD(int, bool, scsi_defs::scsi_level, const unordered_set& = { 512, 1024, 2048, 4096 }); ~SCSIHD() override = default; void FinalizeSetup(off_t); diff --git a/cpp/devices/scsihd_nec.h b/cpp/devices/scsihd_nec.h index eec3c1be..4c2fabe2 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, { 512 }, false) {} + explicit SCSIHD_NEC(int lun) : SCSIHD(lun, false, scsi_level::scsi_1_ccs, { 512 }) {} ~SCSIHD_NEC() override = default; void Open() override; diff --git a/cpp/devices/scsimo.cpp b/cpp/devices/scsimo.cpp index 167064a1..57311be8 100644 --- a/cpp/devices/scsimo.cpp +++ b/cpp/devices/scsimo.cpp @@ -19,10 +19,8 @@ using namespace scsi_command_util; -SCSIMO::SCSIMO(int lun, const unordered_set& sector_sizes) : Disk(SCMO, lun) +SCSIMO::SCSIMO(int lun) : Disk(SCMO, lun, { 512, 1024, 2048, 4096 }) { - SetSectorSizes(sector_sizes); - // 128 MB, 512 bytes per sector, 248826 sectors geometries[512 * 248826] = { 512, 248826 }; // 230 MB, 512 bytes per block, 446325 sectors diff --git a/cpp/devices/scsimo.h b/cpp/devices/scsimo.h index 41735d3b..1ef651b8 100644 --- a/cpp/devices/scsimo.h +++ b/cpp/devices/scsimo.h @@ -26,7 +26,7 @@ class SCSIMO : public Disk { public: - SCSIMO(int, const unordered_set&); + explicit SCSIMO(int); ~SCSIMO() override = default; void Open() override; diff --git a/cpp/piscsi/piscsi_core.cpp b/cpp/piscsi/piscsi_core.cpp index 553e2297..bd491b8c 100644 --- a/cpp/piscsi/piscsi_core.cpp +++ b/cpp/piscsi/piscsi_core.cpp @@ -17,7 +17,6 @@ #include "shared/piscsi_version.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" diff --git a/cpp/piscsi/piscsi_core.h b/cpp/piscsi/piscsi_core.h index 1560851d..8d8b917f 100644 --- a/cpp/piscsi/piscsi_core.h +++ b/cpp/piscsi/piscsi_core.h @@ -67,7 +67,7 @@ private: PiscsiImage piscsi_image; - PiscsiResponse response; + [[no_unique_address]] PiscsiResponse response; PiscsiService service; diff --git a/cpp/piscsi/piscsi_executor.cpp b/cpp/piscsi/piscsi_executor.cpp index d930184e..319623eb 100644 --- a/cpp/piscsi/piscsi_executor.cpp +++ b/cpp/piscsi/piscsi_executor.cpp @@ -10,7 +10,6 @@ #include "shared/piscsi_util.h" #include "shared/protobuf_util.h" #include "shared/piscsi_exceptions.h" -#include "devices/device_factory.h" #include "devices/disk.h" #include "localizer.h" #include "command_context.h" @@ -530,7 +529,7 @@ bool PiscsiExecutor::SetSectorSize(const CommandContext& context, shared_ptr(device); if (disk != nullptr && disk->IsSectorSizeConfigurable()) { - if (!disk->SetConfiguredSectorSize(device_factory, size)) { + if (!disk->SetConfiguredSectorSize(size)) { return context.ReturnLocalizedError(LocalizationKey::ERROR_BLOCK_SIZE, to_string(size)); } } diff --git a/cpp/piscsi/piscsi_executor.h b/cpp/piscsi/piscsi_executor.h index bae4fe60..6c4ea1a9 100644 --- a/cpp/piscsi/piscsi_executor.h +++ b/cpp/piscsi/piscsi_executor.h @@ -11,6 +11,7 @@ #include "hal/bus.h" #include "controllers/controller_manager.h" +#include "devices/device_factory.h" #include class DeviceFactory; @@ -60,7 +61,7 @@ private: ControllerManager& controller_manager; - const DeviceFactory device_factory; + [[no_unique_address]] const DeviceFactory device_factory; unordered_set reserved_ids; }; diff --git a/cpp/piscsi/piscsi_response.cpp b/cpp/piscsi/piscsi_response.cpp index 28e7e1a2..1f4d2fe0 100644 --- a/cpp/piscsi/piscsi_response.cpp +++ b/cpp/piscsi/piscsi_response.cpp @@ -24,26 +24,29 @@ using namespace piscsi_util; using namespace network_util; using namespace protobuf_util; -void PiscsiResponse::GetDeviceProperties(const Device& device, PbDeviceProperties& properties) const +void PiscsiResponse::GetDeviceProperties(shared_ptr device, PbDeviceProperties& properties) const { 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()); + 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.GetDefaultParams()) { + if (device->SupportsParams()) { + for (const auto& [key, value] : device->GetDefaultParams()) { 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); + shared_ptr disk = dynamic_pointer_cast(device); + if (disk != nullptr && disk->IsSectorSizeConfigurable()) { + for (const auto& sector_size : disk->GetSupportedSectorSizes()) { + properties.add_block_sizes(sector_size); + } } } @@ -52,7 +55,7 @@ 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, ""); - GetDeviceProperties(*device, *type_properties->mutable_properties()); + GetDeviceProperties(device, *type_properties->mutable_properties()); } void PiscsiResponse::GetDeviceTypesInfo(PbDeviceTypesInfo& device_types_info) const @@ -67,37 +70,37 @@ void PiscsiResponse::GetDeviceTypesInfo(PbDeviceTypesInfo& device_types_info) co } } -void PiscsiResponse::GetDevice(const Device& device, PbDevice& pb_device, const string& default_folder) const +void PiscsiResponse::GetDevice(shared_ptr device, PbDevice& pb_device, const string& default_folder) const { - pb_device.set_id(device.GetId()); - pb_device.set_unit(device.GetLun()); - pb_device.set_vendor(device.GetVendor()); - pb_device.set_product(device.GetProduct()); - pb_device.set_revision(device.GetRevision()); - pb_device.set_type(device.GetType()); + pb_device.set_id(device->GetId()); + pb_device.set_unit(device->GetLun()); + pb_device.set_vendor(device->GetVendor()); + pb_device.set_product(device->GetProduct()); + pb_device.set_revision(device->GetRevision()); + pb_device.set_type(device->GetType()); GetDeviceProperties(device, *pb_device.mutable_properties()); 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()); + status->set_protected_(device->IsProtected()); + status->set_stopped(device->IsStopped()); + status->set_removed(device->IsRemoved()); + status->set_locked(device->IsLocked()); - if (device.SupportsParams()) { - for (const auto& [key, value] : device.GetParams()) { + if (device->SupportsParams()) { + for (const auto& [key, value] : device->GetParams()) { SetParam(pb_device, key, value); } } - if (const auto disk = dynamic_cast(&device); disk) { - pb_device.set_block_size(device.IsRemoved()? 0 : disk->GetSectorSizeInBytes()); - pb_device.set_block_count(device.IsRemoved() ? 0: disk->GetBlockCount()); + if (const auto disk = dynamic_pointer_cast(device); disk) { + pb_device.set_block_size(device->IsRemoved()? 0 : disk->GetSectorSizeInBytes()); + pb_device.set_block_count(device->IsRemoved() ? 0: disk->GetBlockCount()); } - const auto storage_device = dynamic_cast(&device); + const auto storage_device = dynamic_pointer_cast(device); if (storage_device != nullptr) { - GetImageFile(*pb_device.mutable_file(), default_folder, device.IsReady() ? storage_device->GetFilename() : ""); + GetImageFile(*pb_device.mutable_file(), default_folder, device->IsReady() ? storage_device->GetFilename() : ""); } } @@ -191,7 +194,7 @@ void PiscsiResponse::GetDevices(const unordered_set>& { for (const auto& device : devices) { PbDevice *pb_device = server_info.mutable_devices_info()->add_devices(); - GetDevice(*device, *pb_device, default_folder); + GetDevice(device, *pb_device, default_folder); } } @@ -218,7 +221,7 @@ void PiscsiResponse::GetDevicesInfo(const unordered_setGetId() == id && d->GetLun() == lun) { - GetDevice(*d, *devices_info->add_devices(), default_folder); + GetDevice(d, *devices_info->add_devices(), default_folder); break; } } diff --git a/cpp/piscsi/piscsi_response.h b/cpp/piscsi/piscsi_response.h index b095416f..0015655c 100644 --- a/cpp/piscsi/piscsi_response.h +++ b/cpp/piscsi/piscsi_response.h @@ -47,11 +47,10 @@ 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; + [[no_unique_address]] const DeviceFactory device_factory; - void GetDeviceProperties(const Device&, PbDeviceProperties&) const; - void GetDevice(const Device&, PbDevice&, const string&) const; + void GetDeviceProperties(shared_ptr, PbDeviceProperties&) const; + void GetDevice(shared_ptr, PbDevice&, const string&) const; void GetDeviceTypeProperties(PbDeviceTypesInfo&, PbDeviceType) const; void GetAvailableImages(PbImageFilesInfo&, const string&, const string&, const string&, int) const; void GetAvailableImages(PbServerInfo&, const string&, const string&, const string&, int) const; diff --git a/cpp/test/device_factory_test.cpp b/cpp/test/device_factory_test.cpp index 0c51c194..ce74398b 100644 --- a/cpp/test/device_factory_test.cpp +++ b/cpp/test/device_factory_test.cpp @@ -39,41 +39,6 @@ TEST(DeviceFactoryTest, GetTypeForFile) EXPECT_EQ(device_factory.GetTypeForFile("test.iso.suffix"), UNDEFINED); } -TEST(DeviceFactoryTest, GetSectorSizes) -{ - DeviceFactory device_factory; - - unordered_set sector_sizes = device_factory.GetSectorSizes(SCHD); - EXPECT_EQ(4, sector_sizes.size()); - - 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.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.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.contains(512)); - EXPECT_TRUE(sector_sizes.contains(2048)); -} - TEST(DeviceFactoryTest, GetExtensionMapping) { DeviceFactory device_factory; diff --git a/cpp/test/disk_test.cpp b/cpp/test/disk_test.cpp index b0206403..f7ad298f 100644 --- a/cpp/test/disk_test.cpp +++ b/cpp/test/disk_test.cpp @@ -774,14 +774,8 @@ TEST(DiskTest, SectorSize) { MockDisk disk; - unordered_set sizes = { 512, 1024 }; - disk.SetSectorSizes(sizes); EXPECT_TRUE(disk.IsSectorSizeConfigurable()); - sizes.clear(); - disk.SetSectorSizes(sizes); - EXPECT_FALSE(disk.IsSectorSizeConfigurable()); - disk.SetSectorSizeShiftCount(9); EXPECT_EQ(9, disk.GetSectorSizeShiftCount()); EXPECT_EQ(512, disk.GetSectorSizeInBytes()); @@ -815,13 +809,12 @@ TEST(DiskTest, SectorSize) TEST(DiskTest, ConfiguredSectorSize) { - DeviceFactory device_factory; - MockSCSIHD disk(0, {}, false); + MockSCSIHD disk(0, false); - EXPECT_TRUE(disk.SetConfiguredSectorSize(device_factory, 512)); + EXPECT_TRUE(disk.SetConfiguredSectorSize(512)); EXPECT_EQ(512, disk.GetConfiguredSectorSize()); - EXPECT_FALSE(disk.SetConfiguredSectorSize(device_factory, 1234)); + EXPECT_FALSE(disk.SetConfiguredSectorSize(1234)); EXPECT_EQ(512, disk.GetConfiguredSectorSize()); } diff --git a/cpp/test/mocks.h b/cpp/test/mocks.h index fcf45cd5..cc761802 100644 --- a/cpp/test/mocks.h +++ b/cpp/test/mocks.h @@ -352,7 +352,7 @@ public: MOCK_METHOD(void, FlushCache, (), (override)); MOCK_METHOD(void, Open, (), (override)); - MockDisk() : Disk(SCHD, 0) {} + MockDisk() : Disk(SCHD, 0, { 512, 1024, 2048, 4096 }) {} ~MockDisk() override = default; }; @@ -363,10 +363,15 @@ class MockSCSIHD : public SCSIHD //NOSONAR Ignore inheritance hierarchy depth in FRIEND_TEST(ScsiHdTest, FinalizeSetup); FRIEND_TEST(ScsiHdTest, GetProductData); FRIEND_TEST(ScsiHdTest, SetUpModePages); - FRIEND_TEST(PiscsiExecutorTest, SetSectorSize); + FRIEND_TEST(ScsiHdTest, GetSectorSizes); FRIEND_TEST(ScsiHdTest, ModeSelect); + FRIEND_TEST(PiscsiExecutorTest, SetSectorSize); - using SCSIHD::SCSIHD; +public: + + MockSCSIHD(int lun, bool removable) : SCSIHD(lun, removable, scsi_level::scsi_2) {} + explicit MockSCSIHD(const unordered_set& sector_sizes) : SCSIHD(0, false, scsi_level::scsi_2, sector_sizes) {} + ~MockSCSIHD() override = default; }; class MockSCSIHD_NEC : public SCSIHD_NEC //NOSONAR Ignore inheritance hierarchy depth in unit tests @@ -382,6 +387,7 @@ class MockSCSIHD_NEC : public SCSIHD_NEC //NOSONAR Ignore inheritance hierarchy class MockSCSICD : public SCSICD //NOSONAR Ignore inheritance hierarchy depth in unit tests { + FRIEND_TEST(ScsiCdTest, GetSectorSizes); FRIEND_TEST(ScsiCdTest, SetUpModePages); FRIEND_TEST(ScsiCdTest, ReadToc); diff --git a/cpp/test/piscsi_executor_test.cpp b/cpp/test/piscsi_executor_test.cpp index d6557be4..06b218a1 100644 --- a/cpp/test/piscsi_executor_test.cpp +++ b/cpp/test/piscsi_executor_test.cpp @@ -252,8 +252,6 @@ TEST(PiscsiExecutorTest, Attach) TEST(PiscsiExecutorTest, Insert) { - DeviceFactory device_factory; - auto bus = make_shared(); ControllerManager controller_manager; auto [controller, device] = CreateDevice(SCHD); @@ -500,13 +498,17 @@ TEST(PiscsiExecutorTest, SetSectorSize) CommandContext context(command, "", ""); unordered_set sizes; - auto hd = make_shared(0, sizes, false); + auto hd = make_shared(sizes); EXPECT_FALSE(executor.SetSectorSize(context, hd, 512)); sizes.insert(512); - hd = make_shared(0, sizes, false); + hd = make_shared(sizes); EXPECT_TRUE(executor.SetSectorSize(context, hd, 0)); EXPECT_FALSE(executor.SetSectorSize(context, hd, 1)); + EXPECT_FALSE(executor.SetSectorSize(context, hd, 512)); + + sizes.insert(1024); + hd = make_shared(sizes); EXPECT_TRUE(executor.SetSectorSize(context, hd, 512)); } diff --git a/cpp/test/scsicd_test.cpp b/cpp/test/scsicd_test.cpp index fa5d8743..b4c9154f 100644 --- a/cpp/test/scsicd_test.cpp +++ b/cpp/test/scsicd_test.cpp @@ -34,10 +34,21 @@ TEST(ScsiCdTest, Inquiry) TestInquiry::Inquiry(SCCD, device_type::cd_rom, scsi_level::scsi_1_ccs, "PiSCSI SCSI CD-ROM ", 0x1f, true, "file.is1"); } +TEST(ScsiCdTest, GetSectorSizes) +{ + MockSCSICD cd(0); + + const auto& sector_sizes = cd.GetSupportedSectorSizes(); + EXPECT_EQ(2, sector_sizes.size()); + + EXPECT_TRUE(sector_sizes.contains(512)); + EXPECT_TRUE(sector_sizes.contains(2048)); +} + TEST(ScsiCdTest, SetUpModePages) { map> pages; - MockSCSICD cd(0, {}); + MockSCSICD cd(0); // Non changeable cd.SetUpModePages(pages, 0x3f, false); @@ -51,10 +62,10 @@ TEST(ScsiCdTest, SetUpModePages) TEST(ScsiCdTest, Open) { - MockSCSICD cd_iso(0, {}); - MockSCSICD cd_cue(0, {}); - MockSCSICD cd_raw(0, {}); - MockSCSICD cd_physical(0, {}); + 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"; @@ -111,8 +122,7 @@ TEST(ScsiCdTest, Open) TEST(ScsiCdTest, ReadToc) { auto controller = make_shared(); - const unordered_set sector_sizes; - auto cd = make_shared(0, sector_sizes); + auto cd = make_shared(0); EXPECT_TRUE(cd->Init({})); controller->AddDevice(cd); diff --git a/cpp/test/scsihd_test.cpp b/cpp/test/scsihd_test.cpp index 1cdacad0..693f450b 100644 --- a/cpp/test/scsihd_test.cpp +++ b/cpp/test/scsihd_test.cpp @@ -30,14 +30,14 @@ TEST(ScsiHdTest, Inquiry) TEST(ScsiHdTest, SupportsSaveParameters) { - MockSCSIHD hd(0, {}, false); + MockSCSIHD hd(0, false); EXPECT_TRUE(hd.SupportsSaveParameters()); } TEST(ScsiHdTest, FinalizeSetup) { - MockSCSIHD hd(0, {}, false); + MockSCSIHD hd(0, false); hd.SetSectorSizeInBytes(1024); EXPECT_THROW(hd.FinalizeSetup(0), io_exception) << "Device has 0 blocks"; @@ -45,9 +45,9 @@ TEST(ScsiHdTest, FinalizeSetup) TEST(ScsiHdTest, GetProductData) { - MockSCSIHD hd_kb(0, {}, false); - MockSCSIHD hd_mb(0, {}, false); - MockSCSIHD hd_gb(0, {}, 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)); @@ -73,10 +73,23 @@ TEST(ScsiHdTest, GetProductData) remove(filename); } +TEST(ScsiHdTest, GetSectorSizes) +{ + MockSCSIHD hd(0, false); + + const auto& sector_sizes = hd.GetSupportedSectorSizes(); + EXPECT_EQ(4, sector_sizes.size()); + + EXPECT_TRUE(sector_sizes.contains(512)); + EXPECT_TRUE(sector_sizes.contains(1024)); + EXPECT_TRUE(sector_sizes.contains(2048)); + EXPECT_TRUE(sector_sizes.contains(4096)); +} + TEST(ScsiHdTest, SetUpModePages) { map> pages; - MockSCSIHD hd(0, {}, false); + MockSCSIHD hd(0, false); // Non changeable hd.SetUpModePages(pages, 0x3f, false); @@ -90,7 +103,7 @@ TEST(ScsiHdTest, SetUpModePages) TEST(ScsiHdTest, ModeSelect) { - MockSCSIHD hd(0, { 512 }, false); + MockSCSIHD hd({ 512 }); vector cmd(10); vector buf(255); diff --git a/cpp/test/scsimo_test.cpp b/cpp/test/scsimo_test.cpp index 2422c479..d38ee4a4 100644 --- a/cpp/test/scsimo_test.cpp +++ b/cpp/test/scsimo_test.cpp @@ -28,15 +28,28 @@ TEST(ScsiMoTest, Inquiry) TEST(ScsiMoTest, SupportsSaveParameters) { map> pages; - MockSCSIMO mo(0, {}); + MockSCSIMO mo(0); EXPECT_TRUE(mo.SupportsSaveParameters()); } +TEST(ScsiMoTest, GetSectorSizes) +{ + MockSCSIMO mo(0); + + const auto& sector_sizes = mo.GetSupportedSectorSizes(); + EXPECT_EQ(4, sector_sizes.size()); + + EXPECT_TRUE(sector_sizes.contains(512)); + EXPECT_TRUE(sector_sizes.contains(1024)); + EXPECT_TRUE(sector_sizes.contains(2048)); + EXPECT_TRUE(sector_sizes.contains(4096)); +} + TEST(ScsiMoTest, SetUpModePages) { map> pages; - MockSCSIMO mo(0, {}); + MockSCSIMO mo(0); // Non changeable mo.SetUpModePages(pages, 0x3f, false); @@ -51,7 +64,7 @@ TEST(ScsiMoTest, SetUpModePages) TEST(ScsiMoTest, TestAddVendorPage) { map> pages; - MockSCSIMO mo(0, {}); + MockSCSIMO mo(0); mo.SetReady(true); mo.SetUpModePages(pages, 0x21, false); @@ -122,7 +135,7 @@ TEST(ScsiMoTest, TestAddVendorPage) TEST(ScsiMoTest, ModeSelect) { - MockSCSIMO mo(0, { 1024, 2048 }); + MockSCSIMO mo(0); vector cmd(10); vector buf(255); From 7fa9abe5a3234293f71b98a96323c484237cd154 Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Tue, 14 Nov 2023 15:51:36 +0100 Subject: [PATCH 09/36] Remove fullspec/standard sub-folder (#1324) * Remove fullspec/standard sub-folder --- .github/workflows/cpp.yml | 2 +- cpp/Makefile | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml index 7c3eac8c..b4fac982 100644 --- a/.github/workflows/cpp.yml +++ b/.github/workflows/cpp.yml @@ -35,7 +35,7 @@ jobs: run: make -j $(nproc) test - name: Run unit tests - run: (set -o pipefail && bin/fullspec/piscsi_test | tee piscsi_test_log.txt) + run: (set -o pipefail && bin/piscsi_test | tee piscsi_test_log.txt) - name: Upload logs uses: actions/upload-artifact@v3 diff --git a/cpp/Makefile b/cpp/Makefile index c5d0b83d..104a1585 100644 --- a/cpp/Makefile +++ b/cpp/Makefile @@ -60,8 +60,8 @@ COVERAGE_DIR = coverage COVERAGE_FILE = piscsi.dat OS_FILES = ../os_integration -OBJDIR := obj/$(shell echo $(CONNECT_TYPE) | tr '[:upper:]' '[:lower:]') -BINDIR := bin/$(shell echo $(CONNECT_TYPE) | tr '[:upper:]' '[:lower:]') +OBJDIR := obj +BINDIR := bin BIN_ALL = \ $(BINDIR)/$(PISCSI) \ From baf89dcf863ed855d003b6aaba20e6dbb0132df3 Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Tue, 14 Nov 2023 16:03:25 +0100 Subject: [PATCH 10/36] Use standard C++ timer for long timeouts (#1327), remove unused code (#1328) * Replace timer in WaitSignal() ' Remove unused code * Remove unused file --- cpp/hal/bus.cpp | 35 ++++++++++++---------- cpp/hal/bus.h | 7 +---- cpp/hal/data_sample.h | 1 - cpp/hal/data_sample_raspberry.cpp | 14 --------- cpp/hal/data_sample_raspberry.h | 6 +--- cpp/hal/gpiobus.cpp | 45 +++++++++-------------------- cpp/hal/gpiobus.h | 2 -- cpp/hal/gpiobus_raspberry.cpp | 11 ------- cpp/hal/gpiobus_raspberry.h | 8 ----- cpp/hal/gpiobus_virtual.cpp | 5 ---- cpp/hal/gpiobus_virtual.h | 8 ----- cpp/hal/pin_control.h | 5 +--- cpp/hal/systimer.cpp | 6 ---- cpp/hal/systimer.h | 5 ---- cpp/hal/systimer_raspberry.cpp | 10 ------- cpp/hal/systimer_raspberry.h | 2 -- cpp/test/gpiobus_raspberry_test.cpp | 13 --------- cpp/test/mocks.h | 3 -- 18 files changed, 37 insertions(+), 149 deletions(-) delete mode 100644 cpp/hal/data_sample_raspberry.cpp diff --git a/cpp/hal/bus.cpp b/cpp/hal/bus.cpp index ff2edd1b..b135342a 100644 --- a/cpp/hal/bus.cpp +++ b/cpp/hal/bus.cpp @@ -60,23 +60,28 @@ const char* BUS::GetPhaseStrRaw(phase_t current_phase) { return it != phase_str_mapping.end() ? it->second : "INVALID"; } -//--------------------------------------------------------------------------- +// Phase Table +// Reference Table 8: https://www.staff.uni-mainz.de/tacke/scsi/SCSI2-06.html +// This determines the phase based upon the Msg, C/D and I/O signals. // -// Phase Table -// Reference Table 8: https://www.staff.uni-mainz.de/tacke/scsi/SCSI2-06.html -// This determines the phase based upon the Msg, C/D and I/O signals. -// -//--------------------------------------------------------------------------- +// |MSG|C/D|I/O| Phase +// | 0 | 0 | 0 | DATA OUT +// | 0 | 0 | 1 | DATA IN +// | 0 | 1 | 0 | COMMAND +// | 0 | 1 | 1 | STATUS +// | 1 | 0 | 0 | RESERVED +// | 1 | 0 | 1 | RESERVED +// | 1 | 1 | 0 | MESSAGE OUT +// | 1 | 1 | 1 | MESSAGE IN const array BUS::phase_table = { - // | MSG|C/D|I/O | - phase_t::dataout, // | 0 | 0 | 0 | - phase_t::datain, // | 0 | 0 | 1 | - phase_t::command, // | 0 | 1 | 0 | - phase_t::status, // | 0 | 1 | 1 | - phase_t::reserved, // | 1 | 0 | 0 | - phase_t::reserved, // | 1 | 0 | 1 | - phase_t::msgout, // | 1 | 1 | 0 | - phase_t::msgin // | 1 | 1 | 1 | + phase_t::dataout, + phase_t::datain, + phase_t::command, + phase_t::status, + phase_t::reserved, + phase_t::reserved, + phase_t::msgout, + phase_t::msgin }; //--------------------------------------------------------------------------- diff --git a/cpp/hal/bus.h b/cpp/hal/bus.h index 23740fe2..17a1a805 100644 --- a/cpp/hal/bus.h +++ b/cpp/hal/bus.h @@ -68,8 +68,7 @@ class BUS : public PinControl // Operation modes definition enum class mode_e { TARGET = 0, - INITIATOR = 1, - MONITOR = 2, + INITIATOR = 1 }; static int GetCommandByteCount(uint8_t); @@ -86,7 +85,6 @@ class BUS : public PinControl // Get the string phase name, based upon the raw data static const char *GetPhaseStrRaw(phase_t current_phase); - virtual int GetMode(int pin) = 0; virtual uint32_t Acquire() = 0; virtual unique_ptr GetSample(uint64_t timestamp = 0) = 0; @@ -97,9 +95,6 @@ class BUS : public PinControl // SEL signal event polling virtual bool PollSelectEvent() = 0; - // Clear SEL signal event - virtual void ClearSelectEvent() = 0; - virtual bool GetSignal(int pin) const = 0; // Get SCSI input signal value virtual void SetSignal(int pin, bool ast) = 0; diff --git a/cpp/hal/data_sample.h b/cpp/hal/data_sample.h index c5a57e30..d7e794b0 100644 --- a/cpp/hal/data_sample.h +++ b/cpp/hal/data_sample.h @@ -31,7 +31,6 @@ class DataSample virtual bool GetREQ() const = 0; virtual bool GetACT() const = 0; virtual uint8_t GetDAT() const = 0; - virtual bool GetDP() const = 0; virtual uint32_t GetRawCapture() const = 0; diff --git a/cpp/hal/data_sample_raspberry.cpp b/cpp/hal/data_sample_raspberry.cpp deleted file mode 100644 index 9634a912..00000000 --- a/cpp/hal/data_sample_raspberry.cpp +++ /dev/null @@ -1,14 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Copyright (C) 2022 akuker -// -// [ SCSI Bus Monitor ] -// -//--------------------------------------------------------------------------- - -#include "shared/scsi.h" -#include "data_sample.h" - diff --git a/cpp/hal/data_sample_raspberry.h b/cpp/hal/data_sample_raspberry.h index a0b5e1e7..ee7cbc0e 100644 --- a/cpp/hal/data_sample_raspberry.h +++ b/cpp/hal/data_sample_raspberry.h @@ -74,10 +74,6 @@ class DataSample_Raspberry final : public DataSample { return GetSignal(PIN_ACT); } - bool GetDP() const override - { - return GetSignal(PIN_DP); - } uint8_t GetDAT() const override { uint8_t ret_val = 0; @@ -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 837fc6d7..397ed9cf 100644 --- a/cpp/hal/gpiobus.cpp +++ b/cpp/hal/gpiobus.cpp @@ -1,10 +1,11 @@ //--------------------------------------------------------------------------- // -// SCSI Target Emulator PiSCSI -// for Raspberry Pi +// SCSI Target Emulator PiSCSI +// for Raspberry Pi // -// Powered by XM6 TypeG Technology. -// Copyright (C) 2016-2020 GIMONS +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2020 GIMONS +// Copyright (C) 2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -18,6 +19,7 @@ #ifdef __linux__ #include #endif +#include using namespace std; @@ -403,42 +405,23 @@ bool GPIOBUS::PollSelectEvent() #endif } -//--------------------------------------------------------------------------- -// -// Cancel SEL signal event -// -//--------------------------------------------------------------------------- -void GPIOBUS::ClearSelectEvent() -{ - GPIO_FUNCTION_TRACE -} - -//--------------------------------------------------------------------------- -// -// Wait for signal change -// -//--------------------------------------------------------------------------- bool GPIOBUS::WaitSignal(int pin, bool ast) { - // Get current time - const uint32_t now = SysTimer::GetTimerLow(); - - // Calculate timeout (3000ms) - const uint32_t timeout = 3000 * 1000; + const auto now = chrono::steady_clock::now(); + // Wait up to 3 s do { - // Immediately upon receiving a reset Acquire(); - if (GetRST()) { - return false; - } - // Check for the signal edge if (GetSignal(pin) == ast) { return true; } - } while ((SysTimer::GetTimerLow() - now) < timeout); - // We timed out waiting for the signal + // Abort on a reset + if (GetRST()) { + return false; + } + } while ((chrono::duration_cast(chrono::steady_clock::now() - now).count()) < 3); + return false; } diff --git a/cpp/hal/gpiobus.h b/cpp/hal/gpiobus.h index fe774115..0e9e8129 100644 --- a/cpp/hal/gpiobus.h +++ b/cpp/hal/gpiobus.h @@ -182,8 +182,6 @@ class GPIOBUS : public BUS // SEL signal event polling bool PollSelectEvent() override; - // Clear SEL signal event - void ClearSelectEvent() override; protected: virtual void MakeTable() = 0; diff --git a/cpp/hal/gpiobus_raspberry.cpp b/cpp/hal/gpiobus_raspberry.cpp index 44e09dfe..030413fd 100644 --- a/cpp/hal/gpiobus_raspberry.cpp +++ b/cpp/hal/gpiobus_raspberry.cpp @@ -644,17 +644,6 @@ void GPIOBUS_Raspberry::SetDAT(uint8_t dat) #endif // SIGNAL_CONTROL_MODE } -bool GPIOBUS_Raspberry::GetDP() const -{ - return GetSignal(PIN_DP); -} - -//--------------------------------------------------------------------------- -// -// Create work table -// -//--------------------------------------------------------------------------- - //--------------------------------------------------------------------------- // // Signal table diff --git a/cpp/hal/gpiobus_raspberry.h b/cpp/hal/gpiobus_raspberry.h index a79df0e5..9918fe21 100644 --- a/cpp/hal/gpiobus_raspberry.h +++ b/cpp/hal/gpiobus_raspberry.h @@ -137,8 +137,6 @@ class GPIOBUS_Raspberry : public GPIOBUS // Set REQ signal void SetREQ(bool ast) override; - bool GetDP() const override; - // Get DAT signal uint8_t GetDAT() override; // Set DAT signal @@ -174,12 +172,6 @@ class GPIOBUS_Raspberry : public GPIOBUS // Set Control Signal void SetMode(int pin, int mode) override; // Set SCSI I/O mode - int GetMode(int pin) override - { - // Not implemented (or needed for thist gpio bus type) - (void)pin; - return -1; - } bool GetSignal(int pin) const override; // Get SCSI input signal value void SetSignal(int pin, bool ast) override; diff --git a/cpp/hal/gpiobus_virtual.cpp b/cpp/hal/gpiobus_virtual.cpp index 136dae24..5648a871 100644 --- a/cpp/hal/gpiobus_virtual.cpp +++ b/cpp/hal/gpiobus_virtual.cpp @@ -372,11 +372,6 @@ void GPIOBUS_Virtual::SetREQ(bool ast) SetSignal(PIN_REQ, ast); } -bool GPIOBUS_Virtual::GetDP() const -{ - return GetSignal(PIN_DP); -} - //--------------------------------------------------------------------------- // // Get data signals diff --git a/cpp/hal/gpiobus_virtual.h b/cpp/hal/gpiobus_virtual.h index 85e00c79..991e3f85 100644 --- a/cpp/hal/gpiobus_virtual.h +++ b/cpp/hal/gpiobus_virtual.h @@ -97,8 +97,6 @@ class GPIOBUS_Virtual final : public GPIOBUS void SetREQ(bool ast) override; // Set REQ signal - bool GetDP() const override; - bool WaitREQ(bool ast) override { return WaitSignal(PIN_REQ, ast); @@ -120,12 +118,6 @@ class GPIOBUS_Virtual final : public GPIOBUS // Set Control Signal void SetMode(int pin, int mode) override; // Set SCSI I/O mode - int GetMode(int pin) override - { - // Not implemented (or needed for thist gpio bus type) - (void)pin; - return -1; - } bool GetSignal(int pin) const override; // Get SCSI input signal value void SetSignal(int pin, bool ast) override; diff --git a/cpp/hal/pin_control.h b/cpp/hal/pin_control.h index 902a5de7..d5690d89 100644 --- a/cpp/hal/pin_control.h +++ b/cpp/hal/pin_control.h @@ -52,9 +52,6 @@ class PinControl // Set ENB signal virtual void SetENB(bool ast) = 0; - // Get parity signal - virtual bool GetDP() const = 0; - // GPIO pin direction setting virtual void PinConfig(int pin, int mode) = 0; // GPIO pin pull up/down resistor setting @@ -65,4 +62,4 @@ class PinControl PinControl() = default; virtual ~PinControl() = default; -}; \ No newline at end of file +}; diff --git a/cpp/hal/systimer.cpp b/cpp/hal/systimer.cpp index c2b4e7b8..9073ed82 100644 --- a/cpp/hal/systimer.cpp +++ b/cpp/hal/systimer.cpp @@ -43,12 +43,6 @@ 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) { diff --git a/cpp/hal/systimer.h b/cpp/hal/systimer.h index 0479ec5c..b4c4822d 100644 --- a/cpp/hal/systimer.h +++ b/cpp/hal/systimer.h @@ -29,8 +29,6 @@ class PlatformSpecificTimer virtual void Init() = 0; // Get system timer low byte virtual uint32_t GetTimerLow() = 0; - // Get system timer high byte - virtual uint32_t GetTimerHigh() = 0; // Sleep for N nanoseconds virtual void SleepNsec(uint32_t nsec) = 0; // Sleep for N microseconds @@ -48,8 +46,6 @@ class SysTimer static void Init(); // Get system timer low byte static uint32_t GetTimerLow(); - // Get system timer high byte - static uint32_t GetTimerHigh(); // Sleep for N nanoseconds static void SleepNsec(uint32_t nsec); // Sleep for N microseconds @@ -57,7 +53,6 @@ class SysTimer private: static bool initialized; - static bool is_allwinnner; static bool is_raspberry; static std::unique_ptr systimer_ptr; diff --git a/cpp/hal/systimer_raspberry.cpp b/cpp/hal/systimer_raspberry.cpp index 8814ea02..76da6abf 100644 --- a/cpp/hal/systimer_raspberry.cpp +++ b/cpp/hal/systimer_raspberry.cpp @@ -92,16 +92,6 @@ uint32_t SysTimer_Raspberry::GetTimerLow() return systaddr[SYST_CLO]; } -//--------------------------------------------------------------------------- -// -// Get system timer high byte -// -//--------------------------------------------------------------------------- -uint32_t SysTimer_Raspberry::GetTimerHigh() -{ - return systaddr[SYST_CHI]; -} - //--------------------------------------------------------------------------- // // Sleep in nanoseconds diff --git a/cpp/hal/systimer_raspberry.h b/cpp/hal/systimer_raspberry.h index 809d8229..e5ba2197 100644 --- a/cpp/hal/systimer_raspberry.h +++ b/cpp/hal/systimer_raspberry.h @@ -32,8 +32,6 @@ class SysTimer_Raspberry : public PlatformSpecificTimer 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 diff --git a/cpp/test/gpiobus_raspberry_test.cpp b/cpp/test/gpiobus_raspberry_test.cpp index 5a94d670..03184557 100644 --- a/cpp/test/gpiobus_raspberry_test.cpp +++ b/cpp/test/gpiobus_raspberry_test.cpp @@ -219,16 +219,3 @@ TEST(GpiobusRaspberry, GetREQ) bus.Acquire(); EXPECT_EQ(false, bus.GetREQ()); } - -TEST(GpiobusRaspberry, GetDP) -{ - SetableGpiobusRaspberry bus; - - bus.TestSetGpios(0x00); - bus.TestSetGpioPin(PIN_DP, true); - bus.Acquire(); - EXPECT_EQ(true, bus.GetDP()); - bus.TestSetGpioPin(PIN_DP, false); - bus.Acquire(); - EXPECT_EQ(false, bus.GetDP()); -} diff --git a/cpp/test/mocks.h b/cpp/test/mocks.h index cc761802..3fefdb40 100644 --- a/cpp/test/mocks.h +++ b/cpp/test/mocks.h @@ -53,7 +53,6 @@ public: MOCK_METHOD(void, SetENB, (bool), (override)); MOCK_METHOD(uint8_t, GetDAT, (), (override)); MOCK_METHOD(void, SetDAT, (uint8_t), (override)); - MOCK_METHOD(bool, GetDP, (), (const override)); MOCK_METHOD(uint32_t, Acquire, (), (override)); MOCK_METHOD(int, CommandHandShake, (vector&), (override)); MOCK_METHOD(int, ReceiveHandShake, (uint8_t *, int), (override)); @@ -61,13 +60,11 @@ public: MOCK_METHOD(bool, GetSignal, (int), (const override)); MOCK_METHOD(void, SetSignal, (int, bool), (override)); MOCK_METHOD(bool, PollSelectEvent, (), (override)); - MOCK_METHOD(void, ClearSelectEvent, (), (override)); MOCK_METHOD(unique_ptr, GetSample, (uint64_t), (override)); MOCK_METHOD(void, PinConfig, (int, int), (override)); MOCK_METHOD(void, PullConfig, (int , int ), (override)); MOCK_METHOD(void, SetControl, (int , bool ), (override)); MOCK_METHOD(void, SetMode, (int , int ), (override)); - MOCK_METHOD(int, GetMode, (int ), (override)); MockBus() = default; ~MockBus() override = default; From 98f9292690da50e866cf6ec901334a19c2716c58 Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Tue, 14 Nov 2023 16:13:14 +0100 Subject: [PATCH 11/36] NetBSD compatibility: Increase daynaport minimum packet size to 128 bytes (#1334) (#1335) * NetBSD compatibility: Increase daynaport minimum packet size to 128 bytes --- cpp/devices/scsi_daynaport.cpp | 17 ++++++++++------- cpp/devices/scsi_daynaport.h | 1 + 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/cpp/devices/scsi_daynaport.cpp b/cpp/devices/scsi_daynaport.cpp index 0629ccae..7c1c390f 100644 --- a/cpp/devices/scsi_daynaport.cpp +++ b/cpp/devices/scsi_daynaport.cpp @@ -6,6 +6,7 @@ // Copyright (C) 2020 akuker // Copyright (C) 2014-2020 GIMONS // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2023 Uwe Seimet // // Licensed under the BSD 3-Clause License. // See LICENSE file in the project root folder. @@ -212,13 +213,15 @@ int SCSIDaynaPort::Read(cdb_t cdb, vector& buf, uint64_t) // response->flags = e_no_more_data; // } int size = rx_packet_size; - if (size < 64) { - // A frame must have at least 64 bytes (see https://github.com/PiSCSI/piscsi/issues/619) - // Note that this work-around breaks the checksum. As currently there are no known drivers - // that care for the checksum, and the Daynaport driver for the Atari expects frames of - // 64 bytes it was decided to accept the broken checksum. If a driver should pop up that - // breaks because of this, the work-around has to be re-evaluated. - size = 64; + if (size < 128) { + // A frame must have at least 64 bytes for the Atari driver, see https://github.com/PiSCSI/piscsi/issues/619, + // but also works with 128 bytes. + // The NetBSD driver requires at least 128 bytes, see https://github.com/PiSCSI/piscsi/issues/1098. + // The Mac driver is also fine with 128 bytes. + // Note that this work-around breaks the checksum. As currently there are no known drivers + // that care for the checksum it was decided to accept the broken checksum. + // If a driver should pop up that breaks because of this, the work-around has to be re-evaluated. + size = 128; } SetInt16(buf, 0, size); SetInt32(buf, 2, tap.HasPendingPackets() ? 0x10 : 0x00); diff --git a/cpp/devices/scsi_daynaport.h b/cpp/devices/scsi_daynaport.h index 94f45551..6074e081 100644 --- a/cpp/devices/scsi_daynaport.h +++ b/cpp/devices/scsi_daynaport.h @@ -6,6 +6,7 @@ // Copyright (C) 2020 akuker // Copyright (C) 2014-2020 GIMONS // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2023 Uwe Seimet // // Licensed under the BSD 3-Clause License. // See LICENSE file in the project root folder. From 8493cf4ba8aeb7be840f4b1f7d448bbe584a23ff Mon Sep 17 00:00:00 2001 From: PiSCSI User Date: Wed, 15 Nov 2023 01:44:42 +0000 Subject: [PATCH 12/36] tweak to Webmin links --- .../themes/modern/icons/external-link.svg | 1 + python/web/src/static/themes/modern/style.css | 4 +++ python/web/src/templates/admin.html | 26 +++++++++++-------- 3 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 python/web/src/static/themes/modern/icons/external-link.svg diff --git a/python/web/src/static/themes/modern/icons/external-link.svg b/python/web/src/static/themes/modern/icons/external-link.svg new file mode 100644 index 00000000..6236df3e --- /dev/null +++ b/python/web/src/static/themes/modern/icons/external-link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/python/web/src/static/themes/modern/style.css b/python/web/src/static/themes/modern/style.css index e057da98..ad993af2 100644 --- a/python/web/src/static/themes/modern/style.css +++ b/python/web/src/static/themes/modern/style.css @@ -971,6 +971,10 @@ section#services li.disabled { background: url("icons/cloud-off.svg") no-repeat left center; } +section#services li.extlink { + background: url("icons/external-link.svg") no-repeat left center; +} + /* ------------------------------------------------------------------------------ Drives page diff --git a/python/web/src/templates/admin.html b/python/web/src/templates/admin.html index c28d41a4..2ff060f4 100644 --- a/python/web/src/templates/admin.html +++ b/python/web/src/templates/admin.html @@ -120,18 +120,20 @@
    {% if netatalk_configured %}
  • - {{ _("Mac AFP file sharing is enabled.") }} + {{ _("Mac AFP file sharing is enabled.") }} {% else %}
  • {{ _("Mac AFP file sharing is disabled.") }} {% endif %}
  • {% if webmin_configured %} -
  • - - {{ _("Manage the AFP server") }} - -
  • + {% endif %} {% if samba_configured %}
  • @@ -142,11 +144,13 @@ {% endif %}
  • {% if webmin_configured %} -
  • - - {{ _("Manage the SMB server") }} - -
  • + {% endif %} {% if ftp_configured %}
  • From e5678f623fe18aeaecf26a27ed9417935d22bff8 Mon Sep 17 00:00:00 2001 From: PiSCSI User Date: Wed, 15 Nov 2023 01:58:35 +0000 Subject: [PATCH 13/36] work with sub-path changes in #1354 --- python/web/src/templates/admin.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/web/src/templates/admin.html b/python/web/src/templates/admin.html index 2ff060f4..dd7e54fb 100644 --- a/python/web/src/templates/admin.html +++ b/python/web/src/templates/admin.html @@ -129,7 +129,7 @@ {% if webmin_configured %}
      @@ -146,7 +146,7 @@ {% if webmin_configured %}
        From 28abbb503428ce6f640719603871b57334ab8915 Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Wed, 15 Nov 2023 07:36:07 +0100 Subject: [PATCH 14/36] Temporary fix for compiler issue (#1359) * Fix bookworm compiler issue * fix to Docker for workaround --------- Co-authored-by: Benjamin Krein --- cpp/piscsi/piscsi_response.h | 2 +- docker/backend/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/piscsi/piscsi_response.h b/cpp/piscsi/piscsi_response.h index 0015655c..a4ab0331 100644 --- a/cpp/piscsi/piscsi_response.h +++ b/cpp/piscsi/piscsi_response.h @@ -25,7 +25,7 @@ class PiscsiResponse { public: - PiscsiResponse() = default; + PiscsiResponse() { } ~PiscsiResponse() = default; bool GetImageFile(PbImageFile&, const string&, const string&) const; diff --git a/docker/backend/Dockerfile b/docker/backend/Dockerfile index 0326aa2b..e86a1c5d 100644 --- a/docker/backend/Dockerfile +++ b/docker/backend/Dockerfile @@ -18,7 +18,7 @@ FROM debian:bullseye-slim AS runner USER root WORKDIR /home/pi -COPY --from=build /home/pi/piscsi/cpp/bin/fullspec/* /usr/local/bin/ +COPY --from=build /home/pi/piscsi/cpp/bin/* /usr/local/bin/ COPY docker/backend/piscsi_wrapper.sh /usr/local/bin/piscsi_wrapper.sh RUN chmod +x /usr/local/bin/* RUN mkdir -p /home/pi/images From ec9f83f9df60e768bfc0ffb026d9c969d252f1fa Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Wed, 15 Nov 2023 07:44:46 +0100 Subject: [PATCH 15/36] Improve performance when reading sectors (#1344) --- cpp/hal/gpiobus_raspberry.cpp | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/cpp/hal/gpiobus_raspberry.cpp b/cpp/hal/gpiobus_raspberry.cpp index 030413fd..03bb9640 100644 --- a/cpp/hal/gpiobus_raspberry.cpp +++ b/cpp/hal/gpiobus_raspberry.cpp @@ -606,42 +606,31 @@ uint8_t GPIOBUS_Raspberry::GetDAT() return (uint8_t)data; } -//--------------------------------------------------------------------------- -// -// Set data signals -// -//--------------------------------------------------------------------------- void GPIOBUS_Raspberry::SetDAT(uint8_t dat) { - // Write to port + // Write to ports #if SIGNAL_CONTROL_MODE == 0 uint32_t fsel = gpfsel[0]; fsel &= tblDatMsk[0][dat]; fsel |= tblDatSet[0][dat]; - if (fsel != gpfsel[0]) { - gpfsel[0] = fsel; - gpio[GPIO_FSEL_0] = fsel; - } + gpfsel[0] = fsel; + gpio[GPIO_FSEL_0] = fsel; fsel = gpfsel[1]; fsel &= tblDatMsk[1][dat]; fsel |= tblDatSet[1][dat]; - if (fsel != gpfsel[1]) { - gpfsel[1] = fsel; - gpio[GPIO_FSEL_1] = fsel; - } + gpfsel[1] = fsel; + gpio[GPIO_FSEL_1] = fsel; fsel = gpfsel[2]; fsel &= tblDatMsk[2][dat]; fsel |= tblDatSet[2][dat]; - if (fsel != gpfsel[2]) { - gpfsel[2] = fsel; - gpio[GPIO_FSEL_2] = fsel; - } + gpfsel[2] = fsel; + gpio[GPIO_FSEL_2] = fsel; #else gpio[GPIO_CLR_0] = tblDatMsk[dat]; gpio[GPIO_SET_0] = tblDatSet[dat]; -#endif // SIGNAL_CONTROL_MODE +#endif } //--------------------------------------------------------------------------- From 70bcb78d2467ef7a22388908ecca3ce52d329508 Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Wed, 15 Nov 2023 07:51:21 +0100 Subject: [PATCH 16/36] Use a standard timer for the DaynaPort delay work-around (#1357) * Do not use the proprietary system timer for the DaynaPort delay work-around --- cpp/hal/bus.h | 2 +- cpp/hal/gpiobus.cpp | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/cpp/hal/bus.h b/cpp/hal/bus.h index 17a1a805..506cebf3 100644 --- a/cpp/hal/bus.h +++ b/cpp/hal/bus.h @@ -54,7 +54,7 @@ const static int SCSI_DELAY_FAST_NEGATION_PERIOD_NS = 30; // The DaynaPort SCSI Link do a short delay in the middle of transfering // a packet. This is the number of uS that will be delayed between the // header and the actual data. -const static int SCSI_DELAY_SEND_DATA_DAYNAPORT_US = 100; +const static int SCSI_DELAY_SEND_DATA_DAYNAPORT_NS = 100'000; class bus_exception : public runtime_error diff --git a/cpp/hal/gpiobus.cpp b/cpp/hal/gpiobus.cpp index 397ed9cf..f0dc9cf5 100644 --- a/cpp/hal/gpiobus.cpp +++ b/cpp/hal/gpiobus.cpp @@ -15,7 +15,7 @@ #include #include #include -#include +#include #ifdef __linux__ #include #endif @@ -278,9 +278,12 @@ 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) { - spdlog::trace("DELAYING for " + to_string(SCSI_DELAY_SEND_DATA_DAYNAPORT_US) + " us after " + + spdlog::trace("DELAYING for " + to_string(SCSI_DELAY_SEND_DATA_DAYNAPORT_NS) + " ns after " + to_string(delay_after_bytes) + " bytes"); - SysTimer::SleepUsec(SCSI_DELAY_SEND_DATA_DAYNAPORT_US); + EnableIRQ(); + const timespec ts = { .tv_sec = 0, .tv_nsec = SCSI_DELAY_SEND_DATA_DAYNAPORT_NS}; + nanosleep(&ts, nullptr); + DisableIRQ(); } // Set the DATA signals From fbce3809071e62c1d8abd384ab31b1607bf566e2 Mon Sep 17 00:00:00 2001 From: PiSCSI User Date: Wed, 15 Nov 2023 10:54:40 +0000 Subject: [PATCH 17/36] consolidate Webmin links --- python/web/src/templates/admin.html | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/python/web/src/templates/admin.html b/python/web/src/templates/admin.html index dd7e54fb..cf6bcc58 100644 --- a/python/web/src/templates/admin.html +++ b/python/web/src/templates/admin.html @@ -126,15 +126,6 @@ {{ _("Mac AFP file sharing is disabled.") }} {% endif %} - {% if webmin_configured %} - - {% endif %} {% if samba_configured %}
      • {{ _("Windows SMB file sharing is enabled.") }} @@ -143,15 +134,6 @@ {{ _("Windows SMB file sharing is disabled.") }} {% endif %}
      • - {% if webmin_configured %} - - {% endif %} {% if ftp_configured %}
      • {{ _("FTP file sharing is enabled.") }} @@ -168,6 +150,11 @@ {{ _("Vintage web proxy is disabled.") }} {% endif %}
      • + {% if webmin_configured %} + + {% endif %}
      From ee934c880cc7c9094a8e2c4f924a8ff6968d81f2 Mon Sep 17 00:00:00 2001 From: PiSCSI User Date: Wed, 15 Nov 2023 13:31:03 +0000 Subject: [PATCH 18/36] Re-added Webmin links for AFP & SMB --- python/web/src/templates/admin.html | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/python/web/src/templates/admin.html b/python/web/src/templates/admin.html index cf6bcc58..40484364 100644 --- a/python/web/src/templates/admin.html +++ b/python/web/src/templates/admin.html @@ -126,6 +126,13 @@ {{ _("Mac AFP file sharing is disabled.") }} {% endif %} + {% if webmin_configured %} + + {% endif %} {% if samba_configured %}
    • {{ _("Windows SMB file sharing is enabled.") }} @@ -134,6 +141,13 @@ {{ _("Windows SMB file sharing is disabled.") }} {% endif %}
    • + {% if webmin_configured %} + + {% endif %} {% if ftp_configured %}
    • {{ _("FTP file sharing is enabled.") }} From bb602040e2fd846da0ccde9a7df48fd48e1a8bd7 Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Thu, 16 Nov 2023 12:37:50 +0100 Subject: [PATCH 19/36] Improve BSD compile-time compatibility (#1342) --- cpp/hal/sbc_version.cpp | 2 +- cpp/piscsi/piscsi_service.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cpp/hal/sbc_version.cpp b/cpp/hal/sbc_version.cpp index caebbedf..cd334ad3 100644 --- a/cpp/hal/sbc_version.cpp +++ b/cpp/hal/sbc_version.cpp @@ -144,7 +144,7 @@ uint32_t SBC_Version::GetPeripheralAddress(void) return address; } -#elif defined __NetBSD__ +#elif defined __NetBSD__ && (!defined(__x86_64__) || defined(__X86__)) uint32_t SBC_Version::GetPeripheralAddress(void) { char buf[1024]; diff --git a/cpp/piscsi/piscsi_service.cpp b/cpp/piscsi/piscsi_service.cpp index 4f842def..90f60774 100644 --- a/cpp/piscsi/piscsi_service.cpp +++ b/cpp/piscsi/piscsi_service.cpp @@ -42,7 +42,8 @@ string PiscsiService::Init(const callback& cb, int port) server.sin_family = PF_INET; server.sin_port = htons((uint16_t)port); 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 + if (bind(service_socket, reinterpret_cast(&server), //NOSONAR bit_cast is not supported by the bullseye compiler + static_cast(sizeof(sockaddr_in))) < 0) { Stop(); return "Port " + to_string(port) + " is in use, is piscsi already running?"; } From a7c71e4fbbfcaf3b370187ae23fef8c33f2058f1 Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Thu, 16 Nov 2023 12:40:10 +0100 Subject: [PATCH 20/36] Replace system timer by C++ standard time for timeout of 3 s (#1361) --- cpp/piscsi/piscsi_core.cpp | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/cpp/piscsi/piscsi_core.cpp b/cpp/piscsi/piscsi_core.cpp index bd491b8c..2b2f6385 100644 --- a/cpp/piscsi/piscsi_core.cpp +++ b/cpp/piscsi/piscsi_core.cpp @@ -20,7 +20,6 @@ #include "devices/storage_device.h" #include "hal/gpiobus_factory.h" #include "hal/gpiobus.h" -#include "hal/systimer.h" #include "piscsi/piscsi_core.h" #include #include @@ -29,6 +28,7 @@ #include #include #include +#include using namespace std; using namespace filesystem; @@ -684,21 +684,19 @@ bool Piscsi::ShutDown(AbstractController::piscsi_shutdown_mode shutdown_mode) 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(); + // initiator to assert it while setting the ID (for up to 3 seconds) + if (bus->GetBSY()) { + const auto now = chrono::steady_clock::now(); + while ((chrono::duration_cast(chrono::steady_clock::now() - now).count()) < 3) { + bus->Acquire(); - // Wait for 3s - while ((SysTimer::GetTimerLow() - now) < 3'000'000) { - bus->Acquire(); + if (!bus->GetBSY()) { + return true; + } + } - if (!bus->GetBSY()) { - return true; - } - } + return false; + } - return false; - } - - return true; + return true; } From e5b99d4fa954f347d1881438a0a1a5c4200d8170 Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Fri, 17 Nov 2023 13:00:03 +0100 Subject: [PATCH 21/36] Fix typo (choice 0) in easyinstall.sh (#1372) * Fix typo (choice 0) in easyinstall.sh Co-authored-by: Frank Danapfel --- easyinstall.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/easyinstall.sh b/easyinstall.sh index 8c9394ef..183c9c82 100755 --- a/easyinstall.sh +++ b/easyinstall.sh @@ -1507,8 +1507,8 @@ function runChoice() { function readChoice() { choice=-1 - until [ $choice -ge "0" ] && ([ $choice -eq "99" ] || [ $choice -le "17" ]) ; do - echo -n "Enter your choice (0-17) or CTRL-C to exit: " + until [ $choice -ge "1" ] && ([ $choice -eq "99" ] || [ $choice -le "17" ]) ; do + echo -n "Enter your choice (1-17) or CTRL-C to exit: " read -r choice done From fe1d2dd4219d5a11617c4202237ed0d0ffa43d7e Mon Sep 17 00:00:00 2001 From: PiSCSI User Date: Sat, 18 Nov 2023 16:12:46 +0000 Subject: [PATCH 22/36] revert moving webmin to sub-path --- easyinstall.sh | 4 ---- python/web/service-infra/nginx-default.conf | 6 ------ python/web/src/templates/admin.html | 6 +++--- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/easyinstall.sh b/easyinstall.sh index 183c9c82..ef852625 100755 --- a/easyinstall.sh +++ b/easyinstall.sh @@ -986,10 +986,6 @@ function installWebmin() { sudo sed -i 's@/usr/sbin@/usr/local/sbin@' "$WEBMIN_MODULE_CONFIG" fi rm netatalk2-wbm.tgz || true - - # Configure Webmin to be accessible from a '/webmin' URL path - echo "webprefix=/webmin" | sudo tee -a /etc/webmin/config - echo "webprefixnoredir=1" | sudo tee -a /etc/webmin/config } # updates configuration files and installs packages needed for the OLED screen script diff --git a/python/web/service-infra/nginx-default.conf b/python/web/service-infra/nginx-default.conf index ec76431d..575bca56 100644 --- a/python/web/service-infra/nginx-default.conf +++ b/python/web/service-infra/nginx-default.conf @@ -18,12 +18,6 @@ server { proxy_pass http://127.0.0.1:8080; } - # Configure Webmin to be accessed via '/webmin' URL path - # NOTE: Use of 'https' here is required as is the trailing '/' - location /webmin { - proxy_pass https://127.0.0.1:10000/; - } - # Large files client_max_body_size 0; proxy_read_timeout 1000; diff --git a/python/web/src/templates/admin.html b/python/web/src/templates/admin.html index 40484364..61fd7a63 100644 --- a/python/web/src/templates/admin.html +++ b/python/web/src/templates/admin.html @@ -129,7 +129,7 @@ {% if webmin_configured %} {% endif %} @@ -144,7 +144,7 @@ {% if webmin_configured %} {% endif %} @@ -166,7 +166,7 @@
    • {% if webmin_configured %} {% endif %}
    From 287b9d7623d554f3bb2a99c3d6edd7c89bd4da69 Mon Sep 17 00:00:00 2001 From: i-to-z Date: Sun, 19 Nov 2023 15:39:02 -0800 Subject: [PATCH 23/36] Added ability to set the TYPE/CREATOR resource fork attributes of file(s) inside newly-created cd-rom ISO images of type HFS (#1377) * Added ability to set the TYPE/CREATOR resource fork attributes of file(s) inside newly-created cd-rom ISO images of type HFS * Added file genisoimage_hfs_resource_fork_map.txt under python/web and modified web.py to find it at that location --- .../web/genisoimage_hfs_resource_fork_map.txt | 322 ++++++++++++++++++ python/web/src/web.py | 22 +- 2 files changed, 343 insertions(+), 1 deletion(-) create mode 100644 python/web/genisoimage_hfs_resource_fork_map.txt diff --git a/python/web/genisoimage_hfs_resource_fork_map.txt b/python/web/genisoimage_hfs_resource_fork_map.txt new file mode 100644 index 00000000..62a27454 --- /dev/null +++ b/python/web/genisoimage_hfs_resource_fork_map.txt @@ -0,0 +1,322 @@ +# This file is used by the genisoimage command-line tool when generating an iso of type HFS +# to derive the CREATOR and TYPE resource fork attributes of each file in the generated image. +# +# genisoimage will look up the file's extension in the 1st column below, and then use the +# 3rd and 4th columns to derive the CREATOR and TYPE. +# +# This file ends with a "catch-all" file extension of ".*" which will be used if the file extension +# was not found in this map. +# +# Note: genisoimage does not support custom CREATOR/TYPE per EACH file in the image, +# so the best alternative to use the "per file extension" approach here. +# +# The five columns below are: file extension, file translation, CREATOR, TYPE and Description. +# Lines starting with the '#' character are comment lines and are ignored. +# +# See more info at the genisoimage Linux man page: https://linux.die.net/man/1/genisoimage +# +# The mapping entries below are derived from +# https://github.com/Netatalk/netatalk/blob/branch-netatalk-2-3/config/AppleVolumes.system +# Few entries, e.g the one for ".img" were modified; few more (e.g. ".dc42") were added. +# +# EXTN XLate CREATOR TYPE Application - Description (mime type) +.1st Ascii 'ttxt' 'TEXT' "SimpleText - Text Readme (application/text)" +.669 Raw 'SNPL' '6669' "PlayerPro - 669 MOD Music" +.8med Raw 'SCPL' 'STrk' "SoundApp - Amiga OctaMed music" +.8svx Raw 'SCPL' '8SVX' "SoundApp - Amiga 8-bit sound" +.aif Raw 'SCPL' 'AIFF' "SoundApp - AIFF Sound (audio/x-aiff)" +.aifc Raw 'SCPL' 'AIFC' "SoundApp - AIFF Sound Compressed (audio/x-aiff)" +.aiff Raw 'SCPL' 'AIFF' "SoundApp - AIFF Sound (audio/x-aiff)" +.al Raw 'SCPL' 'ALAW' "SoundApp - ALAW Sound" +.ani Raw 'GKON' 'ANIi' "GraphicConverter - Animated NeoChrome" +.apd Ascii 'ALD3' 'TEXT' "Aldus PageMaker - Aldus Printer Description" +.arc Raw 'SITx' 'mArc' "ArcMac - PC ARChive" +.arj Raw 'DArj' 'BINA' "DeArj - ARJ Archive" +.arr Raw 'GKON' 'ARR ' "GraphicConverter - Amber ARR image" +.art Raw 'GKON' 'ART ' "GraphicConverter - First Publisher" +.ascii Ascii 'ttxt' 'TEXT' "SimpleText - ASCII Text (text/plain)" +.asc Ascii 'ttxt' 'TEXT' "SimpleText - ASCII Text (text/plain)" +.asf Ascii 'Ms01' 'ASF_' "Netshow Player (video/x-ms-asf)" +.asm Ascii 'ttxt' 'TEXT' "SimpleText - Assembly Source" +.asx Ascii 'Ms01' 'ASX_' "Netshow Player (video/x-ms-asf)" +.a Ascii 'ttxt' 'TEXT' "SimpleText - Assembly Source" +.au Raw 'TVOD' 'ULAW' "QuickTime Player - Sun Sound (audio/basic)" +.avi Raw 'TVOD' 'VfW ' "QuickTime Player - AVI Movie (video/avi)" +.bar Raw 'S691' 'BARF' "SunTar - Unix BAR Archive" +.bas Ascii 'ttxt' 'TEXT' "SimpleText - BASIC Source" +.bat Ascii 'ttxt' 'TEXT' "SimpleText - MS-DOS Batch File" +.bga Raw 'ogle' 'BMPp' "PictureViewer - OS/2 Bitmap" +.bib Ascii 'ttxt' 'TEXT' "SimpleText - BibTex Bibliography" +.binary Raw 'hDmp' 'BINA' "HexEdit - Untyped Binary Data (application/octet-stream)" +.bin Raw 'SITx' 'SIT!' "StuffIt Expander - MacBinary (application/macbinary)" +.bld Raw 'GKON' 'BLD ' "GraphicConverter - BLD" +.bmp Raw 'ogle' 'BMPp' "PictureViewer - Windows Bitmap" +.boo Ascii 'ttxt' 'TEXT' "SimpleText - BOO encoded" +.bst Ascii 'ttxt' 'TEXT' "SimpleText - BibTex Style" +.bw Raw 'GKON' 'SGI ' "GraphicConverter - SGI Image" +.cel Raw 'GKON' 'CEL ' "GraphicConverter - KISS CEL" +.cgm Raw 'GKON' 'CGMm' "GraphicConverter - Computer Graphics Meta" +.class Raw 'CWIE' 'Clss' "CodeWarrior - Java Class File" +.clp Raw 'GKON' 'CLPp' "GraphicConverter - Windows Clipboard" +.cmd Ascii 'ttxt' 'TEXT' "SimpleText - OS/2 Batch File" +.com Raw 'SWIN' 'PCFA' "SoftWindows - MS-DOS Executable" +.cpp Ascii 'CWIE' 'TEXT' "CodeWarrior - C++ Source" +.cp Ascii 'CWIE' 'TEXT' "CodeWarrior - C++ Source" +.cpt Raw 'SITx' 'PACT' "StuffIt Expander - Compact Pro Archive" +.csv Ascii 'XCEL' 'TEXT' "Excel - Comma Separated Vars" +.ct Raw 'GKON' '..CT' "GraphicConverter - Scitex-CT" +.c Ascii 'CWIE' 'TEXT' "CodeWarrior - C Source" +.cur Raw 'GKON' 'CUR ' "GraphicConverter - Windows Cursor" +.cut Raw 'GKON' 'Halo' "GraphicConverter - Dr Halo Image" +.cvs Raw 'DAD2' 'drw2' "Canvas - Canvas Drawing" +.cwj Raw 'cwkj' 'CWSS' "ClarisWorks 4.0 - ClarisWorks Document" +.dat Raw 'GKON' 'TCLl' "GraphicConverter - TCL image" +.dbf Raw 'FOX+' 'COMP' "FoxBase+ - DBase Document" +.dc42 Raw 'dCpy' 'dImg' "DiskCopy - Floppy Disk image" +.dcx Raw 'GKON' 'DCXx' "GraphicConverter - Some PCX Images" +.dif Ascii 'XCEL' 'TEXT' "Excel - Data Interchange Format" +.diz Ascii 'R*Ch' 'TEXT' "BBEdit - BBS Descriptive Text" +.dl Raw 'AnVw' 'DL ' "MacAnim Viewer - DL Animation" +.dll Raw 'SWIN' 'PCFL' "SoftWindows - Windows DLL" +.doc Raw 'MSWD' 'WDBN' "Microsoft Word - Word Document (application/msword)" +.dot Raw 'MSWD' 'sDBN' "Microsoft Word - Word for Windows Template" +.dsk Raw 'dCpy' 'dImg' "Disk Copy - Apple DiskCopy Image" +.dvi Raw 'xdvi' 'ODVI' "xdvi - TeX DVI Document (application/x-dvi)" +.dwt Ascii 'DmWr' 'TEXT' "Dreamweaver - Dreamweaver Template" +.dxf Ascii 'SWVL' 'TEXT' "Swivel Pro - AutoCAD 3D Data" +.eps Raw 'vgrd' 'EPSF' "LaserWriter 8 - Postscript (application/postscript)" +.epsf Raw 'vgrd' 'EPSF' "LaserWriter 8 - Postscript (application/postscript)" +.etx Ascii 'ezVu' 'TEXT' "Easy View - SEText (text/x-setext)" +.evy Raw 'ENVY' 'EVYD' "Envoy - Envoy Document" +.exe Raw 'SWIN' 'PCFA' "SoftWindows - MS-DOS Executable" +.faq Ascii 'ttxt' 'TEXT' "SimpleText - ASCII Text (text/x-usenet-faq)" +.fit Raw 'GKON' 'FITS' "GraphicConverter - Flexible Image Transport (image/x-fits)" +.fla Raw 'MFL2' 'SPA ' "Macromedia Flash - Flash source" +.flc Raw 'TVOD' 'FLI ' "QuickTime Player - FLIC Animation" +.fli Raw 'TVOD' 'FLI ' "QuickTime Player - FLI Animation" +.fm Raw 'FMPR' 'FMPR' "FileMaker Pro - FileMaker Pro Database" +.for Ascii 'MPS ' 'TEXT' "MPW Shell - Fortran Source" +.fts Raw 'GKON' 'FITS' "GraphicConverter - Flexible Image Transport" +.gem Raw 'GKON' 'GEM-' "GraphicConverter - GEM Metafile" +.gif Raw 'ogle' 'GIFf' "PictureViewer - GIF Picture (image/gif)" +.gl Raw 'AnVw' 'GL ' "MacAnim Viewer - GL Animation" +.grp Raw 'GKON' 'GRPp' "GraphicConverter - GRP Image" +.gz Raw 'SITx' 'SIT!' "StuffIt Expander - Gnu ZIP Archive (application/x-gzip)" +.hcom Raw 'SCPL' 'FSSD' "SoundApp - SoundEdit Sound ex SOX" +.hpgl Raw 'GKON' 'HPGL' "GraphicConverter - HP GL/2" +.hpp Ascii 'CWIE' 'TEXT' "CodeWarrior - C Include File" +.hp Ascii 'CWIE' 'TEXT' "CodeWarrior - C Include File" +.hqx Ascii 'SITx' 'TEXT' "StuffIt Expander - BinHex (application/mac-binhex40)" +.hr Raw 'GKON' 'TR80' "GraphicConverter - TSR-80 HR +.h Ascii 'CWIE' 'TEXT' "CodeWarrior - C Include File" +.html Ascii 'MOSS' 'TEXT' "Netscape Communicator - HyperText (text/html)" +.htm Ascii 'MOSS' 'TEXT' "Netscape Communicator - HyperText (text/html)" +.i3 Ascii 'R*ch' 'TEXT' "BBEdit - Modula 3 Interface" +.ic1 Raw 'GKON' 'IMAG' "GraphicConverter - Atari Image" +.ic2 Raw 'GKON' 'IMAG' "GraphicConverter - Atari Image" +.ic3 Raw 'GKON' 'IMAG' "GraphicConverter - Atari Image" +.icn Raw 'GKON' 'ICO ' "GraphicConverter - Windows Icon" +.ico Raw 'GKON' 'ICO ' "GraphicConverter - Windows Icon" +.ief Raw 'GKON' 'IEF ' "GraphicConverter - IEF image (image/ief)" +.iff Raw 'GKON' 'ILBM' "GraphicConverter - Amiga IFF Image" +.ilbm Raw 'GKON' 'ILBM' "GraphicConverter - Amiga ILBM Image" +.image Raw 'dCpy' 'dImg' "DiskCopy - Apple DiskCopy Image" +.img Raw 'dCpy' 'dImg' "DiskCopy - Apple DiskCopy Image" +.ini Ascii 'ttxt' 'TEXT' "SimpleText - Windows INI File" +.iso Raw 'ddsk' 'rodh' "Disk Copy - Apple ISO Image" +.iss Raw 'GKON' 'ISS ' "GraphicConverter - ISS" +.java Ascii 'CWIE' 'TEXT' "CodeWarrior - Java Source File" +.jfif Raw 'ogle' 'JFIF' "PictureViewer - JFIF Image" +.jif Raw 'GKON' 'JIFf' "GraphicConverter - JIF99a" +.jpeg Raw 'ogle' 'JPEG' "PictureViewer - JPEG Picture (image/jpeg)" +.jpe Raw 'ogle' 'JPEG' "PictureViewer - JPEG Picture (image/jpeg)" +.jpg Raw 'ogle' 'JPEG' "PictureViewer - JPEG Picture (image/jpeg)" +.latex Ascii 'OTEX' 'TEXT' "OzTex - Latex (application/x-latex)" +.lbm Raw 'GKON' 'ILBM' "GraphicConverter - Amiga IFF Image" +.lha Raw 'SITx' 'LHA ' "StuffIt Expander - LHArc Archive" +.lwf Raw 'GKON' 'lwfF' "GraphicConverter - LuraWave(LWF)" +.lzh Raw 'SITx' 'LHA ' "StuffIt Expander - LHArc Archive" +.m1a Raw 'TVOD' 'MPEG' "MoviePlayer - MPEG-1 audiostream (audio/x-mpeg)" +.m1s Raw 'TVOD' 'MPEG' "MoviePlayer - MPEG-1 systemstream" +.m1v Raw 'TVOD' 'M1V ' "MoviePlayer - MPEG-1 IPB videostream (video/mpeg)" +.m2 Ascii 'R*ch' 'TEXT' "BBEdit - Modula 2 Source" +.m2v Raw 'MPG2' 'MPG2' "MPEG2decoder - MPEG-2 IPB videostream" +.m3 Ascii 'R*ch' 'TEXT' "BBEdit - Modula 3 Source" +.mac Raw 'ogle' 'PICT' "PictureViewer - PICT Picture (image/x-pict)" +.mak Ascii 'R*ch' 'TEXT' "BBEdit - Makefile" +.mbm Raw 'GKON' 'MBM ' "GraphicConverter - PSION 5(MBM)" +.mcw Raw 'MSWD' 'WDBN' "Microsoft Word - Mac Word Document" +.med Raw 'SCPL' 'STrk' "SoundApp - Amiga MED Sound" +.me Ascii 'ttxt' 'TEXT' "SimpleText - Text Readme" +.mf Ascii '*MF*' 'TEXT' "Metafont - Metafont" +.midi Raw 'TVOD' 'Midi' "MoviePlayer - MIDI Music" +.mid Raw 'TVOD' 'Midi' "MoviePlayer - MIDI Music" +.mif Ascii 'Fram' 'TEXT' "FrameMaker - FrameMaker MIF (application/x-framemaker)" +.mime Ascii 'SITx' 'TEXT' "StuffIt Expander - MIME Message (message/rfc822)" +.ml Ascii 'R*ch' 'TEXT' "BBEdit - ML Source" +.mod Raw 'SCPL' 'STrk' "SoundApp - MOD Music" +.mol Ascii 'RSML' 'TEXT' "RasMac - MDL Molfile" +.moov Raw 'TVOD' 'MooV' "MoviePlayer - QuickTime Movie (video/quicktime)" +.mov Raw 'TVOD' 'MooV' "MoviePlayer - QuickTime Movie (video/quicktime)" +.mp2 Raw 'TVOD' 'MPEG' "MoviePlayer - MPEG-1 audiostream (audio/x-mpeg)" +.mp3 Raw 'TVOD' 'MPG3' "MoviePlayer - MPEG-3 audiostream (audio/x-mpeg)" +.mpa Raw 'TVOD' 'MPEG' "MoviePlayer - MPEG-1 audiostream (audio/x-mpeg)" +.mpeg Raw 'TVOD' 'MPEG' "MoviePlayer - MPEG Movie of some sort (video/mpeg)" +.mpe Raw 'TVOD' 'MPEG' "MoviePlayer - MPEG Movie of some sort (video/mpeg)" +.mpg Raw 'TVOD' 'MPEG' "MoviePlayer - MPEG Movie of some sort (video/mpeg)" +.msp Raw 'GKON' 'MSPp' "GraphicConverter - Microsoft Paint" +.mtm Raw 'SNPL' 'MTM ' "PlayerPro - MultiMOD Music" +.mwii Raw 'MWII' 'MW2D' "MacWrite II - MacWrite Document (application/macwriteii)" +.mw Raw 'MWII' 'MW2D' "MacWrite II - MacWrite Document (application/macwriteii)" +.neo Raw 'GKON' 'NeoC' "GraphicConverter - Atari NeoChrome" +.nfo Ascii 'ttxt' 'TEXT' "SimpleText - Info Text (application/text)" +.ngg Raw 'GKON' 'NGGC' "GraphicConverter - Mobile Phone (Nokia) Format" +.nol Raw 'GKON' 'NOL ' "GraphicConverter - Phone (Nokia) Format" +.nst Raw 'SCPL' 'STrk' "SoundApp - MOD Music" +.obj Raw 'SWIN' 'PCFL' "SoftWindows - Object (DOS/Windows)" +.oda Raw 'ODA ' 'ODIF' "MacODA XTND Translator - ODA Document (application/oda)" +.okt Raw 'SCPL' 'OKTA' "SoundApp - Oktalyser MOD Music" +.out Raw 'hDmp' 'BINA' "HexEdit - Output File" +.ovl Raw 'SWIN' 'PCFL' "SoftWindows - Overlay (DOS/Windows)" +.pac Raw 'GKON' 'STAD' "GraphicConverter - Atari STAD Image" +.pal Raw '8BIM' '8BCT' "GraphicConverter - Color Table" +.pas Ascii 'CWIE' 'TEXT' "CodeWarrior - Pascal Source" +.pbm Raw 'GKON' 'PPGM' "GraphicConverter - Portable Bitmap (image/x-portable-bitmap)" +.pc1 Raw 'GKON' 'Dega' "GraphicConverter - Atari Degas Image" +.pc2 Raw 'GKON' 'Dega' "GraphicConverter - Atari Degas Image" +.pc3 Raw 'GKON' 'Dega' "GraphicConverter - Atari Degas Image" +.pcs Raw 'GKON' 'PICS' "GraphicConverter - Animated PICTs" +.pct Raw 'ogle' 'PICT' "PictureViewer - PICT Picture (image/x-pict)" +.pcx Raw 'GKON' 'PCXx' "GraphicConverter - PC PaintBrush" +.pdb Ascii 'RSML' 'TEXT' "RasMac - Brookhaven PDB file" +.pdf Raw 'CARO' 'PDF ' "Acrobat Reader - Portable Document Format (application/pdf)" +.pdx Ascii 'ALD5' 'TEXT' "PageMaker - Printer Description" +.pf Raw 'SITx' 'CSIT' "StuffIt Expander - Private File" +.pgc Raw 'GKON' 'PGCF' "GraphicConverter - PGC/PGF Atari Portfolio PCG" +.pgm Raw 'GKON' 'PPGM' "GraphicConverter - Portable Graymap (image/x-portable-graymap)" +.pi1 Raw 'GKON' 'Dega' "GraphicConverter - Atari Degas Image" +.pi2 Raw 'GKON' 'Dega' "GraphicConverter - Atari Degas Image" +.pi3 Raw 'GKON' 'Dega' "GraphicConverter - Atari Degas Image" +.pic Raw 'ogle' 'PICT' "PictureViewer - PICT Picture (image/x-pict)" +.pics Raw 'GKON' 'PICS' "GraphicConverter - PICS-PICT Sequence" +.pict Raw 'ogle' 'PICT' "PictureViewer - PICT Picture (image/x-macpict)" +.pit Raw 'SITx' 'PIT ' "StuffIt Expander - PackIt Archive" +.pkg Raw 'SITx' 'HBSF' "StuffIt Expander - AppleLink Package" +.pl Ascii 'McPL' 'TEXT' "MacPerl - Perl Source" +.plt Raw 'GKON' 'HPGL' "GraphicConverter - HP GL/2" +.pm3 Raw 'ALD3' 'ALB3' "PageMaker - PageMaker 3 Document" +.pm4 Raw 'ALD4' 'ALB4' "PageMaker - PageMaker 4 Document" +.pm5 Raw 'ALD5' 'ALB5' "PageMaker - PageMaker 5 Document" +.pm Raw 'GKON' 'PMpm' "GraphicConverter - Bitmap from xv" +.png Raw 'ogle' 'PNG ' "PictureViewer - Portable Network Graphic" +.pntg Raw 'ogle' 'PNTG' "PictureViewer - Macintosh Painting" +.ppd Ascii 'ALD5' 'TEXT' "PageMaker - Printer Description" +.ppm Raw 'GKON' 'PPGM' "GraphicConverter - Portable Pixmap (image/x-portable-pixmap)" +.prn Ascii 'R*ch' 'TEXT' "BBEdit - Printer Output File" +.psd Raw '8BIM' '8BPS' "Photoshop - PhotoShop Document" +.ps Ascii 'vgrd' 'TEXT' "LaserWriter 8 - PostScript (application/postscript)" +.pt4 Raw 'ALD4' 'ALT4' "PageMaker - PageMaker 4 Template" +.pt5 Raw 'ALD5' 'ALT5' "PageMaker - PageMaker 5 Template" +.p Ascii 'CWIE' 'TEXT' "CodeWarrior - Pascal Source" +.pxr Raw '8BIM' 'PXR ' "Photoshop - Pixar Image" +.qdv Raw 'GKON' 'QDVf' "GraphicConverter - QDV image" +.qt Raw 'TVOD' 'MooV' "MoviePlayer - QuickTime Movie (video/quicktime)" +.qxd Raw 'XPR3' 'XDOC' "QuarkXpress - QuarkXpress Document" +.qxt Raw 'XPR3' 'XTMP' "QuarkXpress - QuarkXpress Template" +.raw Raw 'ddsk' 'rodh' "Disk Copy - Apple raw disk Image" +.readme Ascii 'ttxt' 'TEXT' "SimpleText - Text Readme (application/text)" +.rgba Raw 'GKON' 'SGI ' "GraphicConverter - SGI Image (image/x-rgb)" +.rgb Raw 'GKON' 'SGI ' "GraphicConverter - SGI Image (image/x-rgb)" +.rib Ascii 'RINI' 'TEXT' "Renderman - Renderman 3D Data" +.rif Raw 'GKON' 'RIFF' "GraphicConverter - RIFF Graphic" +.rle Raw 'GKON' 'RLE ' "GraphicConverter - RLE image" +.rme Ascii 'ttxt' 'TEXT' "SimpleText - Text Readme" +.rpl Raw 'REP!' 'FRL!' "Replica - Replica Document" +.rsc Raw 'RSED' 'rsrc' "ResEdit - Resource File" +.rsrc Raw 'RSED' 'rsrc' "ResEdit - Resource File" +.rtf Ascii 'MSWD' 'TEXT' "Microsoft Word - Rich Text Format (application/rtf)" +.rtx Ascii 'R*ch' 'TEXT' "BBEdit - Rich Text (text/richtext)" +.s3m Raw 'SNPL' 'S3M ' "PlayerPro - ScreamTracker 3 MOD" +.scc Raw 'GKON' 'MSX ' "GraphicConverter - MSX pitcure" +.scg Raw 'GKON' 'RIX3' "GraphicConverter - ColoRIX" +.sci Raw 'GKON' 'RIX3' "GraphicConverter - ColoRIX" +.scp Raw 'GKON' 'RIX3' "GraphicConverter - ColoRIX" +.scr Raw 'GKON' 'RIX3' "GraphicConverter - ColoRIX" +.scu Raw 'GKON' 'RIX3' "GraphicConverter - ColoRIX" +.sea Raw '????' 'APPL' "Self Extracting Archive - Self-Extracting Archive" +.sf Raw 'SDHK' 'IRCM' "SoundHack - IRCAM Sound" +.sgi Raw 'ogle' 'SGI ' "PictureViewer - SGI Image" +.shar Ascii 'UnSh' 'TEXT' "UnShar - Unix Shell Archive (application/x-shar)" +.sha Ascii 'UnSh' 'TEXT' "UnShar - Unix Shell Archive (application/x-shar)" +.shp Raw 'GKON' 'SHPp' "GraphicConverter - Printmaster Icon Library" +.sithqx Ascii 'SITx' 'TEXT' "StuffIt Expander - BinHexed StuffIt Archive (application/mac-binhex40)" +.sit Raw 'SITx' 'SIT!' "StuffIt Expander - StuffIt 1.5.1 Archive (application/x-stuffit)" +.six Raw 'GKON' 'SIXE' "GraphicConverter - SIXEL image" +.slk Ascii 'XCEL' 'TEXT' "Excel - SYLK Spreadsheet" +.snd Raw 'SCPL' 'BINA' "SoundApp - Sound of various types" +.spc Raw 'GKON' 'Spec' "GraphicConverter - Atari Spectrum 512" +.sr Raw 'GKON' 'SUNn' "GraphicConverter - Sun Raster Image" +.sty Ascii '*TEX' 'TEXT' "Textures - TeX Style" +.sun Raw 'GKON' 'SUNn' "GraphicConverter - Sun Raster Image" +.sup Raw 'GKON' 'SCRN' "GraphicConverter - StartupScreen" +.svx Raw 'SCPL' '8SVX' "SoundApp - Amiga IFF Sound" +.swf Raw 'SWF2' 'SWFL' "Macromedia Flash - Flash" +.syk Ascii 'XCEL' 'TEXT' "Excel - SYLK Spreadsheet" +.sylk Ascii 'XCEL' 'TEXT' "Excel - SYLK Spreadsheet" +.targa Raw 'GKON' 'TPIC' "GraphicConverter - Truevision Image" +.tar Raw 'SITx' 'TARF' "StuffIt Expander - Unix Tape ARchive (application/x-tar)" +.taz Raw 'SITx' 'ZIVU' "StuffIt Expander - Compressed Tape ARchive (application/x-compress)" +.texinfo Ascii 'OTEX' 'TEXT' "OzTeX - TeX Document (application/x-texinfo)" +.texi Ascii 'OTEX' 'TEXT' "OzTeX - TeX Document" +.tex Ascii 'OTEX' 'TEXT' "OzTeX - TeX Document (application/x-tex)" +.text Ascii 'ttxt' 'TEXT' "SimpleText - ASCII Text (text/plain)" +.tga Raw 'GKON' 'TPIC' "GraphicConverter - Truevision Image" +.tgz Raw 'SITx' 'Gzip' "StuffIt Expander - Gnu ZIPed Tape ARchive (application/x-gzip)" +.tiff Raw 'ogle' 'TIFF' "PictureViewer - TIFF Picture (image/tiff)" +.tif Raw 'ogle' 'TIFF' "PictureViewer - TIFF Picture (image/tiff)" +.tny Raw 'GKON' 'TINY' "GraphicConverter - Atari TINY Bitmap" +.tsv Ascii 'XCEL' 'TEXT' "Excel - Tab Separated Values (text/tab-separated-values)" +.tx8 Ascii 'ttxt' 'TEXT' "SimpleText - 8-bit ASCII Text" +.txt Ascii 'ttxt' 'TEXT' "SimpleText - ASCII Text (text/plain)" +.ul Raw 'TVOD' 'ULAW' "MoviePlayer - Mu-Law Sound (audio/basic)" +.url Raw 'Arch' 'AURL' "Anarchie - URL Bookmark (message/external-body)" +.uue Ascii 'SITx' 'TEXT' "StuffIt Expander - UUEncode" +.uu Ascii 'SITx' 'TEXT' "StuffIt Expander - UUEncode" +.vff Raw 'GKON' 'VFFf' "GraphicConverter - DESR VFF Greyscale Image" +.vga Raw 'ogle' 'BMPp' "PictureViewer - OS/2 Bitmap" +.voc Raw 'SCPL' 'VOC ' "SoundApp - VOC Sound" +.vpb Raw 'GKON' 'VPB ' "GraphicConverter - VPB QUANTEL" +.w51 Raw 'WPC2' '.WP5' "WordPerfect - WordPerfect PC 5.1 Doc (application/wordperfect5.1)" +.wav Raw 'TVOD' 'WAVE' "MoviePlayer - Windows WAV Sound (audio/x-wav)" +.wbmp Raw 'GKON' 'WBMP' "GraphicConverter - WBMP" +.wk1 Raw 'XCEL' 'XLBN' "Excel - Lotus Spreadsheet r2.1" +.wks Raw 'XCEL' 'XLBN' "Excel - Lotus Spreadsheet r1.x" +.wmf Raw 'GKON' 'WMF ' "GraphicConverter - Windows Metafile" +.wp4 Raw 'WPC2' '.WP4' "WordPerfect - WordPerfect PC 4.2 Doc" +.wp5 Raw 'WPC2' '.WP5' "WordPerfect - WordPerfect PC 5.x Doc (application/wordperfect5.1)" +.wp6 Raw 'WPC2' '.WP6' "WordPerfect - WordPerfect PC 6.x Doc" +.wpg Raw 'GKON' 'WPGf' "GraphicConverter - WordPerfect Graphic" +.wpm Raw 'WPC2' 'WPD1' "WordPerfect - WordPerfect Mac" +.wp Raw 'WPC2' '.WP5' "WordPerfect - WordPerfect PC 5.x Doc (application/wordperfect5.1)" +.wri Raw 'MSWD' 'WDBN' "Microsoft Word - MS Write/Windows" +.wve Raw 'SCPL' 'BINA' "SoundApp - PSION sound" +.x10 Raw 'GKON' 'XWDd' "GraphicConverter - X-Windows Dump (image/x-xwd)" +.x11 Raw 'GKON' 'XWDd' "GraphicConverter - X-Windows Dump (image/x-xwd)" +.xbm Raw 'GKON' 'XBM ' "GraphicConverter - X-Windows Bitmap (image/x-xbm)" +.x-face Raw 'GKON' 'TEXT' "GraphicConverter - X-Face" +.xlc Raw 'XCEL' 'XLC ' "Excel - Excel Chart" +.xlm Raw 'XCEL' 'XLM ' "Excel - Excel Macro" +.xls Raw 'XCEL' 'XLS ' "Excel - Excel Spreadsheet" +.xlw Raw 'XCEL' 'XLW ' "Excel - Excel Workspace" +.xl Raw 'XCEL' 'XLS ' "Excel - Excel Spreadsheet" +.xm Raw 'SNPL' 'XM ' "PlayerPro - FastTracker MOD Music" +.xpm Raw 'GKON' 'XPM ' "GraphicConverter - X-Windows Pixmap (image/x-xpm)" +.xpm Raw 'GKON' 'XPM ' "GraphicConverter - X-Windows Pixmap (image/x-xpixmap)" +.xwd Raw 'GKON' 'XWDd' "GraphicConverter - X-Windows Dump (image/x-xwd)" +.zip Raw 'SITx' 'ZIP ' "StuffIt Expander - PC ZIP Archive (application/zip)" +.zoo Raw 'Booz' 'Zoo ' "MacBooz - Zoo Archive" +.z Raw 'SITx' 'ZIVU' "StuffIt Expander - Unix Compress Archive (application/x-compress)" +# +# The default translation for anything not recognized: +# +* Ascii 'ttxt' 'TEXT' "SimpleText - Text file" diff --git a/python/web/src/web.py b/python/web/src/web.py index 349d9aaa..fbed803f 100644 --- a/python/web/src/web.py +++ b/python/web/src/web.py @@ -940,7 +940,27 @@ def download_to_iso(): local_file = request.form.get("file") if iso_type == "HFS": - iso_args = ["-hfs"] + # The file genisoimage_hfs_resource_fork_map.txt is part of the piscsi + # repository tree, so it should be present in the parent folder; trust but verify: + genisoimage_hfs_resource_fork_map_file_path = Path( + f"{WEB_DIR}/../genisoimage_hfs_resource_fork_map.txt" + ) + if genisoimage_hfs_resource_fork_map_file_path.exists(): + # genisoimage will look up the file extension in this map file to + # derive the file's CREATOR and TYPE resource fork attributes. + # See more at https://linux.die.net/man/1/genisoimage + iso_args = ["-hfs", "-map", str(genisoimage_hfs_resource_fork_map_file_path)] + logging.info( + "Found and using the genisoimage hfs map file at %s", + str(genisoimage_hfs_resource_fork_map_file_path), + ) + else: + logging.warning( + "The genisoimage hfs map file is not present at %s. " + "Will not set resource fork attributes of files in the iso image!", + str(genisoimage_hfs_resource_fork_map_file_path), + ) + iso_args = ["-hfs"] elif iso_type == "ISO-9660 Level 1": iso_args = ["-iso-level", "1"] elif iso_type == "ISO-9660 Level 2": From abc5c4b9ac1c74dc37b412cfe9b0555aaa8620ad Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Mon, 20 Nov 2023 07:40:53 +0100 Subject: [PATCH 24/36] scsictl: Create files with binary/JSON or text format protobuf data (#1369) --- cpp/scsictl/scsictl_core.cpp | 430 +++++++++++++++++++++-------------- cpp/scsictl/scsictl_core.h | 6 +- doc/scsictl.1 | 13 ++ doc/scsictl_man_page.txt | 43 ++-- 4 files changed, 309 insertions(+), 183 deletions(-) diff --git a/cpp/scsictl/scsictl_core.cpp b/cpp/scsictl/scsictl_core.cpp index 6f6b8860..6cee760b 100644 --- a/cpp/scsictl/scsictl_core.cpp +++ b/cpp/scsictl/scsictl_core.cpp @@ -1,11 +1,12 @@ //--------------------------------------------------------------------------- // -// 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) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -18,11 +19,16 @@ #include "scsictl/scsictl_parser.h" #include "scsictl/scsictl_commands.h" #include "scsictl/scsictl_core.h" +#include +#include #include #include #include +#include using namespace std; +using namespace google::protobuf; +using namespace google::protobuf::util; using namespace piscsi_interface; using namespace piscsi_util; using namespace protobuf_util; @@ -33,8 +39,8 @@ void ScsiCtl::Banner(const vector& args) const 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] " + << "[-C FILENAME:FILESIZE] [-d FILENAME] [-B FILENAME] [-J FILENAME] [-T FILENAME] [-R CURRENT_NAME:NEW_NAME] " + << "[-x CURRENT_NAME:NEW_NAME] [-z LOCALE] " << "[-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" @@ -66,7 +72,7 @@ int ScsiCtl::run(const vector& args) const PbCommand command; PbDeviceDefinition* device = command.add_devices(); device->set_id(-1); - const char *hostname = "localhost"; + string hostname = "localhost"; int port = 6868; string param; string log_level; @@ -74,7 +80,10 @@ int ScsiCtl::run(const vector& args) const string reserved_ids; string image_params; string filename; - string token; + string filename_json; + string filename_binary; + string filename_text; + string token; bool list = false; string locale = GetLocale(); @@ -82,186 +91,228 @@ int ScsiCtl::run(const vector& args) const opterr = 1; int opt; while ((opt = getopt(static_cast(args.size()), args.data(), - "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()) { - cerr << "Error: " << error << endl; - exit(EXIT_FAILURE); - } - break; + "e::lmos::vDINOSTVXa:b:c:d:f:h:i:n:p:r:t:x:z:B:C:E:F:J:L:P::R:Z:")) != -1) { + switch (opt) { + case 'i': + if (const string error = SetIdAndLun(*device, optarg); !error.empty()) { + cerr << "Error: " << error << endl; + exit(EXIT_FAILURE); + } + break; - case 'C': - command.set_operation(CREATE_IMAGE); - image_params = optarg; - break; + case 'C': + command.set_operation(CREATE_IMAGE); + image_params = optarg; + break; - case 'b': - int block_size; - if (!GetAsUnsignedInt(optarg, block_size)) { - cerr << "Error: Invalid block size " << optarg << endl; - exit(EXIT_FAILURE); - } - device->set_block_size(block_size); - break; + case 'b': + int block_size; + if (!GetAsUnsignedInt(optarg, block_size)) { + cerr << "Error: Invalid block size " << optarg << endl; + exit(EXIT_FAILURE); + } + device->set_block_size(block_size); + break; - case 'c': - command.set_operation(parser.ParseOperation(optarg)); - if (command.operation() == NO_OPERATION) { - cerr << "Error: Unknown operation '" << optarg << "'" << endl; - exit(EXIT_FAILURE); - } - break; + case 'c': + command.set_operation(parser.ParseOperation(optarg)); + if (command.operation() == NO_OPERATION) { + cerr << "Error: Unknown operation '" << optarg << "'" << endl; + exit(EXIT_FAILURE); + } + break; - case 'D': - command.set_operation(DETACH_ALL); - break; + case 'D': + command.set_operation(DETACH_ALL); + break; - case 'd': - command.set_operation(DELETE_IMAGE); - image_params = optarg; - break; + case 'd': + command.set_operation(DELETE_IMAGE); + image_params = optarg; + break; - case 'E': - command.set_operation(IMAGE_FILE_INFO); - filename = optarg; - break; + case 'E': + filename = optarg; + if (filename.empty()) { + cerr << "Error: Missing filename" << endl; + exit(EXIT_FAILURE); + } + command.set_operation(IMAGE_FILE_INFO); + break; - case 'e': - command.set_operation(DEFAULT_IMAGE_FILES_INFO); - if (optarg) { - SetCommandParams(command, optarg); + case 'e': + command.set_operation(DEFAULT_IMAGE_FILES_INFO); + if (optarg) { + SetCommandParams(command, optarg); + } + break; + + case 'F': + command.set_operation(DEFAULT_FOLDER); + default_folder = optarg; + break; + + case 'f': + param = optarg; + break; + + case 'h': + hostname = optarg; + if (hostname.empty()) { + cerr << "Error: Missing hostname" << endl; + exit(EXIT_FAILURE); + } + break; + + case 'B': + filename_binary = optarg; + if (filename_binary.empty()) { + cerr << "Error: Missing filename" << endl; + exit(EXIT_FAILURE); + } + break; + + case 'J': + filename_json = optarg; + if (filename_json.empty()) { + cerr << "Error: Missing filename" << endl; + exit(EXIT_FAILURE); + } + break; + + case 'Z': + filename_text = optarg; + if (filename_text.empty()) { + cerr << "Error: Missing filename" << endl; + exit(EXIT_FAILURE); + } + break; + + case 'I': + command.set_operation(RESERVED_IDS_INFO); + break; + + case 'L': + command.set_operation(LOG_LEVEL); + log_level = optarg; + break; + + case 'l': + list = true; + break; + + case 'm': + command.set_operation(MAPPING_INFO); + break; + + case 'N': + command.set_operation(NETWORK_INTERFACES_INFO); + break; + + case 'O': + command.set_operation(LOG_LEVEL_INFO); + break; + + case 'o': + command.set_operation(OPERATION_INFO); + break; + + case 't': + device->set_type(parser.ParseType(optarg)); + if (device->type() == UNDEFINED) { + cerr << "Error: Unknown device type '" << optarg << "'" << endl; + exit(EXIT_FAILURE); + } + break; + + case 'r': + command.set_operation(RESERVE_IDS); + reserved_ids = optarg; + break; + + case 'R': + command.set_operation(RENAME_IMAGE); + image_params = optarg; + break; + + case 'n': + SetProductData(*device, optarg); + break; + + case 'p': + if (!GetAsUnsignedInt(optarg, port) || port <= 0 || port > 65535) { + cerr << "Error: Invalid port " << optarg << ", port must be between 1 and 65535" << endl; + exit(EXIT_FAILURE); + } + break; + + case 's': + command.set_operation(SERVER_INFO); + if (optarg) { + if (const string error = SetCommandParams(command, optarg); !error.empty()) { + cerr << "Error: " << error << endl; + exit(EXIT_FAILURE); } - break; + } + break; - case 'F': - command.set_operation(DEFAULT_FOLDER); - default_folder = optarg; - break; + case 'S': + command.set_operation(STATISTICS_INFO); + break; - case 'f': - param = optarg; - break; + case 'v': + cout << "scsictl version: " << piscsi_get_version_string() << '\n'; + exit(EXIT_SUCCESS); + break; - case 'h': - hostname = optarg; - break; + case 'P': + token = optarg ? optarg : getpass("Password: "); + break; - case 'I': - command.set_operation(RESERVED_IDS_INFO); - break; + case 'V': + command.set_operation(VERSION_INFO); + break; - case 'L': - command.set_operation(LOG_LEVEL); - log_level = optarg; - break; + case 'x': + command.set_operation(COPY_IMAGE); + image_params = optarg; + break; - case 'l': - list = true; - break; + case 'T': + command.set_operation(DEVICE_TYPES_INFO); + break; - case 'm': - command.set_operation(MAPPING_INFO); - break; + case 'X': + command.set_operation(SHUT_DOWN); + SetParam(command, "mode", "rascsi"); + break; - case 'N': - command.set_operation(NETWORK_INTERFACES_INFO); - break; + case 'z': + locale = optarg; + break; - case 'O': - command.set_operation(LOG_LEVEL_INFO); - break; - - case 'o': - command.set_operation(OPERATION_INFO); - break; - - case 't': - device->set_type(parser.ParseType(optarg)); - if (device->type() == UNDEFINED) { - cerr << "Error: Unknown device type '" << optarg << "'" << endl; - exit(EXIT_FAILURE); - } - break; - - case 'r': - command.set_operation(RESERVE_IDS); - reserved_ids = optarg; - break; - - case 'R': - command.set_operation(RENAME_IMAGE); - image_params = optarg; - break; - - case 'n': - SetProductData(*device, optarg); - break; - - case 'p': - if (!GetAsUnsignedInt(optarg, port) || port <= 0 || port > 65535) { - cerr << "Error: Invalid port " << optarg << ", port must be between 1 and 65535" << endl; - exit(EXIT_FAILURE); - } - break; - - case 's': - command.set_operation(SERVER_INFO); - if (optarg) { - if (const string error = SetCommandParams(command, optarg); !error.empty()) { - cerr << "Error: " << error << endl; - exit(EXIT_FAILURE); - } - } - break; - - case 'S': - command.set_operation(STATISTICS_INFO); - break; - - case 'v': - cout << "scsictl version: " << piscsi_get_version_string() << '\n'; - exit(EXIT_SUCCESS); - break; - - case 'P': - token = optarg ? optarg : getpass("Password: "); - break; - - case 'V': - command.set_operation(VERSION_INFO); - break; - - case 'x': - command.set_operation(COPY_IMAGE); - image_params = optarg; - break; - - case 'T': - command.set_operation(DEVICE_TYPES_INFO); - break; - - case 'X': - command.set_operation(SHUT_DOWN); - SetParam(command, "mode", "rascsi"); - break; - - case 'z': - locale = optarg; - break; - - default: - break; - } - } + default: + break; + } + } // For macos only 'optind != argc' appears to work, but then non-argument options do not reject arguments if (optopt) { exit(EXIT_FAILURE); } - SetParam(command, "token", token); - SetParam(command, "locale", locale); + if (!filename_json.empty()) { + return ExportAsJson(command, filename_json); + } + if (!filename_binary.empty()) { + return ExportAsBinary(command, filename_binary); + } + if (!filename_text.empty()) { + return ExportAsText(command, filename_text); + } + + SetParam(command, "token", token); + SetParam(command, "locale", locale); ScsictlCommands scsictl_commands(command, hostname, port); @@ -277,7 +328,7 @@ int ScsiCtl::run(const vector& args) const else { ParseParameters(*device, param); - status = scsictl_commands.Execute(log_level, default_folder, reserved_ids, image_params, filename); + status = scsictl_commands.Execute(log_level, default_folder, reserved_ids, image_params, filename); } } catch(const io_exception& e) { @@ -290,3 +341,48 @@ int ScsiCtl::run(const vector& args) const return status ? EXIT_SUCCESS : EXIT_FAILURE; } + +int ScsiCtl::ExportAsBinary(const PbCommand &command, const string &filename) const +{ + const string binary = command.SerializeAsString(); + + ofstream out; + out.open(filename, ios::binary); + out << binary; + if (out.fail()) { + cerr << "Error: Can't create protobuf binary file '" << filename << "'" << endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +int ScsiCtl::ExportAsJson(const PbCommand &command, const string &filename) const +{ + string json; + MessageToJsonString(command, &json); + + ofstream out(filename); + out << json; + if (out.fail()) { + cerr << "Error: Can't create protobuf JSON file '" << filename << "'" << endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +int ScsiCtl::ExportAsText(const PbCommand &command, const string &filename) const +{ + string text; + TextFormat::PrintToString(command, &text); + + ofstream out(filename); + out << text; + if (out.fail()) { + cerr << "Error: Can't create protobuf text format file '" << filename << "'" << endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/cpp/scsictl/scsictl_core.h b/cpp/scsictl/scsictl_core.h index a96076f4..4b8025ec 100644 --- a/cpp/scsictl/scsictl_core.h +++ b/cpp/scsictl/scsictl_core.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -28,4 +28,8 @@ class ScsiCtl private: void Banner(const vector&) const; + + int ExportAsBinary(const PbCommand&, const string&) const; + int ExportAsJson(const PbCommand&, const string&) const; + int ExportAsText(const PbCommand&, const string&) const; }; diff --git a/doc/scsictl.1 b/doc/scsictl.1 index 3df74fba..7737e939 100644 --- a/doc/scsictl.1 +++ b/doc/scsictl.1 @@ -17,6 +17,10 @@ scsictl \- Sends management commands to the piscsi process \fB\-T\fR | \fB\-V\fR | \fB\-X\fR | +\fB\-Z\fR | +[\fB\-d\fR \fIFILENAME\fR] | +[\fB\-B\fR \fIFILENAME\fR] | +[\fB\-J\fR \fIFILENAME\fR] | [\fB\-C\fR \fIFILENAME:FILESIZE\fR] | [\fB\-E\fR \fIFILENAME\fR] | [\fB\-F\fR \fIIMAGE_FOLDER\fR] | @@ -117,6 +121,15 @@ Shut down the piscsi process. .BR \-d\fI " "\fIFILENAME Delete an image file in the default image folder. .TP +.BR \-B\fI " "\fIFILENAME +Do not send command to piscsi but write it to a protobuf binary file. +.TP +.BR \-J\fI " "\fIFILENAME +Do not send command to piscsi but write it to a protobuf JSON file. +.TP +.BR \-Z\fI " "\fIFILENAME +Do not send command to piscsi but write it to a protobuf text format file. +.TP .BR \-x\fI " "\fICURRENT_NAME:NEW_NAME Copy an image file in the default image folder. .TP diff --git a/doc/scsictl_man_page.txt b/doc/scsictl_man_page.txt index f98b7208..7dfc6e10 100644 --- a/doc/scsictl_man_page.txt +++ b/doc/scsictl_man_page.txt @@ -7,22 +7,23 @@ NAME SYNOPSIS 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‐ + | -X | -Z | [-d FILENAME] | [-B FILENAME] | [-J FILENAME] | [-C FILE‐ + NAME: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] | [-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. scsictl also runs on + 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 + Note: The command and type arguments are case insensitive. Only the first letter of the command/type is evaluated by the tool. OPTIONS @@ -41,7 +42,7 @@ OPTIONS -I Gets the list of reserved device IDs. -L LOG_LEVEL - Set the piscsi log level (trace, debug, info, warning, error, + Set the piscsi log level (trace, debug, info, warning, error, off). -h HOST @@ -49,19 +50,19 @@ OPTIONS -e List all images files in the default image folder. - -N Lists all available network interfaces provided that they are + -N Lists all available network interfaces provided that they are up. - -O Display the available piscsi server log levels and the current + -O Display the available piscsi server log levels and the current log level. - -P Prompt for the access token in case piscsi requires authentica‐ + -P Prompt for the access token in case piscsi requires authentica‐ tion. - -l List all of the devices that are currently being emulated by + -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 + -m List all file extensions recognized by PiSCSI and the device types they map to. -o Display operation meta data information. @@ -73,11 +74,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 + Comma-separated list of IDs to reserve. Pass an empty list in order to not reserve anything. -s [FOLDER_PATTERN:FILE_PATTERN:OPERATIONS] - Display server-side settings like available images or supported + Display server-side settings like available images or supported device types. -S Display statistics. @@ -93,6 +94,18 @@ OPTIONS -d FILENAME Delete an image file in the default image folder. + -B FILENAME + Do not send command to piscsi but write it to a protobuf binary + file. + + -J FILENAME + Do not send command to piscsi but write it to a protobuf JSON + file. + + -Z FILENAME + Do not send command to piscsi but write it to a protobuf text + format file. + -x CURRENT_NAME:NEW_NAME Copy an image file in the default image folder. From 4d1a10cb6b27a2dfef9b3e798d28f4c743f08d2d Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Sun, 26 Nov 2023 05:25:54 +0100 Subject: [PATCH 25/36] Fix missing logging for a LUN when the LUN is explicitly specified (#1379) * Fix missing logging for a LUN when the LUN is explicitly specified * Do not suppress controller messages when LUN is specified * Fix misleading logging for DaynaPort --- cpp/controllers/scsi_controller.cpp | 4 ++++ cpp/devices/device_logger.cpp | 18 ++++++++---------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/cpp/controllers/scsi_controller.cpp b/cpp/controllers/scsi_controller.cpp index 09752595..773b06dd 100644 --- a/cpp/controllers/scsi_controller.cpp +++ b/cpp/controllers/scsi_controller.cpp @@ -865,6 +865,10 @@ bool ScsiController::XferOutBlockOriented(bool cont) LogTrace("Done with DaynaPort Set Multicast Address"); break; + case scsi_command::eCmdSetIfaceMode: + LogTrace("Done with setting DaynaPort MAC address (ignore)"); + break; + default: stringstream s; s << "Received an unexpected command ($" << setfill('0') << setw(2) << hex diff --git a/cpp/devices/device_logger.cpp b/cpp/devices/device_logger.cpp index 65afb129..4d78364a 100644 --- a/cpp/devices/device_logger.cpp +++ b/cpp/devices/device_logger.cpp @@ -39,16 +39,14 @@ void DeviceLogger::Error(const string& message) const void DeviceLogger::Log(level::level_enum level, const string& message) const { - if (!message.empty() && - (log_device_id == -1 || - (log_device_id == id && (log_device_lun == -1 || log_device_lun == lun)))) { - if (lun == -1) { - log(level, "(ID " + to_string(id) + ") - " + message); - } - else { - log(level, "(ID:LUN " + to_string(id) + ":" + to_string(lun) + ") - " + message); - } - } + if ((log_device_id == -1 || log_device_id == id) && (lun == -1 || log_device_lun == -1 || log_device_lun == lun)) { + if (lun == -1) { + log(level, "(ID " + to_string(id) + ") - " + message); + } + else { + log(level, "(ID:LUN " + to_string(id) + ":" + to_string(lun) + ") - " + message); + } + } } void DeviceLogger::SetIdAndLun(int i, int l) From 28959aaf977e540f57ca17151a019717c31e1a60 Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Sun, 26 Nov 2023 13:13:26 +0900 Subject: [PATCH 26/36] Revert "scsictl: Create files with binary/JSON or text format protobuf data (#1369)" This reverts commit abc5c4b9ac1c74dc37b412cfe9b0555aaa8620ad. --- cpp/scsictl/scsictl_core.cpp | 430 ++++++++++++++--------------------- cpp/scsictl/scsictl_core.h | 6 +- doc/scsictl.1 | 13 -- doc/scsictl_man_page.txt | 43 ++-- 4 files changed, 183 insertions(+), 309 deletions(-) diff --git a/cpp/scsictl/scsictl_core.cpp b/cpp/scsictl/scsictl_core.cpp index 6cee760b..6f6b8860 100644 --- a/cpp/scsictl/scsictl_core.cpp +++ b/cpp/scsictl/scsictl_core.cpp @@ -1,12 +1,11 @@ //--------------------------------------------------------------------------- // -// 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 -// Copyright (C) 2021-2023 Uwe Seimet +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2020 GIMONS +// Copyright (C) 2020-2023 Contributors to the PiSCSI project // //--------------------------------------------------------------------------- @@ -19,16 +18,11 @@ #include "scsictl/scsictl_parser.h" #include "scsictl/scsictl_commands.h" #include "scsictl/scsictl_core.h" -#include -#include #include #include #include -#include using namespace std; -using namespace google::protobuf; -using namespace google::protobuf::util; using namespace piscsi_interface; using namespace piscsi_util; using namespace protobuf_util; @@ -39,8 +33,8 @@ void ScsiCtl::Banner(const vector& args) const 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] [-B FILENAME] [-J FILENAME] [-T FILENAME] [-R CURRENT_NAME:NEW_NAME] " - << "[-x CURRENT_NAME:NEW_NAME] [-z LOCALE] " + << "[-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] [-S] [-v] [-V] [-y] [-X]\n" << " where ID[:LUN] ID := {0-" << (ControllerManager::GetScsiIdMax() - 1) << "}," << " LUN := {0-" << (ControllerManager::GetScsiLunMax() - 1) << "}, default is 0\n" @@ -72,7 +66,7 @@ int ScsiCtl::run(const vector& args) const PbCommand command; PbDeviceDefinition* device = command.add_devices(); device->set_id(-1); - string hostname = "localhost"; + const char *hostname = "localhost"; int port = 6868; string param; string log_level; @@ -80,10 +74,7 @@ int ScsiCtl::run(const vector& args) const string reserved_ids; string image_params; string filename; - string filename_json; - string filename_binary; - string filename_text; - string token; + string token; bool list = false; string locale = GetLocale(); @@ -91,228 +82,186 @@ int ScsiCtl::run(const vector& args) const opterr = 1; int opt; while ((opt = getopt(static_cast(args.size()), args.data(), - "e::lmos::vDINOSTVXa:b:c:d:f:h:i:n:p:r:t:x:z:B:C:E:F:J:L:P::R:Z:")) != -1) { - switch (opt) { - case 'i': - if (const string error = SetIdAndLun(*device, optarg); !error.empty()) { - cerr << "Error: " << error << endl; - exit(EXIT_FAILURE); - } - break; + "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()) { + cerr << "Error: " << error << endl; + exit(EXIT_FAILURE); + } + break; - case 'C': - command.set_operation(CREATE_IMAGE); - image_params = optarg; - break; + case 'C': + command.set_operation(CREATE_IMAGE); + image_params = optarg; + break; - case 'b': - int block_size; - if (!GetAsUnsignedInt(optarg, block_size)) { - cerr << "Error: Invalid block size " << optarg << endl; - exit(EXIT_FAILURE); - } - device->set_block_size(block_size); - break; + case 'b': + int block_size; + if (!GetAsUnsignedInt(optarg, block_size)) { + cerr << "Error: Invalid block size " << optarg << endl; + exit(EXIT_FAILURE); + } + device->set_block_size(block_size); + break; - case 'c': - command.set_operation(parser.ParseOperation(optarg)); - if (command.operation() == NO_OPERATION) { - cerr << "Error: Unknown operation '" << optarg << "'" << endl; - exit(EXIT_FAILURE); - } - break; + case 'c': + command.set_operation(parser.ParseOperation(optarg)); + if (command.operation() == NO_OPERATION) { + cerr << "Error: Unknown operation '" << optarg << "'" << endl; + exit(EXIT_FAILURE); + } + break; - case 'D': - command.set_operation(DETACH_ALL); - break; + case 'D': + command.set_operation(DETACH_ALL); + break; - case 'd': - command.set_operation(DELETE_IMAGE); - image_params = optarg; - break; + case 'd': + command.set_operation(DELETE_IMAGE); + image_params = optarg; + break; - case 'E': - filename = optarg; - if (filename.empty()) { - cerr << "Error: Missing filename" << endl; - exit(EXIT_FAILURE); - } - command.set_operation(IMAGE_FILE_INFO); - break; + case 'E': + command.set_operation(IMAGE_FILE_INFO); + filename = optarg; + break; - case 'e': - command.set_operation(DEFAULT_IMAGE_FILES_INFO); - if (optarg) { - SetCommandParams(command, optarg); - } - break; - - case 'F': - command.set_operation(DEFAULT_FOLDER); - default_folder = optarg; - break; - - case 'f': - param = optarg; - break; - - case 'h': - hostname = optarg; - if (hostname.empty()) { - cerr << "Error: Missing hostname" << endl; - exit(EXIT_FAILURE); - } - break; - - case 'B': - filename_binary = optarg; - if (filename_binary.empty()) { - cerr << "Error: Missing filename" << endl; - exit(EXIT_FAILURE); - } - break; - - case 'J': - filename_json = optarg; - if (filename_json.empty()) { - cerr << "Error: Missing filename" << endl; - exit(EXIT_FAILURE); - } - break; - - case 'Z': - filename_text = optarg; - if (filename_text.empty()) { - cerr << "Error: Missing filename" << endl; - exit(EXIT_FAILURE); - } - break; - - case 'I': - command.set_operation(RESERVED_IDS_INFO); - break; - - case 'L': - command.set_operation(LOG_LEVEL); - log_level = optarg; - break; - - case 'l': - list = true; - break; - - case 'm': - command.set_operation(MAPPING_INFO); - break; - - case 'N': - command.set_operation(NETWORK_INTERFACES_INFO); - break; - - case 'O': - command.set_operation(LOG_LEVEL_INFO); - break; - - case 'o': - command.set_operation(OPERATION_INFO); - break; - - case 't': - device->set_type(parser.ParseType(optarg)); - if (device->type() == UNDEFINED) { - cerr << "Error: Unknown device type '" << optarg << "'" << endl; - exit(EXIT_FAILURE); - } - break; - - case 'r': - command.set_operation(RESERVE_IDS); - reserved_ids = optarg; - break; - - case 'R': - command.set_operation(RENAME_IMAGE); - image_params = optarg; - break; - - case 'n': - SetProductData(*device, optarg); - break; - - case 'p': - if (!GetAsUnsignedInt(optarg, port) || port <= 0 || port > 65535) { - cerr << "Error: Invalid port " << optarg << ", port must be between 1 and 65535" << endl; - exit(EXIT_FAILURE); - } - break; - - case 's': - command.set_operation(SERVER_INFO); - if (optarg) { - if (const string error = SetCommandParams(command, optarg); !error.empty()) { - cerr << "Error: " << error << endl; - exit(EXIT_FAILURE); + case 'e': + command.set_operation(DEFAULT_IMAGE_FILES_INFO); + if (optarg) { + SetCommandParams(command, optarg); } - } - break; + break; - case 'S': - command.set_operation(STATISTICS_INFO); - break; + case 'F': + command.set_operation(DEFAULT_FOLDER); + default_folder = optarg; + break; - case 'v': - cout << "scsictl version: " << piscsi_get_version_string() << '\n'; - exit(EXIT_SUCCESS); - break; + case 'f': + param = optarg; + break; - case 'P': - token = optarg ? optarg : getpass("Password: "); - break; + case 'h': + hostname = optarg; + break; - case 'V': - command.set_operation(VERSION_INFO); - break; + case 'I': + command.set_operation(RESERVED_IDS_INFO); + break; - case 'x': - command.set_operation(COPY_IMAGE); - image_params = optarg; - break; + case 'L': + command.set_operation(LOG_LEVEL); + log_level = optarg; + break; - case 'T': - command.set_operation(DEVICE_TYPES_INFO); - break; + case 'l': + list = true; + break; - case 'X': - command.set_operation(SHUT_DOWN); - SetParam(command, "mode", "rascsi"); - break; + case 'm': + command.set_operation(MAPPING_INFO); + break; - case 'z': - locale = optarg; - break; + case 'N': + command.set_operation(NETWORK_INTERFACES_INFO); + break; - default: - break; - } - } + case 'O': + command.set_operation(LOG_LEVEL_INFO); + break; + + case 'o': + command.set_operation(OPERATION_INFO); + break; + + case 't': + device->set_type(parser.ParseType(optarg)); + if (device->type() == UNDEFINED) { + cerr << "Error: Unknown device type '" << optarg << "'" << endl; + exit(EXIT_FAILURE); + } + break; + + case 'r': + command.set_operation(RESERVE_IDS); + reserved_ids = optarg; + break; + + case 'R': + command.set_operation(RENAME_IMAGE); + image_params = optarg; + break; + + case 'n': + SetProductData(*device, optarg); + break; + + case 'p': + if (!GetAsUnsignedInt(optarg, port) || port <= 0 || port > 65535) { + cerr << "Error: Invalid port " << optarg << ", port must be between 1 and 65535" << endl; + exit(EXIT_FAILURE); + } + break; + + case 's': + command.set_operation(SERVER_INFO); + if (optarg) { + if (const string error = SetCommandParams(command, optarg); !error.empty()) { + cerr << "Error: " << error << endl; + exit(EXIT_FAILURE); + } + } + break; + + case 'S': + command.set_operation(STATISTICS_INFO); + break; + + case 'v': + cout << "scsictl version: " << piscsi_get_version_string() << '\n'; + exit(EXIT_SUCCESS); + break; + + case 'P': + token = optarg ? optarg : getpass("Password: "); + break; + + case 'V': + command.set_operation(VERSION_INFO); + break; + + case 'x': + command.set_operation(COPY_IMAGE); + image_params = optarg; + break; + + case 'T': + command.set_operation(DEVICE_TYPES_INFO); + break; + + case 'X': + command.set_operation(SHUT_DOWN); + SetParam(command, "mode", "rascsi"); + break; + + case 'z': + locale = optarg; + break; + + default: + break; + } + } // For macos only 'optind != argc' appears to work, but then non-argument options do not reject arguments if (optopt) { exit(EXIT_FAILURE); } - if (!filename_json.empty()) { - return ExportAsJson(command, filename_json); - } - if (!filename_binary.empty()) { - return ExportAsBinary(command, filename_binary); - } - if (!filename_text.empty()) { - return ExportAsText(command, filename_text); - } - - SetParam(command, "token", token); - SetParam(command, "locale", locale); + SetParam(command, "token", token); + SetParam(command, "locale", locale); ScsictlCommands scsictl_commands(command, hostname, port); @@ -328,7 +277,7 @@ int ScsiCtl::run(const vector& args) const else { ParseParameters(*device, param); - status = scsictl_commands.Execute(log_level, default_folder, reserved_ids, image_params, filename); + status = scsictl_commands.Execute(log_level, default_folder, reserved_ids, image_params, filename); } } catch(const io_exception& e) { @@ -341,48 +290,3 @@ int ScsiCtl::run(const vector& args) const return status ? EXIT_SUCCESS : EXIT_FAILURE; } - -int ScsiCtl::ExportAsBinary(const PbCommand &command, const string &filename) const -{ - const string binary = command.SerializeAsString(); - - ofstream out; - out.open(filename, ios::binary); - out << binary; - if (out.fail()) { - cerr << "Error: Can't create protobuf binary file '" << filename << "'" << endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; -} - -int ScsiCtl::ExportAsJson(const PbCommand &command, const string &filename) const -{ - string json; - MessageToJsonString(command, &json); - - ofstream out(filename); - out << json; - if (out.fail()) { - cerr << "Error: Can't create protobuf JSON file '" << filename << "'" << endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; -} - -int ScsiCtl::ExportAsText(const PbCommand &command, const string &filename) const -{ - string text; - TextFormat::PrintToString(command, &text); - - ofstream out(filename); - out << text; - if (out.fail()) { - cerr << "Error: Can't create protobuf text format file '" << filename << "'" << endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; -} diff --git a/cpp/scsictl/scsictl_core.h b/cpp/scsictl/scsictl_core.h index 4b8025ec..a96076f4 100644 --- a/cpp/scsictl/scsictl_core.h +++ b/cpp/scsictl/scsictl_core.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022 Uwe Seimet // //--------------------------------------------------------------------------- @@ -28,8 +28,4 @@ class ScsiCtl private: void Banner(const vector&) const; - - int ExportAsBinary(const PbCommand&, const string&) const; - int ExportAsJson(const PbCommand&, const string&) const; - int ExportAsText(const PbCommand&, const string&) const; }; diff --git a/doc/scsictl.1 b/doc/scsictl.1 index 7737e939..3df74fba 100644 --- a/doc/scsictl.1 +++ b/doc/scsictl.1 @@ -17,10 +17,6 @@ scsictl \- Sends management commands to the piscsi process \fB\-T\fR | \fB\-V\fR | \fB\-X\fR | -\fB\-Z\fR | -[\fB\-d\fR \fIFILENAME\fR] | -[\fB\-B\fR \fIFILENAME\fR] | -[\fB\-J\fR \fIFILENAME\fR] | [\fB\-C\fR \fIFILENAME:FILESIZE\fR] | [\fB\-E\fR \fIFILENAME\fR] | [\fB\-F\fR \fIIMAGE_FOLDER\fR] | @@ -121,15 +117,6 @@ Shut down the piscsi process. .BR \-d\fI " "\fIFILENAME Delete an image file in the default image folder. .TP -.BR \-B\fI " "\fIFILENAME -Do not send command to piscsi but write it to a protobuf binary file. -.TP -.BR \-J\fI " "\fIFILENAME -Do not send command to piscsi but write it to a protobuf JSON file. -.TP -.BR \-Z\fI " "\fIFILENAME -Do not send command to piscsi but write it to a protobuf text format file. -.TP .BR \-x\fI " "\fICURRENT_NAME:NEW_NAME Copy an image file in the default image folder. .TP diff --git a/doc/scsictl_man_page.txt b/doc/scsictl_man_page.txt index 7dfc6e10..f98b7208 100644 --- a/doc/scsictl_man_page.txt +++ b/doc/scsictl_man_page.txt @@ -7,23 +7,22 @@ NAME SYNOPSIS scsictl -e | -l | -m | -o | -v | -D | -I | -L | -O | -P | -S | -T | -V - | -X | -Z | [-d FILENAME] | [-B FILENAME] | [-J FILENAME] | [-C FILE‐ - NAME: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] | [-s - [FOLDER_PATTERN:FILE_PATTERN:OPERATIONS]] | [-t TYPE] | [-x CUR‐ + | -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. scsictl also runs on + 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 + Note: The command and type arguments are case insensitive. Only the first letter of the command/type is evaluated by the tool. OPTIONS @@ -42,7 +41,7 @@ OPTIONS -I Gets the list of reserved device IDs. -L LOG_LEVEL - Set the piscsi log level (trace, debug, info, warning, error, + Set the piscsi log level (trace, debug, info, warning, error, off). -h HOST @@ -50,19 +49,19 @@ OPTIONS -e List all images files in the default image folder. - -N Lists all available network interfaces provided that they are + -N Lists all available network interfaces provided that they are up. - -O Display the available piscsi server log levels and the current + -O Display the available piscsi server log levels and the current log level. - -P Prompt for the access token in case piscsi requires authentica‐ + -P Prompt for the access token in case piscsi requires authentica‐ tion. - -l List all of the devices that are currently being emulated by + -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 + -m List all file extensions recognized by PiSCSI and the device types they map to. -o Display operation meta data information. @@ -74,11 +73,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 + Comma-separated list of IDs to reserve. Pass an empty list in order to not reserve anything. -s [FOLDER_PATTERN:FILE_PATTERN:OPERATIONS] - Display server-side settings like available images or supported + Display server-side settings like available images or supported device types. -S Display statistics. @@ -94,18 +93,6 @@ OPTIONS -d FILENAME Delete an image file in the default image folder. - -B FILENAME - Do not send command to piscsi but write it to a protobuf binary - file. - - -J FILENAME - Do not send command to piscsi but write it to a protobuf JSON - file. - - -Z FILENAME - Do not send command to piscsi but write it to a protobuf text - format file. - -x CURRENT_NAME:NEW_NAME Copy an image file in the default image folder. From 0f352396be05823ef0ed8a387b4c92c7e14736e8 Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Mon, 27 Nov 2023 16:02:01 +0900 Subject: [PATCH 27/36] easyinstall.sh: fix typos in script syntax (#1391) * easyinstall.sh: fix typo in sudoCache() function name * Fix typo in apt-get command --- easyinstall.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/easyinstall.sh b/easyinstall.sh index ef852625..335cf1a2 100755 --- a/easyinstall.sh +++ b/easyinstall.sh @@ -92,7 +92,7 @@ function initialChecks() { } # Only to be used for pi-gen automated install -function cacheSudo() { +function sudoCache() { echo "Caching sudo password" echo raspberry | sudo -v -S } @@ -969,7 +969,7 @@ function installWebmin() { curl -o setup-repos.sh https://raw.githubusercontent.com/webmin/webmin/master/setup-repos.sh sudo sh setup-repos.sh -f rm setup-repos.sh - sudo apt-get install webmin --install-recommends --assume-yes Date: Thu, 7 Dec 2023 17:38:24 -0800 Subject: [PATCH 28/36] Web UI: Rework the Attach Device section to be universal (#1393) * Correct German translation for Key * Web UI: Rework the Attach Device section to be universal * Web UI: Warn when working dirs are missing * Refactor tests to use global endpoint constants * Add fallback for unknown disk type devices * Rearrange the index page sections * Move Macproxy help text to admins page * Remove image list exception for SCHD * Show Settings button when auth is diabled * Tweak CSS styles for both themes * Move Eject action next to the file name, and improve UI labels --- .../web/src/static/themes/classic/style.css | 9 + python/web/src/static/themes/modern/style.css | 26 +- python/web/src/templates/admin.html | 2 + python/web/src/templates/base.html | 50 +- python/web/src/templates/index.html | 429 ++++++++++-------- .../translations/de/LC_MESSAGES/messages.po | 2 +- python/web/src/web.py | 97 +--- python/web/src/web_utils.py | 15 +- python/web/tests/api/conftest.py | 43 +- python/web/tests/api/test_auth.py | 11 +- python/web/tests/api/test_devices.py | 49 +- python/web/tests/api/test_files.py | 60 ++- python/web/tests/api/test_misc.py | 29 +- python/web/tests/api/test_settings.py | 61 +-- 14 files changed, 455 insertions(+), 428 deletions(-) diff --git a/python/web/src/static/themes/classic/style.css b/python/web/src/static/themes/classic/style.css index d77de45a..de672365 100644 --- a/python/web/src/static/themes/classic/style.css +++ b/python/web/src/static/themes/classic/style.css @@ -58,6 +58,15 @@ div.footer div.theme-change-hint { margin-bottom: 15px; } +div.login-status { + text-align: right; +} + +div.login-status a { + color: white; + text-decoration: underline; +} + div.logged-in { background-color: green; } diff --git a/python/web/src/static/themes/modern/style.css b/python/web/src/static/themes/modern/style.css index ad993af2..4aa05505 100644 --- a/python/web/src/static/themes/modern/style.css +++ b/python/web/src/static/themes/modern/style.css @@ -282,18 +282,6 @@ div.header div.login-form-title { display: none; } -div.header div.authentication-disabled span.separator { - display: none; -} - -div.header div.authentication-disabled span.wiki-help-text { - display: block; -} - -div.header div.authentication-disabled a { - color: #fff; -} - @media (max-width: 900px) { div.header { flex-wrap: wrap; @@ -663,10 +651,11 @@ table#attached-devices td.actions { table#attached-devices td.parameters form { display: flex; + align-items: center; } table#attached-devices td.parameters form label { - display: none; + padding: 0 0.5rem 0 0; } table#attached-devices td.parameters form select { @@ -775,6 +764,11 @@ section#files p { margin-top: 1rem; } +section#files details.subdir { + padding-left: 1rem; + padding-right: 1rem; +} + section#files details.subdir summary.dirname { text-decoration: underline; font-family: monospace; @@ -864,7 +858,7 @@ section#upload a p { /* ------------------------------------------------------------------------------ - Index > Section: Attach peripheral devices + Index > Section: Attach devices ------------------------------------------------------------------------------ */ section#attach-devices table th:last-child, @@ -876,6 +870,10 @@ section#attach-devices form { display: block; } +section#attach-devices table form select.table-dropdown { + width: 16rem; +} + @media (max-width: 900px) { section#attach-devices table tr th:nth-child(2), section#attach-devices table tr td:nth-child(2) { diff --git a/python/web/src/templates/admin.html b/python/web/src/templates/admin.html index 61fd7a63..bb1b6d91 100644 --- a/python/web/src/templates/admin.html +++ b/python/web/src/templates/admin.html @@ -115,6 +115,8 @@
    • {{ _("If you want to add a service, run the easyinstall.sh script and choose the one to install.") }}
    • {{ _("In order to manage the services in the Web UI, you may install Webmin as well.") }}
    • +
    • {{ _("To browse the modern web, install a vintage web proxy such as Macproxy.", url="https://github.com/PiSCSI/piscsi/wiki/Vintage-Web-Proxy#macproxy") }} +
      diff --git a/python/web/src/templates/base.html b/python/web/src/templates/base.html index 0c555f72..5f317a92 100644 --- a/python/web/src/templates/base.html +++ b/python/web/src/templates/base.html @@ -27,37 +27,31 @@
      - {% if env["auth_active"] %} + {% if env["logged_in"] or not env["auth_active"] %} + {% else %} - + {% endif %}
      diff --git a/python/web/src/templates/index.html b/python/web/src/templates/index.html index 01e4967b..c8f78b01 100644 --- a/python/web/src/templates/index.html +++ b/python/web/src/templates/index.html @@ -24,6 +24,7 @@
    +{% if env["cfg_dir_exists"] %}

    @@ -54,6 +55,13 @@

    +{% else %} + +
    +{{ _("Please create the PiSCSI configuration dir to use configurations:")}} {{ CFG_DIR }} +
    + +{% endif %} @@ -82,13 +90,12 @@ {% endif %} @@ -135,13 +149,6 @@
    {{ device.device_name }} - {% if "No Media" in device.status %} + {% if "No Media" in device.status %}
    + - - - +
    {% else %} {% if device.params %} @@ -120,7 +127,14 @@ {% endif %} {% endfor %} {% elif device.file %} - {{ device.file }} +
    + + {% if device.device_type in REMOVABLE_DEVICE_TYPES and "No Media" not in device.status %} + + + + {% endif %} +
    {% endif %} {% endif %}
    {% if device.id in scsi_ids["occupied_ids"] %} - {% if device.device_type in REMOVABLE_DEVICE_TYPES and "No Media" not in device.status %} -
    - - - -
    - {% endif %}
    @@ -209,12 +216,8 @@ -{% if not files|length: %} -
    - {{ _("The images directory is currently empty.") }} -
    -{% else %} - +{% if env["image_dir_exists"] %} +{% if files|length %}
    {% for subdir, group in formatted_image_files.items() %} @@ -309,7 +312,6 @@ {% else %} - + + + + + + {% for type in device_types.keys() %} + + + + + + {% endfor %} +
    {{ _("Device") }}{{ _("Key") }}{{ _("Actions") }}
    + {% if device_types[type]["name"] == type %} + {% if type in REMOVABLE_DEVICE_TYPES %} +
    {{ _("Unknown Removable Disk Drive") }}
    + {% elif type in DISK_DEVICE_TYPES %} +
    {{ _("Unknown Fixed Disk Drive") }}
    + {% else %} +
    {{ _("Unknown Device") }}
    + {% endif %} + {% else %} +
    {{ device_types[type]["name"] }}
    + {% endif %} +
    +
    {{ type }}
    +
    + + + {% for key, value in device_types[type]["params"] | dictsort %} + + {% if value.isnumeric() %} + + {% elif key == "interface" %} + + {% else %} + + {% endif %} + {% endfor %} + {% if type in DISK_DEVICE_TYPES %} + + + + + {% endif %} + + + + + + +

    @@ -432,6 +596,69 @@
    +
    +
    + + {{ _("Create Empty Disk Image") }} + +
      +
    • {{ _("Please refer to wiki documentation to learn more about the supported image file types.", url="https://github.com/PiSCSI/piscsi/wiki/Supported-Device-Types#image-types") }}
    • +
    • {{ _("It is not recommended to use the Lido hard disk driver with the Macintosh Plus.") }}
    • +
    +
    + +
    + + + + + + + + + + + +
    +
    + +
    +

    {{ _("Create Disk Image With Properties") }}

    +
    + +
    +
    @@ -507,166 +734,4 @@
    -
    -
    - - {{ _("Create Empty Disk Image") }} - -
      -
    • {{ _("Please refer to wiki documentation to learn more about the supported image file types.", url="https://github.com/PiSCSI/piscsi/wiki/Supported-Device-Types#image-types") }}
    • -
    • {{ _("It is not recommended to use the Lido hard disk driver with the Macintosh Plus.") }}
    • -
    -
    - -
    - - - - - - - - - - - -
    -
    - -
    -

    {{ _("Create Disk Image With Properties") }}

    -
    - -
    - -
    -
    - - {{ _("Attach Peripheral Device") }} - -
      - - {% if bridge_configured %} -
    • {{ _("The piscsi_bridge network bridge is active and ready to be used by an emulated network adapter!") }}
    • - {% else %} -
    • {{ _("Please configure the piscsi_bridge network bridge before attaching an emulated network adapter!") }}
    • - {% endif %} -
    • {{ _("To browse the modern web, install a vintage web proxy such as Macproxy.", url="https://github.com/PiSCSI/piscsi/wiki/Vintage-Web-Proxy#macproxy") }}
    • - -
    • {{ _("Read more about supported device types on the wiki.", url="https://github.com/PiSCSI/piscsi/wiki/Supported-Device-Types") }} -
    • -
    -
    - - - - - - - {% for type in REMOVABLE_DEVICE_TYPES + PERIPHERAL_DEVICE_TYPES %} - - - - - - {% endfor %} -
    {{ _("Device") }}{{ _("Key") }}{{ _("Parameters and Actions") }}
    -
    {{ device_types[type]["name"] }}
    -
    -
    {{ type }}
    -
    -
    - - {% for key, value in device_types[type]["params"] | dictsort %} - - {% if value.isnumeric() %} - - {% elif key == "interface" %} - - {% else %} - - {% endif %} - {% endfor %} - {% if type in REMOVABLE_DEVICE_TYPES %} - - - {% endif %} - - - - - -
    -
    -
    - -
    {% endblock content %} diff --git a/python/web/src/translations/de/LC_MESSAGES/messages.po b/python/web/src/translations/de/LC_MESSAGES/messages.po index 9c6fabd7..6b2391cf 100644 --- a/python/web/src/translations/de/LC_MESSAGES/messages.po +++ b/python/web/src/translations/de/LC_MESSAGES/messages.po @@ -1309,7 +1309,7 @@ msgstr "" #: src/templates/index.html:594 msgid "Key" -msgstr "Taste" +msgstr "Kürzel" #: src/templates/index.html:595 msgid "Parameters and Actions" diff --git a/python/web/src/web.py b/python/web/src/web.py index fbed803f..10788101 100644 --- a/python/web/src/web.py +++ b/python/web/src/web.py @@ -46,7 +46,6 @@ from return_code_mapper import ReturnCodeMapper from socket_cmds_flask import SocketCmdsFlask from web_utils import ( - working_dirs_exist, sort_and_format_devices, get_valid_scsi_ids, map_device_types_and_names, @@ -125,6 +124,9 @@ def get_env_info(): "image_dir": server_info["image_dir"], "image_root_dir": Path(server_info["image_dir"]).name, "shared_root_dir": Path(FILE_SERVER_DIR).name, + "image_dir_exists": Path(server_info["image_dir"]).exists(), + "cfg_dir_exists": Path(CFG_DIR).exists(), + "hd_suffixes": tuple(server_info["schd"]), "cd_suffixes": tuple(server_info["sccd"]), "rm_suffixes": tuple(server_info["scrm"]), "mo_suffixes": tuple(server_info["scmo"]), @@ -219,7 +221,6 @@ def index(): Sets up data structures for and renders the index page """ server_info = piscsi_cmd.get_server_info() - working_dirs_exist((server_info["image_dir"], CFG_DIR)) devices = piscsi_cmd.list_devices() device_types = map_device_types_and_names(piscsi_cmd.get_device_types()["device_types"]) @@ -304,9 +305,6 @@ def drive_list(): """ Sets up the data structures and kicks off the rendering of the drive list page """ - server_info = piscsi_cmd.get_server_info() - working_dirs_exist((server_info["image_dir"], CFG_DIR)) - return response( template="drives.html", page_title=_("PiSCSI Create Drive"), @@ -342,7 +340,6 @@ def upload_page(): Sets up the data structures and kicks off the rendering of the file uploading page """ server_info = piscsi_cmd.get_server_info() - working_dirs_exist((server_info["image_dir"], CFG_DIR)) return response( template="upload.html", @@ -544,7 +541,6 @@ def show_diskinfo(): if not safe_path["status"]: return response(error=True, message=safe_path["msg"]) server_info = piscsi_cmd.get_server_info() - working_dirs_exist((server_info["image_dir"], CFG_DIR)) returncode, diskinfo = sys_cmd.get_diskinfo(Path(server_info["image_dir"]) / file_name) if returncode == 0: return response( @@ -647,16 +643,17 @@ def log_level(): return response(error=True, message=process["msg"]) -@APP.route("/scsi/attach_device", methods=["POST"]) +@APP.route("/scsi/attach", methods=["POST"]) @login_required def attach_device(): """ - Attaches a peripheral device that doesn't take an image file as argument + Attaches device of any type """ scsi_id = request.form.get("scsi_id") unit = request.form.get("unit") device_type = request.form.get("type") drive_name = request.form.get("drive_name") + file_name = request.form.get("file_name") if not scsi_id: return response(error=True, message=_("No SCSI ID specified")) @@ -690,11 +687,29 @@ def attach_device(): "device_type": device_type, "params": params, } + + if file_name: + kwargs["params"]["file"] = file_name + + # If drive_props is defined use properies from this dict, + # otherwise fall back to the properties file if it exists if drive_props: kwargs["vendor"] = drive_props["vendor"] kwargs["product"] = drive_props["product"] kwargs["revision"] = drive_props["revision"] kwargs["block_size"] = drive_props["block_size"] + else: + drive_properties = Path(CFG_DIR) / f"{file_name}.{PROPERTIES_SUFFIX}" + if drive_properties.is_file(): + process = file_cmd.read_drive_properties(drive_properties) + process = ReturnCodeMapper.add_msg(process) + if not process["status"]: + return response(error=True, message=process["msg"]) + conf = process["conf"] + kwargs["vendor"] = conf["vendor"] + kwargs["product"] = conf["product"] + kwargs["revision"] = conf["revision"] + kwargs["block_size"] = conf["block_size"] process = piscsi_cmd.attach_device(scsi_id, **kwargs) process = ReturnCodeMapper.add_msg(process) @@ -711,70 +726,6 @@ def attach_device(): return response(error=True, message=process["msg"]) -@APP.route("/scsi/attach", methods=["POST"]) -@login_required -def attach_image(): - """ - Attaches a file image as a device - """ - file_name = request.form.get("file_name") - file_size = request.form.get("file_size") - scsi_id = request.form.get("scsi_id") - unit = request.form.get("unit") - device_type = request.form.get("type") - - if not scsi_id: - return response(error=True, message=_("No SCSI ID specified")) - if not file_name: - return response(error=True, message=_("No image file to insert")) - - kwargs = {"unit": int(unit), "params": {"file": file_name}} - - if device_type: - kwargs["device_type"] = device_type - device_types = piscsi_cmd.get_device_types() - expected_block_size = min(device_types["device_types"][device_type]["block_sizes"]) - - # Attempt to load the device properties file: - # same file name with PROPERTIES_SUFFIX appended - drive_properties = Path(CFG_DIR) / f"{file_name}.{PROPERTIES_SUFFIX}" - if drive_properties.is_file(): - process = file_cmd.read_drive_properties(drive_properties) - process = ReturnCodeMapper.add_msg(process) - if not process["status"]: - return response(error=True, message=process["msg"]) - conf = process["conf"] - kwargs["vendor"] = conf["vendor"] - kwargs["product"] = conf["product"] - kwargs["revision"] = conf["revision"] - kwargs["block_size"] = conf["block_size"] - expected_block_size = conf["block_size"] - - process = piscsi_cmd.attach_device(scsi_id, **kwargs) - process = ReturnCodeMapper.add_msg(process) - if process["status"]: - if int(file_size) % int(expected_block_size): - logging.warning( - "The image file size %s bytes is not a multiple of %s. " - "PiSCSI will ignore the trailing data. " - "The image may be corrupted, so proceed with caution.", - file_size, - expected_block_size, - ) - return response( - message=_( - "Attached %(file_name)s as %(device_type)s to " - "SCSI ID %(id_number)s LUN %(unit_number)s", - file_name=file_name, - device_type=get_device_name(device_type), - id_number=scsi_id, - unit_number=unit, - ) - ) - - return response(error=True, message=process["msg"]) - - @APP.route("/scsi/detach_all", methods=["POST"]) @login_required def detach_all_devices(): diff --git a/python/web/src/web_utils.py b/python/web/src/web_utils.py index bc779836..9b22e45b 100644 --- a/python/web/src/web_utils.py +++ b/python/web/src/web_utils.py @@ -8,26 +8,13 @@ from pathlib import Path from ua_parser import user_agent_parser from re import findall -from flask import request, abort +from flask import request from flask_babel import _ from werkzeug.utils import secure_filename from piscsi.sys_cmds import SysCmds -def working_dirs_exist(working_dirs): - """ - Method for validating that working dirs exist. - Takes (tuple) of (str) working_dirs with paths to required dirs. - """ - for dir_path in working_dirs: - if not Path(dir_path).exists(): - abort( - 503, - _(f"Please create directory: {dir_path}"), - ) - - def get_valid_scsi_ids(devices, reserved_ids): """ Takes a list of (dict)s devices, and list of (int)s reserved_ids. diff --git a/python/web/tests/api/conftest.py b/python/web/tests/api/conftest.py index a254b1a8..56116f87 100644 --- a/python/web/tests/api/conftest.py +++ b/python/web/tests/api/conftest.py @@ -8,6 +8,41 @@ FILE_SIZE_1_MIB = 1048576 STATUS_SUCCESS = "success" STATUS_ERROR = "error" +ENV_ENDPOINT = "/env" +HEALTHCHECK_ENDPOINT = "/healthcheck" +PWA_FAVICON_ENDPOINT = "/pwa/favicon.ico" +LOGIN_ENDPOINT = "/login" +LOGOUT_ENDPOINT = "/logout" +ATTACH_ENDPOINT = "/scsi/attach" +DETACH_ENDPOINT = "/scsi/detach" +DETACH_ALL_ENDPOINT = "/scsi/detach_all" +EJECT_ENDPOINT = "/scsi/eject" +RESERVE_ENDPOINT = "/scsi/reserve" +RELEASE_ENDPOINT = "/scsi/release" +INFO_ENDPOINT = "/scsi/info" +CREATE_ENDPOINT = "/files/create" +RENAME_ENDPOINT = "/files/rename" +COPY_ENDPOINT = "/files/copy" +DELETE_ENDPOINT = "/files/delete" +DOWNLOAD_URL_ENDPOINT = "/files/download_url" +DOWNLOAD_IMAGE_ENDPOINT = "/files/download_image" +DOWNLOAD_CONFIG_ENDPOINT = "/files/download_config" +EXTRACT_IMAGE_ENDPOINT = "/files/extract_image" +UPLOAD_ENDPOINT = "/files/upload" +CREATE_ISO_ENDPOINT = "/files/create_iso" +DISKINFO_ENDPOINT = "/files/diskinfo" +DRIVE_LIST_ENDPOINT = "/drive/list" +DRIVE_CREATE_ENDPOINT = "/drive/create" +DRIVE_CDROM_ENDPOINT = "/drive/cdrom" +MANPAGE_ENDPOINT = "/sys/manpage?app=piscsi" +LANGUAGE_ENDPOINT = "/language" +LOG_LEVEL_ENDPOINT = "/logs/level" +LOG_SHOW_ENDPOINT = "/logs/show" +CONFIG_SAVE_ENDPOINT = "/config/save" +CONFIG_ACTION_ENDPOINT = "/config/action" +THEME_ENDPOINT = "/theme" +SYS_RENAME_ENDPOINT = "/sys/rename" + @pytest.fixture(scope="function") def create_test_image(request, http_client): @@ -18,7 +53,7 @@ def create_test_image(request, http_client): file_name = f"{file_prefix}.{image_type}" response = http_client.post( - "/files/create", + CREATE_ENDPOINT, data={ "file_name": file_prefix, "type": image_type, @@ -42,7 +77,7 @@ def create_test_image(request, http_client): def delete(): for image in images: - response = http_client.post("/files/delete", data={"file_name": image["file_name"]}) + response = http_client.post(DELETE_ENDPOINT, data={"file_name": image["file_name"]}) if response.status_code != 200 or response.json()["status"] != STATUS_SUCCESS: warnings.warn( f"Failed to auto-delete file created with create_test_image fixture: {image}" @@ -71,7 +106,7 @@ def list_attached_images(http_client): @pytest.fixture(scope="function") def delete_file(http_client): def delete(file_name): - response = http_client.post("/files/delete", data={"file_name": file_name}) + response = http_client.post(DELETE_ENDPOINT, data={"file_name": file_name}) if response.status_code != 200 or response.json()["status"] != STATUS_SUCCESS: warnings.warn(f"Failed to delete file via delete_file fixture: {file_name}") @@ -81,7 +116,7 @@ def delete_file(http_client): @pytest.fixture(scope="function") def detach_devices(http_client): def detach(): - response = http_client.post("/scsi/detach_all") + response = http_client.post(DETACH_ALL_ENDPOINT) if response.json()["status"] == STATUS_SUCCESS: return True raise Exception("Failed to detach SCSI devices") diff --git a/python/web/tests/api/test_auth.py b/python/web/tests/api/test_auth.py index 1261aa58..404df4b6 100644 --- a/python/web/tests/api/test_auth.py +++ b/python/web/tests/api/test_auth.py @@ -1,11 +1,10 @@ -from conftest import STATUS_SUCCESS, STATUS_ERROR +from conftest import STATUS_SUCCESS, STATUS_ERROR, LOGIN_ENDPOINT, LOGOUT_ENDPOINT -# route("/login", methods=["POST"]) def test_login_with_valid_credentials(pytestconfig, http_client_unauthenticated): # Note: This test depends on the piscsi group existing and 'username' a member the group response = http_client_unauthenticated.post( - "/login", + LOGIN_ENDPOINT, data={ "username": pytestconfig.getoption("piscsi_username"), "password": pytestconfig.getoption("piscsi_password"), @@ -19,10 +18,9 @@ def test_login_with_valid_credentials(pytestconfig, http_client_unauthenticated) assert "env" in response_data["data"] -# route("/login", methods=["POST"]) def test_login_with_invalid_credentials(http_client_unauthenticated): response = http_client_unauthenticated.post( - "/login", + LOGIN_ENDPOINT, data={ "username": "__INVALID_USER__", "password": "__INVALID_PASS__", @@ -38,7 +36,6 @@ def test_login_with_invalid_credentials(http_client_unauthenticated): ) -# route("/logout") def test_logout(http_client): - response = http_client.get("/logout") + response = http_client.get(LOGOUT_ENDPOINT) assert response.status_code == 200 diff --git a/python/web/tests/api/test_devices.py b/python/web/tests/api/test_devices.py index f13c6887..1b39686a 100644 --- a/python/web/tests/api/test_devices.py +++ b/python/web/tests/api/test_devices.py @@ -2,20 +2,24 @@ import pytest from conftest import ( SCSI_ID, - FILE_SIZE_1_MIB, STATUS_SUCCESS, + ATTACH_ENDPOINT, + DETACH_ENDPOINT, + DETACH_ALL_ENDPOINT, + EJECT_ENDPOINT, + RESERVE_ENDPOINT, + RELEASE_ENDPOINT, + INFO_ENDPOINT, ) -# route("/scsi/attach", methods=["POST"]) -def test_attach_image(http_client, create_test_image, detach_devices): +def test_attach_device_with_image(http_client, create_test_image, detach_devices): test_image = create_test_image() response = http_client.post( - "/scsi/attach", + ATTACH_ENDPOINT, data={ "file_name": test_image, - "file_size": FILE_SIZE_1_MIB, "scsi_id": SCSI_ID, "unit": 0, "type": "SCHD", @@ -26,14 +30,13 @@ def test_attach_image(http_client, create_test_image, detach_devices): assert response.status_code == 200 assert response_data["status"] == STATUS_SUCCESS assert response_data["messages"][0]["message"] == ( - f"Attached {test_image} as Hard Disk Drive to SCSI ID {SCSI_ID} LUN 0" + f"Attached Hard Disk Drive to SCSI ID {SCSI_ID} LUN 0" ) # Cleanup detach_devices() -# route("/scsi/attach_device", methods=["POST"]) @pytest.mark.parametrize( "device_name,device_config", [ @@ -89,7 +92,7 @@ def test_attach_device(env, http_client, detach_devices, device_name, device_con device_config["unit"] = 0 response = http_client.post( - "/scsi/attach_device", + ATTACH_ENDPOINT, data=device_config, ) @@ -105,15 +108,13 @@ def test_attach_device(env, http_client, detach_devices, device_name, device_con detach_devices() -# route("/scsi/detach", methods=["POST"]) def test_detach_device(http_client, create_test_image): test_image = create_test_image() http_client.post( - "/scsi/attach", + ATTACH_ENDPOINT, data={ "file_name": test_image, - "file_size": FILE_SIZE_1_MIB, "scsi_id": SCSI_ID, "unit": 0, "type": "SCHD", @@ -121,7 +122,7 @@ def test_detach_device(http_client, create_test_image): ) response = http_client.post( - "/scsi/detach", + DETACH_ENDPOINT, data={ "scsi_id": SCSI_ID, "unit": 0, @@ -135,7 +136,6 @@ def test_detach_device(http_client, create_test_image): assert response_data["messages"][0]["message"] == f"Detached SCSI ID {SCSI_ID} LUN 0" -# route("/scsi/detach_all", methods=["POST"]) def test_detach_all_devices(http_client, create_test_image, list_attached_images): test_images = [] scsi_ids = [4, 5, 6] @@ -145,10 +145,9 @@ def test_detach_all_devices(http_client, create_test_image, list_attached_images test_images.append(test_image) http_client.post( - "/scsi/attach", + ATTACH_ENDPOINT, data={ "file_name": test_image, - "file_size": FILE_SIZE_1_MIB, "scsi_id": scsi_id, "unit": 0, "type": "SCHD", @@ -157,7 +156,7 @@ def test_detach_all_devices(http_client, create_test_image, list_attached_images assert list_attached_images() == test_images - response = http_client.post("/scsi/detach_all") + response = http_client.post(DETACH_ALL_ENDPOINT) response_data = response.json() assert response.status_code == 200 @@ -165,15 +164,13 @@ def test_detach_all_devices(http_client, create_test_image, list_attached_images assert list_attached_images() == [] -# route("/scsi/eject", methods=["POST"]) def test_eject_device(http_client, create_test_image, detach_devices): test_image = create_test_image() http_client.post( - "/scsi/attach", + ATTACH_ENDPOINT, data={ "file_name": test_image, - "file_size": FILE_SIZE_1_MIB, "scsi_id": SCSI_ID, "unit": 0, "type": "SCCD", # CD-ROM @@ -181,7 +178,7 @@ def test_eject_device(http_client, create_test_image, detach_devices): ) response = http_client.post( - "/scsi/eject", + EJECT_ENDPOINT, data={ "scsi_id": SCSI_ID, "unit": 0, @@ -198,15 +195,13 @@ def test_eject_device(http_client, create_test_image, detach_devices): detach_devices() -# route("/scsi/info", methods=["POST"]) def test_show_device_info(http_client, create_test_image, detach_devices): test_image = create_test_image() http_client.post( - "/scsi/attach", + ATTACH_ENDPOINT, data={ "file_name": test_image, - "file_size": FILE_SIZE_1_MIB, "scsi_id": SCSI_ID, "unit": 0, "type": "SCHD", @@ -214,7 +209,7 @@ def test_show_device_info(http_client, create_test_image, detach_devices): ) response = http_client.post( - "/scsi/info", + INFO_ENDPOINT, ) response_data = response.json() @@ -228,13 +223,11 @@ def test_show_device_info(http_client, create_test_image, detach_devices): detach_devices() -# route("/scsi/reserve", methods=["POST"]) -# route("/scsi/release", methods=["POST"]) def test_reserve_and_release_device(http_client): scsi_id = 0 response = http_client.post( - "/scsi/reserve", + RESERVE_ENDPOINT, data={ "scsi_id": scsi_id, "memo": "TEST", @@ -248,7 +241,7 @@ def test_reserve_and_release_device(http_client): assert response_data["messages"][0]["message"] == f"Reserved SCSI ID {scsi_id}" response = http_client.post( - "/scsi/release", + RELEASE_ENDPOINT, data={ "scsi_id": scsi_id, }, diff --git a/python/web/tests/api/test_files.py b/python/web/tests/api/test_files.py index 984cc4ec..a1838c65 100644 --- a/python/web/tests/api/test_files.py +++ b/python/web/tests/api/test_files.py @@ -5,16 +5,26 @@ import os from conftest import ( FILE_SIZE_1_MIB, STATUS_SUCCESS, + CREATE_ENDPOINT, + RENAME_ENDPOINT, + COPY_ENDPOINT, + DELETE_ENDPOINT, + DOWNLOAD_URL_ENDPOINT, + DOWNLOAD_IMAGE_ENDPOINT, + DOWNLOAD_CONFIG_ENDPOINT, + EXTRACT_IMAGE_ENDPOINT, + UPLOAD_ENDPOINT, + CREATE_ISO_ENDPOINT, + DISKINFO_ENDPOINT, ) -# route("/files/create", methods=["POST"]) def test_create_file(http_client, list_files, delete_file): file_prefix = str(uuid.uuid4()) file_name = f"{file_prefix}.hds" response = http_client.post( - "/files/create", + CREATE_ENDPOINT, data={ "file_name": file_prefix, "type": "hds", @@ -34,13 +44,12 @@ def test_create_file(http_client, list_files, delete_file): delete_file(file_name) -# route("/files/create", methods=["POST"]) def test_create_file_with_properties(http_client, list_files, delete_file): file_prefix = str(uuid.uuid4()) file_name = f"{file_prefix}.hds" response = http_client.post( - "/files/create", + CREATE_ENDPOINT, data={ "file_name": file_prefix, "type": "hds", @@ -64,13 +73,12 @@ def test_create_file_with_properties(http_client, list_files, delete_file): delete_file(file_name) -# route("/files/create", methods=["POST"]) def test_create_file_and_format_hfs(http_client, list_files, delete_file): file_prefix = str(uuid.uuid4()) file_name = f"{file_prefix}.hda" response = http_client.post( - "/files/create", + CREATE_ENDPOINT, data={ "file_name": file_prefix, "type": "hda", @@ -91,7 +99,6 @@ def test_create_file_and_format_hfs(http_client, list_files, delete_file): delete_file(file_name) -# route("/files/create", methods=["POST"]) def test_create_file_and_format_fat(env, http_client, list_files, delete_file): if env["is_docker"]: pytest.skip("Test not supported in Docker environment.") @@ -99,7 +106,7 @@ def test_create_file_and_format_fat(env, http_client, list_files, delete_file): file_name = f"{file_prefix}.hdr" response = http_client.post( - "/files/create", + CREATE_ENDPOINT, data={ "file_name": file_prefix, "type": "hdr", @@ -120,13 +127,12 @@ def test_create_file_and_format_fat(env, http_client, list_files, delete_file): delete_file(file_name) -# route("/files/rename", methods=["POST"]) def test_rename_file(http_client, create_test_image, list_files, delete_file): original_file = create_test_image(auto_delete=False) renamed_file = f"{uuid.uuid4()}.rename" response = http_client.post( - "/files/rename", + RENAME_ENDPOINT, data={"file_name": original_file, "new_file_name": renamed_file}, ) @@ -141,13 +147,12 @@ def test_rename_file(http_client, create_test_image, list_files, delete_file): delete_file(renamed_file) -# route("/files/copy", methods=["POST"]) def test_copy_file(http_client, create_test_image, list_files, delete_file): original_file = create_test_image() copy_file = f"{uuid.uuid4()}.copy" response = http_client.post( - "/files/copy", + COPY_ENDPOINT, data={ "file_name": original_file, "copy_file_name": copy_file, @@ -167,11 +172,10 @@ def test_copy_file(http_client, create_test_image, list_files, delete_file): delete_file(copy_file) -# route("/files/delete", methods=["POST"]) def test_delete_file(http_client, create_test_image, list_files): file_name = create_test_image(auto_delete=False) - response = http_client.post("/files/delete", data={"file_name": file_name}) + response = http_client.post(DELETE_ENDPOINT, data={"file_name": file_name}) response_data = response.json() @@ -181,7 +185,6 @@ def test_delete_file(http_client, create_test_image, list_files): assert file_name not in list_files() -# route("/files/extract_image", methods=["POST"]) @pytest.mark.parametrize( "archive_file_name,image_file_name", [ @@ -205,7 +208,7 @@ def test_extract_file( ) http_client.post( - "/files/download_url", + DOWNLOAD_URL_ENDPOINT, data={ "destination": "disk_images", "images_subdir": "", @@ -214,7 +217,7 @@ def test_extract_file( ) response = http_client.post( - "/files/extract_image", + EXTRACT_IMAGE_ENDPOINT, data={ "archive_file": archive_file_name, "archive_members": image_file_name, @@ -233,7 +236,6 @@ def test_extract_file( delete_file(image_file_name) -# route("/files/upload", methods=["POST"]) def test_upload_file(http_client, delete_file): file_name = f"{uuid.uuid4()}.test" @@ -267,7 +269,7 @@ def test_upload_file(http_client, delete_file): file_data = {"file": (file_name, file.read(chunk_size))} response = http_client.post( - "/files/upload", + UPLOAD_ENDPOINT, data=form_data, files=file_data, ) @@ -283,11 +285,10 @@ def test_upload_file(http_client, delete_file): delete_file(file_name) -# route("/files/download_image", methods=["POST"]) def test_download_image(http_client, create_test_image): file_name = create_test_image() - response = http_client.post("/files/download_image", data={"file": file_name}) + response = http_client.post(DOWNLOAD_IMAGE_ENDPOINT, data={"file": file_name}) assert response.status_code == 200 assert response.headers["content-type"] == "application/octet-stream" @@ -295,13 +296,12 @@ def test_download_image(http_client, create_test_image): assert response.headers["content-length"] == str(FILE_SIZE_1_MIB) -# route("/files/download_config", methods=["POST"]) def test_download_properties(http_client, list_files, delete_file): file_prefix = str(uuid.uuid4()) file_name = f"{file_prefix}.hds" response = http_client.post( - "/files/create", + CREATE_ENDPOINT, data={ "file_name": file_prefix, "type": "hds", @@ -321,7 +321,7 @@ def test_download_properties(http_client, list_files, delete_file): ) assert file_name in list_files() - response = http_client.post("/files/download_config", data={"file": f"{file_name}.properties"}) + response = http_client.post(DOWNLOAD_CONFIG_ENDPOINT, data={"file": f"{file_name}.properties"}) assert response.status_code == 200 assert response.headers["content-type"] == "application/octet-stream" @@ -331,7 +331,6 @@ def test_download_properties(http_client, list_files, delete_file): delete_file(file_name) -# route("/files/download_url", methods=["POST"]) def test_download_url_to_dir(env, httpserver, http_client, list_files, delete_file): file_name = str(uuid.uuid4()) http_path = f"/images/{file_name}" @@ -347,7 +346,7 @@ def test_download_url_to_dir(env, httpserver, http_client, list_files, delete_fi ) response = http_client.post( - "/files/download_url", + DOWNLOAD_URL_ENDPOINT, data={ "destination": "disk_images", "images_subdir": subdir, @@ -369,7 +368,6 @@ def test_download_url_to_dir(env, httpserver, http_client, list_files, delete_fi delete_file(file_name) -# route("/files/create_iso", methods=["POST"]) def test_create_iso_from_url( httpserver, http_client, @@ -392,7 +390,7 @@ def test_create_iso_from_url( ) response = http_client.post( - "/files/create_iso", + CREATE_ISO_ENDPOINT, data={ "type": ISO_TYPE, "url": url, @@ -414,7 +412,6 @@ def test_create_iso_from_url( delete_file(iso_file_name) -# route("/files/create_iso", methods=["POST"]) def test_create_iso_from_local_file( http_client, create_test_image, @@ -427,7 +424,7 @@ def test_create_iso_from_local_file( ISO_TYPE = "HFS" response = http_client.post( - "/files/create_iso", + CREATE_ISO_ENDPOINT, data={ "type": ISO_TYPE, "file": test_file_name, @@ -449,12 +446,11 @@ def test_create_iso_from_local_file( delete_file(iso_file_name) -# route("/files/diskinfo", methods=["POST"]) def test_show_diskinfo(http_client, create_test_image): test_image = create_test_image() response = http_client.post( - "/files/diskinfo", + DISKINFO_ENDPOINT, data={ "file_name": test_image, }, diff --git a/python/web/tests/api/test_misc.py b/python/web/tests/api/test_misc.py index 727ffa0e..5d0ef0e7 100644 --- a/python/web/tests/api/test_misc.py +++ b/python/web/tests/api/test_misc.py @@ -3,10 +3,16 @@ import uuid from conftest import ( FILE_SIZE_1_MIB, STATUS_SUCCESS, + ENV_ENDPOINT, + PWA_FAVICON_ENDPOINT, + HEALTHCHECK_ENDPOINT, + DRIVE_LIST_ENDPOINT, + DRIVE_CREATE_ENDPOINT, + DRIVE_CDROM_ENDPOINT, + MANPAGE_ENDPOINT, ) -# route("/") def test_index(http_client): response = http_client.get("/") response_data = response.json() @@ -16,9 +22,8 @@ def test_index(http_client): assert "devices" in response_data["data"] -# route("/env") def test_get_env_info(http_client): - response = http_client.get("/env") + response = http_client.get(ENV_ENDPOINT) response_data = response.json() assert response.status_code == 200 @@ -26,17 +31,15 @@ def test_get_env_info(http_client): assert "running_env" in response_data["data"] -# route("/pwa/") def test_pwa_route(http_client): - response = http_client.get("/pwa/favicon.ico") + response = http_client.get(PWA_FAVICON_ENDPOINT) assert response.status_code == 200 assert response.headers["content-disposition"] == "inline; filename=favicon.ico" -# route("/drive/list", methods=["GET"]) def test_show_named_drive_presets(http_client): - response = http_client.get("/drive/list") + response = http_client.get(DRIVE_LIST_ENDPOINT) response_data = response.json() prev_drive = {"name": ""} @@ -57,12 +60,11 @@ def test_show_named_drive_presets(http_client): assert "files" in response_data["data"] -# route("/drive/cdrom", methods=["POST"]) def test_create_cdrom_properties_file(env, http_client): file_name = f"{uuid.uuid4()}.iso" response = http_client.post( - "/drive/cdrom", + DRIVE_CDROM_ENDPOINT, data={ "drive_name": "Sony CDU-8012", "file_name": file_name, @@ -78,13 +80,12 @@ def test_create_cdrom_properties_file(env, http_client): ) -# route("/drive/create", methods=["POST"]) def test_create_image_with_properties_file(http_client, delete_file): file_prefix = str(uuid.uuid4()) file_name = f"{file_prefix}.hds" response = http_client.post( - "/drive/create", + DRIVE_CREATE_ENDPOINT, data={ "drive_name": "Miniscribe M8425", "size": FILE_SIZE_1_MIB, @@ -105,16 +106,14 @@ def test_create_image_with_properties_file(http_client, delete_file): delete_file(file_name) -# route("/sys/manpage", methods=["POST"]) def test_show_manpage(http_client): - response = http_client.get("/sys/manpage?app=piscsi") + response = http_client.get(MANPAGE_ENDPOINT) response_data = response.json() assert response.status_code == 200 assert "piscsi" in response_data["data"]["manpage"] -# route("/healthcheck", methods=["GET"]) def test_healthcheck(http_client): - response = http_client.get("/healthcheck") + response = http_client.get(HEALTHCHECK_ENDPOINT) assert response.status_code == 200 diff --git a/python/web/tests/api/test_settings.py b/python/web/tests/api/test_settings.py index 3875fd9e..94da4a51 100644 --- a/python/web/tests/api/test_settings.py +++ b/python/web/tests/api/test_settings.py @@ -1,10 +1,20 @@ import pytest import uuid -from conftest import STATUS_SUCCESS +from conftest import ( + STATUS_SUCCESS, + ENV_ENDPOINT, + LANGUAGE_ENDPOINT, + LOG_LEVEL_ENDPOINT, + LOG_SHOW_ENDPOINT, + CONFIG_SAVE_ENDPOINT, + CONFIG_ACTION_ENDPOINT, + THEME_ENDPOINT, + SYS_RENAME_ENDPOINT, + RESERVE_ENDPOINT, +) -# route("/language", methods=["POST"]) @pytest.mark.parametrize( "locale,confirm_message", [ @@ -18,7 +28,7 @@ from conftest import STATUS_SUCCESS ) def test_set_language(http_client, locale, confirm_message): response = http_client.post( - "/language", + LANGUAGE_ENDPOINT, data={ "locale": locale, }, @@ -31,11 +41,10 @@ def test_set_language(http_client, locale, confirm_message): assert response_data["messages"][0]["message"] == confirm_message -# route("/logs/level", methods=["POST"]) @pytest.mark.parametrize("level", ["trace", "debug", "info", "warn", "err", "off"]) def test_set_log_level(http_client, level): response = http_client.post( - "/logs/level", + LOG_LEVEL_ENDPOINT, data={ "level": level, }, @@ -49,17 +58,16 @@ def test_set_log_level(http_client, level): # Cleanup http_client.post( - "/logs/level", + LOG_LEVEL_ENDPOINT, data={ "level": "debug", }, ) -# route("/logs/show", methods=["POST"]) def test_show_logs(http_client): response = http_client.post( - "/logs/show", + LOG_SHOW_ENDPOINT, data={ "lines": 100, "scope": "piscsi", @@ -73,8 +81,6 @@ def test_show_logs(http_client): assert response_data["data"]["scope"] == "piscsi" -# route("/config/save", methods=["POST"]) -# route("/config/action", methods=["POST"]) def test_save_load_and_delete_configs(env, http_client): config_name = str(uuid.uuid4()) config_json_file = f"{config_name}.json" @@ -86,7 +92,7 @@ def test_save_load_and_delete_configs(env, http_client): # Save the initial state to a config response = http_client.post( - "/config/save", + CONFIG_SAVE_ENDPOINT, data={ "name": config_name, }, @@ -104,7 +110,7 @@ def test_save_load_and_delete_configs(env, http_client): # Modify the state http_client.post( - "/scsi/reserve", + RESERVE_ENDPOINT, data={ "scsi_id": reserved_scsi_id, "memo": reservation_memo, @@ -115,7 +121,7 @@ def test_save_load_and_delete_configs(env, http_client): # Load the saved config response = http_client.post( - "/config/action", + CONFIG_ACTION_ENDPOINT, data={ "name": config_json_file, "load": True, @@ -135,7 +141,7 @@ def test_save_load_and_delete_configs(env, http_client): # Delete the saved config response = http_client.post( - "/config/action", + CONFIG_ACTION_ENDPOINT, data={ "name": config_json_file, "delete": True, @@ -153,15 +159,13 @@ def test_save_load_and_delete_configs(env, http_client): assert config_json_file not in http_client.get("/").json()["data"]["config_files"] -# route("/config/save", methods=["POST"]) -# route("/config/action", methods=["POST"]) -def test_download_configs(env, http_client, delete_file): +def test_download_configs(env, http_client): config_name = str(uuid.uuid4()) config_json_file = f"{config_name}.json" # Save the initial state to a config response = http_client.post( - "/config/save", + CONFIG_SAVE_ENDPOINT, data={ "name": config_name, }, @@ -172,7 +176,7 @@ def test_download_configs(env, http_client, delete_file): # Download the saved config response = http_client.post( - "/config/action", + CONFIG_ACTION_ENDPOINT, data={ "name": config_json_file, "send": True, @@ -185,7 +189,7 @@ def test_download_configs(env, http_client, delete_file): # Delete the saved config response = http_client.post( - "/config/action", + CONFIG_ACTION_ENDPOINT, data={ "name": config_json_file, "delete": True, @@ -196,7 +200,6 @@ def test_download_configs(env, http_client, delete_file): assert config_json_file not in http_client.get("/").json()["data"]["config_files"] -# route("/theme", methods=["POST"]) @pytest.mark.parametrize( "theme", [ @@ -206,7 +209,7 @@ def test_download_configs(env, http_client, delete_file): ) def test_set_theme(http_client, theme): response = http_client.post( - "/theme", + THEME_ENDPOINT, data={ "name": theme, }, @@ -219,7 +222,6 @@ def test_set_theme(http_client, theme): assert response_data["messages"][0]["message"] == f"Theme changed to '{theme}'." -# route("/theme", methods=["GET"]) @pytest.mark.parametrize( "theme", [ @@ -229,7 +231,7 @@ def test_set_theme(http_client, theme): ) def test_set_theme_via_query_string(http_client, theme): response = http_client.get( - "/theme", + THEME_ENDPOINT, params={ "name": theme, }, @@ -242,17 +244,16 @@ def test_set_theme_via_query_string(http_client, theme): assert response_data["messages"][0]["message"] == f"Theme changed to '{theme}'." -# route("/sys/rename", methods=["POST"]) def test_rename_system(env, http_client): new_name = "SYSTEM NAME TEST" - response = http_client.get("/env") + response = http_client.get(ENV_ENDPOINT) response_data = response.json() old_name = response_data["data"]["system_name"] response = http_client.post( - "/sys/rename", + SYS_RENAME_ENDPOINT, data={ "system_name": new_name, }, @@ -264,13 +265,13 @@ def test_rename_system(env, http_client): assert response_data["status"] == STATUS_SUCCESS assert response_data["messages"][0]["message"] == f"System name changed to '{new_name}'." - response = http_client.get("/env") + response = http_client.get(ENV_ENDPOINT) response_data = response.json() assert response_data["data"]["system_name"] == new_name response = http_client.post( - "/sys/rename", + SYS_RENAME_ENDPOINT, data={ "system_name": old_name, }, @@ -282,7 +283,7 @@ def test_rename_system(env, http_client): assert response_data["status"] == STATUS_SUCCESS assert response_data["messages"][0]["message"] == f"System name changed to '{old_name}'." - response = http_client.get("/env") + response = http_client.get(ENV_ENDPOINT) response_data = response.json() assert response_data["data"]["system_name"] == old_name From 7268084819eece3868efb86ba9a80ac5562389bf Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Sun, 10 Dec 2023 22:56:35 -0800 Subject: [PATCH 29/36] Control board client regression fixes (#1394) * Control board client should use new FileCmds initiator * Restore informative logging, but drop to debug level * Use the correct object to call list_images() --- .../ctrlboard_menu_update_event_handler.py | 7 ++++--- python/ctrlboard/src/ctrlboard_menu_builder.py | 11 +++-------- python/ctrlboard/src/menu/cycler.py | 2 +- 3 files changed, 8 insertions(+), 12 deletions(-) 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 768e996f..45e53ce5 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 @@ -106,8 +106,9 @@ class CtrlBoardMenuUpdateEventHandler(Observer): handler_function(info_object) except AttributeError: log = logging.getLogger(__name__) - log.error( - "Handler function not found or returned an error. Skipping.", + log.debug( + "Handler function [%s] not found or returned an error. Skipping.", + str(handler_function_name), ) # noinspection PyUnusedLocal @@ -194,7 +195,7 @@ class CtrlBoardMenuUpdateEventHandler(Observer): """Method handles the rotary button press in the profile selection menu for selecting a profile to load.""" if info_object is not None and "name" in info_object: - file_cmd = FileCmds(sock_cmd=self.sock_cmd, piscsi=self.piscsi_cmd) + file_cmd = FileCmds(piscsi=self.piscsi_cmd) result = file_cmd.read_config(file_name=info_object["name"]) if result["status"] is True: self._menu_controller.show_message("Profile loaded!") diff --git a/python/ctrlboard/src/ctrlboard_menu_builder.py b/python/ctrlboard/src/ctrlboard_menu_builder.py index 3f322131..afdcb7f9 100644 --- a/python/ctrlboard/src/ctrlboard_menu_builder.py +++ b/python/ctrlboard/src/ctrlboard_menu_builder.py @@ -28,12 +28,7 @@ class CtrlBoardMenuBuilder(MenuBuilder): def __init__(self, piscsi_cmd: PiscsiCmds): super().__init__() self._piscsi_client = piscsi_cmd - self.file_cmd = FileCmds( - sock_cmd=piscsi_cmd.sock_cmd, - piscsi=piscsi_cmd, - token=piscsi_cmd.token, - locale=piscsi_cmd.locale, - ) + self.file_cmd = FileCmds(piscsi=piscsi_cmd) def build(self, name: str, context_object=None) -> Menu: if name == CtrlBoardMenuBuilder.SCSI_ID_MENU: @@ -48,7 +43,7 @@ class CtrlBoardMenuBuilder(MenuBuilder): return self.create_device_info_menu(context_object) log = logging.getLogger(__name__) - log.error("Provided menu name cannot be built!") + log.debug("Provided menu name [%s] cannot be built!", name) return self.create_scsi_id_list_menu(context_object) @@ -142,7 +137,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.piscsi_cmd.list_images() + images_info = self._piscsi_client.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/ctrlboard/src/menu/cycler.py b/python/ctrlboard/src/menu/cycler.py index e3144af7..aa785084 100644 --- a/python/ctrlboard/src/menu/cycler.py +++ b/python/ctrlboard/src/menu/cycler.py @@ -23,7 +23,7 @@ class Cycler: self._menu_controller = menu_controller self.sock_cmd = sock_cmd self.piscsi_cmd = piscsi_cmd - self.file_cmd = FileCmds(sock_cmd=self.sock_cmd, piscsi=self.piscsi_cmd) + self.file_cmd = FileCmds(piscsi=self.piscsi_cmd) self.cycle_entries = self.populate_cycle_entries() self.return_string = return_string self.return_entry = return_entry From 6d88932b851af023d17db38e3128d082ddadf242 Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Sun, 10 Dec 2023 21:36:19 +0900 Subject: [PATCH 30/36] Web UI: Bump dropzone library to v6.0.0-beta2 --- python/web/src/templates/upload.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/python/web/src/templates/upload.html b/python/web/src/templates/upload.html index 2a3b9b3e..823abfbb 100644 --- a/python/web/src/templates/upload.html +++ b/python/web/src/templates/upload.html @@ -41,8 +41,8 @@ @@ -78,6 +78,7 @@ b: "{{ _("B") }}" } } + Dropzone.discover()