Merge branch 'develop' into dependabot/pip/python/web/jinja2-3.1.3

This commit is contained in:
Daniel Markstedt 2024-01-13 16:00:49 +09:00 committed by GitHub
commit 83f4feaa21
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
91 changed files with 1483 additions and 1022 deletions

View File

@ -35,7 +35,7 @@ jobs:
run: make -j $(nproc) test run: make -j $(nproc) test
- name: Run unit tests - 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 - name: Upload logs
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3

View File

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

3
cpp/.gitignore vendored
View File

@ -12,6 +12,3 @@ obj
bin bin
coverage coverage
generated generated
.project
.cproject
.settings

View File

@ -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
}
]
},
]
}

View File

@ -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
}
}
]
}

View File

@ -60,8 +60,8 @@ COVERAGE_DIR = coverage
COVERAGE_FILE = piscsi.dat COVERAGE_FILE = piscsi.dat
OS_FILES = ../os_integration OS_FILES = ../os_integration
OBJDIR := obj/$(shell echo $(CONNECT_TYPE) | tr '[:upper:]' '[:lower:]') OBJDIR := obj
BINDIR := bin/$(shell echo $(CONNECT_TYPE) | tr '[:upper:]' '[:lower:]') BINDIR := bin
BIN_ALL = \ BIN_ALL = \
$(BINDIR)/$(PISCSI) \ $(BINDIR)/$(PISCSI) \

View File

@ -865,6 +865,10 @@ bool ScsiController::XferOutBlockOriented(bool cont)
LogTrace("Done with DaynaPort Set Multicast Address"); LogTrace("Done with DaynaPort Set Multicast Address");
break; break;
case scsi_command::eCmdSetIfaceMode:
LogTrace("Done with setting DaynaPort MAC address (ignore)");
break;
default: default:
stringstream s; stringstream s;
s << "Received an unexpected command ($" << setfill('0') << setw(2) << hex s << "Received an unexpected command ($" << setfill('0') << setw(2) << hex

View File

@ -7,7 +7,7 @@
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "shared/network_util.h" #include "shared/piscsi_util.h"
#include "scsihd.h" #include "scsihd.h"
#include "scsihd_nec.h" #include "scsihd_nec.h"
#include "scsimo.h" #include "scsimo.h"
@ -20,39 +20,14 @@
using namespace std; using namespace std;
using namespace piscsi_util; 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 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; 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; return it->second;
} }
@ -75,8 +50,7 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(PbDeviceType type, int lun
if (const string ext = GetExtensionLowerCase(filename); ext == "hdn" || ext == "hdi" || ext == "nhd") { if (const string ext = GetExtensionLowerCase(filename); ext == "hdn" || ext == "hdi" || ext == "nhd") {
device = make_shared<SCSIHD_NEC>(lun); device = make_shared<SCSIHD_NEC>(lun);
} else { } else {
device = make_shared<SCSIHD>(lun, sector_sizes.find(type)->second, false, device = make_shared<SCSIHD>(lun, false, ext == "hd1" ? scsi_level::scsi_1_ccs : scsi_level::scsi_2);
ext == "hd1" ? scsi_level::scsi_1_ccs : scsi_level::scsi_2);
// Some Apple tools require a particular drive identification // Some Apple tools require a particular drive identification
if (ext == "hda") { if (ext == "hda") {
@ -88,17 +62,17 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(PbDeviceType type, int lun
} }
case SCRM: case SCRM:
device = make_shared<SCSIHD>(lun, sector_sizes.find(type)->second, true); device = make_shared<SCSIHD>(lun, true, scsi_level::scsi_2);
device->SetProduct("SCSI HD (REM.)"); device->SetProduct("SCSI HD (REM.)");
break; break;
case SCMO: case SCMO:
device = make_shared<SCSIMO>(lun, sector_sizes.find(type)->second); device = make_shared<SCSIMO>(lun);
device->SetProduct("SCSI MO"); device->SetProduct("SCSI MO");
break; break;
case SCCD: case SCCD:
device = make_shared<SCSICD>(lun, sector_sizes.find(type)->second, device = make_shared<SCSICD>(lun,
GetExtensionLowerCase(filename) == "is1" ? scsi_level::scsi_1_ccs : scsi_level::scsi_2); GetExtensionLowerCase(filename) == "is1" ? scsi_level::scsi_1_ccs : scsi_level::scsi_2);
device->SetProduct("SCSI CD-ROM"); device->SetProduct("SCSI CD-ROM");
break; break;
@ -135,15 +109,3 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(PbDeviceType type, int lun
return device; return device;
} }
// TODO Move to respective device, which may require changes in the SCSI_HD/SCSIHD_NEC inheritance hierarchy
unordered_set<uint32_t> DeviceFactory::GetSectorSizes(PbDeviceType type) const
{
const auto& it = sector_sizes.find(type);
if (it != sector_sizes.end()) {
return it->second;
}
else {
return {};
}
}

View File

@ -11,9 +11,7 @@
#pragma once #pragma once
#include "shared/piscsi_util.h"
#include <string> #include <string>
#include <unordered_set>
#include <unordered_map> #include <unordered_map>
#include "generated/piscsi_interface.pb.h" #include "generated/piscsi_interface.pb.h"
@ -27,19 +25,32 @@ class DeviceFactory
public: public:
DeviceFactory(); DeviceFactory() = default;
~DeviceFactory() = default; ~DeviceFactory() = default;
shared_ptr<PrimaryDevice> CreateDevice(PbDeviceType, int, const string&) const; shared_ptr<PrimaryDevice> CreateDevice(PbDeviceType, int, const string&) const;
PbDeviceType GetTypeForFile(const string&) const; PbDeviceType GetTypeForFile(const string&) const;
unordered_set<uint32_t> GetSectorSizes(PbDeviceType type) const; const auto& GetExtensionMapping() const { return EXTENSION_MAPPING; }
const auto& GetExtensionMapping() const { return extension_mapping; }
private: private:
unordered_map<PbDeviceType, unordered_set<uint32_t>> sector_sizes; const inline static unordered_map<string, PbDeviceType, piscsi_util::StringHash, equal_to<>> EXTENSION_MAPPING = {
{ "hd1", SCHD },
{ "hds", SCHD },
{ "hda", SCHD },
{ "hdn", SCHD },
{ "hdi", SCHD },
{ "nhd", SCHD },
{ "hdr", SCRM },
{ "mos", SCMO },
{ "is1", SCCD },
{ "iso", SCCD }
};
unordered_map<string, PbDeviceType, piscsi_util::StringHash, equal_to<>> extension_mapping; const inline static unordered_map<string, PbDeviceType, piscsi_util::StringHash, equal_to<>> DEVICE_MAPPING = {
{ "bridge", SCBR },
unordered_map<string, PbDeviceType, piscsi_util::StringHash, equal_to<>> device_mapping; { "daynaport", SCDP },
{ "printer", SCLP },
{ "services", SCHS }
};
}; };

View File

@ -39,16 +39,14 @@ void DeviceLogger::Error(const string& message) const
void DeviceLogger::Log(level::level_enum level, const string& message) const void DeviceLogger::Log(level::level_enum level, const string& message) const
{ {
if (!message.empty() && if ((log_device_id == -1 || log_device_id == id) && (lun == -1 || log_device_lun == -1 || log_device_lun == lun)) {
(log_device_id == -1 || if (lun == -1) {
(log_device_id == id && (log_device_lun == -1 || log_device_lun == lun)))) { log(level, "(ID " + to_string(id) + ") - " + message);
if (lun == -1) { }
log(level, "(ID " + to_string(id) + ") - " + message); else {
} log(level, "(ID:LUN " + to_string(id) + ":" + to_string(lun) + ") - " + message);
else { }
log(level, "(ID:LUN " + to_string(id) + ":" + to_string(lun) + ") - " + message); }
}
}
} }
void DeviceLogger::SetIdAndLun(int i, int l) void DeviceLogger::SetIdAndLun(int i, int l)

View File

@ -695,12 +695,18 @@ uint32_t Disk::GetSectorSizeInBytes() const
void Disk::SetSectorSizeInBytes(uint32_t size_in_bytes) 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)"); throw io_exception("Invalid sector size of " + to_string(size_in_bytes) + " byte(s)");
} }
uint64_t current_blocks = GetBlockCount();
uint32_t current_size_shift_count = size_shift_count;
uint64_t current_size = current_blocks << current_size_shift_count;
size_shift_count = CalculateShiftCount(size_in_bytes); size_shift_count = CalculateShiftCount(size_in_bytes);
assert(size_shift_count); assert(size_shift_count);
if ((current_blocks > 0) && (current_size_shift_count > 0)) {
SetBlockCount(current_size >> size_shift_count);
}
} }
uint32_t Disk::GetConfiguredSectorSize() const uint32_t Disk::GetConfiguredSectorSize() const
@ -708,13 +714,13 @@ uint32_t Disk::GetConfiguredSectorSize() const
return configured_sector_size; 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; return false;
} }
configured_sector_size = configured_size; configured_sector_size = configured_size;
return true; return true;
} }

View File

@ -16,7 +16,6 @@
#include "shared/scsi.h" #include "shared/scsi.h"
#include "shared/piscsi_util.h" #include "shared/piscsi_util.h"
#include "device_factory.h"
#include "disk_track.h" #include "disk_track.h"
#include "disk_cache.h" #include "disk_cache.h"
#include "interfaces/scsi_block_commands.h" #include "interfaces/scsi_block_commands.h"
@ -35,8 +34,7 @@ class Disk : public StorageDevice, private ScsiBlockCommands
unique_ptr<DiskCache> cache; unique_ptr<DiskCache> cache;
// The supported configurable sector sizes, empty if not configurable unordered_set<uint32_t> supported_sector_sizes;
unordered_set<uint32_t> sector_sizes;
uint32_t configured_sector_size = 0; uint32_t configured_sector_size = 0;
// Sector size shift count (9=512, 10=1024, 11=2048, 12=4096) // Sector size shift count (9=512, 10=1024, 11=2048, 12=4096)
@ -50,7 +48,9 @@ class Disk : public StorageDevice, private ScsiBlockCommands
public: public:
using StorageDevice::StorageDevice; Disk(PbDeviceType type, int lun, const unordered_set<uint32_t>& s)
: StorageDevice(type, lun), supported_sector_sizes(s) {}
~Disk() override = default;
bool Init(const param_map&) override; bool Init(const param_map&) override;
void CleanUp() override; void CleanUp() override;
@ -64,8 +64,9 @@ public:
virtual int Read(span<uint8_t> , uint64_t); virtual int Read(span<uint8_t> , uint64_t);
uint32_t GetSectorSizeInBytes() const; uint32_t GetSectorSizeInBytes() const;
bool IsSectorSizeConfigurable() const { return !sector_sizes.empty(); } bool IsSectorSizeConfigurable() const { return supported_sector_sizes.size() > 1; }
bool SetConfiguredSectorSize(const DeviceFactory&, uint32_t); const auto& GetSupportedSectorSizes() const { return supported_sector_sizes; }
bool SetConfiguredSectorSize(uint32_t);
void FlushCache() override; void FlushCache() override;
vector<PbStatistics> GetStatistics() const override; vector<PbStatistics> GetStatistics() const override;
@ -111,7 +112,7 @@ protected:
void SetUpCache(off_t, bool = false); void SetUpCache(off_t, bool = false);
void ResizeCache(const string&, bool); void ResizeCache(const string&, bool);
bool GetRawMode() const { return (cache?cache->GetRawMode():false); }
void SetUpModePages(map<int, vector<byte>>&, int, bool) const override; void SetUpModePages(map<int, vector<byte>>&, int, bool) const override;
void AddErrorPage(map<int, vector<byte>>&, bool) const; void AddErrorPage(map<int, vector<byte>>&, bool) const;
virtual void AddFormatPage(map<int, vector<byte>>&, bool) const; virtual void AddFormatPage(map<int, vector<byte>>&, bool) const;
@ -119,7 +120,6 @@ protected:
void AddCachePage(map<int, vector<byte>>&, bool) const; void AddCachePage(map<int, vector<byte>>&, bool) const;
unordered_set<uint32_t> GetSectorSizes() const; unordered_set<uint32_t> GetSectorSizes() const;
void SetSectorSizes(const unordered_set<uint32_t>& sizes) { sector_sizes = sizes; }
void SetSectorSizeInBytes(uint32_t); void SetSectorSizeInBytes(uint32_t);
uint32_t GetSectorSizeShiftCount() const { return size_shift_count; } uint32_t GetSectorSizeShiftCount() const { return size_shift_count; }
void SetSectorSizeShiftCount(uint32_t count) { size_shift_count = count; } void SetSectorSizeShiftCount(uint32_t count) { size_shift_count = count; }

View File

@ -51,6 +51,7 @@ public:
~DiskCache() = default; ~DiskCache() = default;
void SetRawMode(bool b) { cd_raw = b; } // CD-ROM raw mode setting void SetRawMode(bool b) { cd_raw = b; } // CD-ROM raw mode setting
bool GetRawMode() const { return cd_raw; }
bool Save(); // Save and release all bool Save(); // Save and release all
bool ReadSector(span<uint8_t>, uint32_t); // Sector Read bool ReadSector(span<uint8_t>, uint32_t); // Sector Read

View File

@ -114,7 +114,7 @@ void ModePageDevice::ModeSense10() const
EnterDataInPhase(); EnterDataInPhase();
} }
void ModePageDevice::ModeSelect(scsi_command, cdb_t, span<const uint8_t>, int) const void ModePageDevice::ModeSelect(scsi_command, cdb_t, span<const uint8_t>, int)
{ {
// There is no default implementation of MODE SELECT // There is no default implementation of MODE SELECT
throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code); throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code);

View File

@ -23,7 +23,7 @@ public:
bool Init(const param_map&) override; bool Init(const param_map&) override;
virtual void ModeSelect(scsi_defs::scsi_command, cdb_t, span<const uint8_t>, int) const; virtual void ModeSelect(scsi_defs::scsi_command, cdb_t, span<const uint8_t>, int);
protected: protected:

View File

@ -40,7 +40,8 @@ string scsi_command_util::ModeSelect(scsi_command cmd, cdb_t cdb, span<const uin
} }
length -= offset; length -= offset;
bool has_valid_page_code = false; // treat zero length as valid
bool has_valid_page_code = (length == 0);
// Parse the pages // Parse the pages
while (length > 0) { while (length > 0) {
@ -62,6 +63,10 @@ string scsi_command_util::ModeSelect(scsi_command cmd, cdb_t cdb, span<const uin
has_valid_page_code = true; has_valid_page_code = true;
} }
else if (page == 0x01) {
// OpenVMS Alpha 7.3 uses this
has_valid_page_code = true;
}
else { else {
stringstream s; stringstream s;
s << "Unknown MODE SELECT page code: $" << setfill('0') << setw(2) << hex << page; s << "Unknown MODE SELECT page code: $" << setfill('0') << setw(2) << hex << page;
@ -71,7 +76,7 @@ string scsi_command_util::ModeSelect(scsi_command cmd, cdb_t cdb, span<const uin
// Advance to the next page // Advance to the next page
const int size = buf[offset + 1] + 2; const int size = buf[offset + 1] + 2;
length -= size; length -= size + 1;
offset += size; offset += size;
} }

View File

@ -6,6 +6,7 @@
// Copyright (C) 2020 akuker // Copyright (C) 2020 akuker
// Copyright (C) 2014-2020 GIMONS // Copyright (C) 2014-2020 GIMONS
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp) // Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2023 Uwe Seimet
// //
// Licensed under the BSD 3-Clause License. // Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder. // See LICENSE file in the project root folder.
@ -212,13 +213,15 @@ int SCSIDaynaPort::Read(cdb_t cdb, vector<uint8_t>& buf, uint64_t)
// response->flags = e_no_more_data; // response->flags = e_no_more_data;
// } // }
int size = rx_packet_size; int size = rx_packet_size;
if (size < 64) { if (size < 128) {
// A frame must have at least 64 bytes (see https://github.com/PiSCSI/piscsi/issues/619) // A frame must have at least 64 bytes for the Atari driver, see https://github.com/PiSCSI/piscsi/issues/619,
// Note that this work-around breaks the checksum. As currently there are no known drivers // but also works with 128 bytes.
// that care for the checksum, and the Daynaport driver for the Atari expects frames of // The NetBSD driver requires at least 128 bytes, see https://github.com/PiSCSI/piscsi/issues/1098.
// 64 bytes it was decided to accept the broken checksum. If a driver should pop up that // The Mac driver is also fine with 128 bytes.
// breaks because of this, the work-around has to be re-evaluated. // Note that this work-around breaks the checksum. As currently there are no known drivers
size = 64; // 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); SetInt16(buf, 0, size);
SetInt32(buf, 2, tap.HasPendingPackets() ? 0x10 : 0x00); SetInt32(buf, 2, tap.HasPendingPackets() ? 0x10 : 0x00);

View File

@ -6,6 +6,7 @@
// Copyright (C) 2020 akuker // Copyright (C) 2020 akuker
// Copyright (C) 2014-2020 GIMONS // Copyright (C) 2014-2020 GIMONS
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp) // Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2023 Uwe Seimet
// //
// Licensed under the BSD 3-Clause License. // Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder. // See LICENSE file in the project root folder.

View File

@ -197,7 +197,7 @@ bool SCSIPrinter::WriteByteSequence(span<const uint8_t> buf)
out.write((const char *)buf.data(), buf.size()); out.write((const char *)buf.data(), buf.size());
const bool status = out.fail(); const bool status = out.fail();
if (!status) { if (status) {
++print_error_count; ++print_error_count;
} }

View File

@ -21,11 +21,8 @@
using namespace scsi_defs; using namespace scsi_defs;
using namespace scsi_command_util; using namespace scsi_command_util;
SCSICD::SCSICD(int lun, const unordered_set<uint32_t>& sector_sizes, scsi_defs::scsi_level level) SCSICD::SCSICD(int lun, scsi_defs::scsi_level level) : Disk(SCCD, lun, { 512, 2048 }), scsi_level(level)
: Disk(SCCD, lun), scsi_level(level)
{ {
SetSectorSizes(sector_sizes);
SetReadOnly(true); SetReadOnly(true);
SetRemovable(true); SetRemovable(true);
SetLockable(true); SetLockable(true);
@ -168,6 +165,29 @@ vector<uint8_t> SCSICD::InquiryInternal() const
return HandleInquiry(device_type::cd_rom, scsi_level, true); return HandleInquiry(device_type::cd_rom, scsi_level, true);
} }
void SCSICD::ModeSelect(scsi_command cmd, cdb_t cdb, span<const uint8_t> buf, int length)
{
int sector_size = 1 << GetSectorSizeShiftCount();
int wanted_sector_size;
// skip Block Descriptor
int offset = 4;
// evaluate Mode Parameter Block Descriptor, sector size
wanted_sector_size = scsi_command_util::GetInt16(buf, offset + 6);
if (wanted_sector_size != sector_size) {
LogDebug("Changing sector size from " + to_string(sector_size) + " to " + to_string(wanted_sector_size));
SetSectorSizeInBytes(wanted_sector_size);
ClearTrack();
CreateDataTrack();
FlushCache();
ResizeCache(GetFilename(), GetRawMode());
}
if (const string result = scsi_command_util::ModeSelect(cmd, cdb, buf, length, sector_size);
!result.empty()) {
LogWarn(result);
}
}
void SCSICD::SetUpModePages(map<int, vector<byte>>& pages, int page, bool changeable) const void SCSICD::SetUpModePages(map<int, vector<byte>>& pages, int page, bool changeable) const
{ {
Disk::SetUpModePages(pages, page, changeable); Disk::SetUpModePages(pages, page, changeable);

View File

@ -25,7 +25,7 @@ class SCSICD : public Disk, private ScsiMmcCommands
{ {
public: public:
SCSICD(int, const unordered_set<uint32_t>&, scsi_defs::scsi_level = scsi_level::scsi_2); SCSICD(int, scsi_defs::scsi_level = scsi_level::scsi_2);
~SCSICD() override = default; ~SCSICD() override = default;
bool Init(const param_map&) override; bool Init(const param_map&) override;
@ -34,6 +34,7 @@ public:
vector<uint8_t> InquiryInternal() const override; vector<uint8_t> InquiryInternal() const override;
int Read(span<uint8_t>, uint64_t) override; int Read(span<uint8_t>, uint64_t) override;
void ModeSelect(scsi_defs::scsi_command, cdb_t, span<const uint8_t>, int) override;
protected: protected:

View File

@ -19,11 +19,9 @@
using namespace scsi_command_util; using namespace scsi_command_util;
SCSIHD::SCSIHD(int lun, const unordered_set<uint32_t>& sector_sizes, bool removable, scsi_defs::scsi_level level) SCSIHD::SCSIHD(int lun, bool removable, scsi_defs::scsi_level level, const unordered_set<uint32_t>& sector_sizes)
: Disk(removable ? SCRM : SCHD, lun), scsi_level(level) : Disk(removable ? SCRM : SCHD, lun, sector_sizes), scsi_level(level)
{ {
SetSectorSizes(sector_sizes);
SetProtectable(true); SetProtectable(true);
SetRemovable(removable); SetRemovable(removable);
SetLockable(removable); SetLockable(removable);
@ -84,7 +82,7 @@ vector<uint8_t> SCSIHD::InquiryInternal() const
return HandleInquiry(device_type::direct_access, scsi_level, IsRemovable()); return HandleInquiry(device_type::direct_access, scsi_level, IsRemovable());
} }
void SCSIHD::ModeSelect(scsi_command cmd, cdb_t cdb, span<const uint8_t> buf, int length) const void SCSIHD::ModeSelect(scsi_command cmd, cdb_t cdb, span<const uint8_t> buf, int length)
{ {
if (const string result = scsi_command_util::ModeSelect(cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount()); if (const string result = scsi_command_util::ModeSelect(cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount());
!result.empty()) { !result.empty()) {

View File

@ -28,7 +28,7 @@ class SCSIHD : public Disk
public: public:
SCSIHD(int, const unordered_set<uint32_t>&, bool, scsi_defs::scsi_level = scsi_level::scsi_2); SCSIHD(int, bool, scsi_defs::scsi_level, const unordered_set<uint32_t>& = { 512, 1024, 2048, 4096 });
~SCSIHD() override = default; ~SCSIHD() override = default;
void FinalizeSetup(off_t); void FinalizeSetup(off_t);
@ -37,7 +37,7 @@ public:
// Commands // Commands
vector<uint8_t> InquiryInternal() const override; vector<uint8_t> InquiryInternal() const override;
void ModeSelect(scsi_defs::scsi_command, cdb_t, span<const uint8_t>, int) const override; void ModeSelect(scsi_defs::scsi_command, cdb_t, span<const uint8_t>, int) override;
void AddFormatPage(map<int, vector<byte>>&, bool) const override; void AddFormatPage(map<int, vector<byte>>&, bool) const override;
void AddVendorPage(map<int, vector<byte>>&, int, bool) const override; void AddVendorPage(map<int, vector<byte>>&, int, bool) const override;

View File

@ -33,7 +33,7 @@ class SCSIHD_NEC : public SCSIHD //NOSONAR The inheritance hierarchy depth is ac
{ {
public: 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; ~SCSIHD_NEC() override = default;
void Open() override; void Open() override;

View File

@ -19,10 +19,8 @@
using namespace scsi_command_util; using namespace scsi_command_util;
SCSIMO::SCSIMO(int lun, const unordered_set<uint32_t>& 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 // 128 MB, 512 bytes per sector, 248826 sectors
geometries[512 * 248826] = { 512, 248826 }; geometries[512 * 248826] = { 512, 248826 };
// 230 MB, 512 bytes per block, 446325 sectors // 230 MB, 512 bytes per block, 446325 sectors
@ -90,7 +88,7 @@ void SCSIMO::AddOptionPage(map<int, vector<byte>>& pages, bool) const
// Do not report update blocks // Do not report update blocks
} }
void SCSIMO::ModeSelect(scsi_command cmd, cdb_t cdb, span<const uint8_t> buf, int length) const void SCSIMO::ModeSelect(scsi_command cmd, cdb_t cdb, span<const uint8_t> buf, int length)
{ {
if (const string result = scsi_command_util::ModeSelect(cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount()); if (const string result = scsi_command_util::ModeSelect(cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount());
!result.empty()) { !result.empty()) {

View File

@ -26,13 +26,13 @@ class SCSIMO : public Disk
{ {
public: public:
SCSIMO(int, const unordered_set<uint32_t>&); explicit SCSIMO(int);
~SCSIMO() override = default; ~SCSIMO() override = default;
void Open() override; void Open() override;
vector<uint8_t> InquiryInternal() const override; vector<uint8_t> InquiryInternal() const override;
void ModeSelect(scsi_defs::scsi_command, cdb_t, span<const uint8_t>, int) const override; void ModeSelect(scsi_defs::scsi_command, cdb_t, span<const uint8_t>, int) override;
protected: protected:

View File

@ -60,23 +60,28 @@ const char* BUS::GetPhaseStrRaw(phase_t current_phase) {
return it != phase_str_mapping.end() ? it->second : "INVALID"; 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 // |MSG|C/D|I/O| Phase
// Reference Table 8: https://www.staff.uni-mainz.de/tacke/scsi/SCSI2-06.html // | 0 | 0 | 0 | DATA OUT
// This determines the phase based upon the Msg, C/D and I/O signals. // | 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<phase_t, 8> BUS::phase_table = { const array<phase_t, 8> BUS::phase_table = {
// | MSG|C/D|I/O | phase_t::dataout,
phase_t::dataout, // | 0 | 0 | 0 | phase_t::datain,
phase_t::datain, // | 0 | 0 | 1 | phase_t::command,
phase_t::command, // | 0 | 1 | 0 | phase_t::status,
phase_t::status, // | 0 | 1 | 1 | phase_t::reserved,
phase_t::reserved, // | 1 | 0 | 0 | phase_t::reserved,
phase_t::reserved, // | 1 | 0 | 1 | phase_t::msgout,
phase_t::msgout, // | 1 | 1 | 0 | phase_t::msgin
phase_t::msgin // | 1 | 1 | 1 |
}; };
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------

View File

@ -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 // 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 // a packet. This is the number of uS that will be delayed between the
// header and the actual data. // 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 class bus_exception : public runtime_error
@ -68,8 +68,7 @@ class BUS : public PinControl
// Operation modes definition // Operation modes definition
enum class mode_e { enum class mode_e {
TARGET = 0, TARGET = 0,
INITIATOR = 1, INITIATOR = 1
MONITOR = 2,
}; };
static int GetCommandByteCount(uint8_t); static int GetCommandByteCount(uint8_t);
@ -86,7 +85,6 @@ class BUS : public PinControl
// Get the string phase name, based upon the raw data // Get the string phase name, based upon the raw data
static const char *GetPhaseStrRaw(phase_t current_phase); static const char *GetPhaseStrRaw(phase_t current_phase);
virtual int GetMode(int pin) = 0;
virtual uint32_t Acquire() = 0; virtual uint32_t Acquire() = 0;
virtual unique_ptr<DataSample> GetSample(uint64_t timestamp = 0) = 0; virtual unique_ptr<DataSample> GetSample(uint64_t timestamp = 0) = 0;
@ -97,9 +95,6 @@ class BUS : public PinControl
// SEL signal event polling // SEL signal event polling
virtual bool PollSelectEvent() = 0; virtual bool PollSelectEvent() = 0;
// Clear SEL signal event
virtual void ClearSelectEvent() = 0;
virtual bool GetSignal(int pin) const = 0; virtual bool GetSignal(int pin) const = 0;
// Get SCSI input signal value // Get SCSI input signal value
virtual void SetSignal(int pin, bool ast) = 0; virtual void SetSignal(int pin, bool ast) = 0;

View File

@ -31,7 +31,6 @@ class DataSample
virtual bool GetREQ() const = 0; virtual bool GetREQ() const = 0;
virtual bool GetACT() const = 0; virtual bool GetACT() const = 0;
virtual uint8_t GetDAT() const = 0; virtual uint8_t GetDAT() const = 0;
virtual bool GetDP() const = 0;
virtual uint32_t GetRawCapture() const = 0; virtual uint32_t GetRawCapture() const = 0;

View File

@ -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"

View File

@ -74,10 +74,6 @@ class DataSample_Raspberry final : public DataSample
{ {
return GetSignal(PIN_ACT); return GetSignal(PIN_ACT);
} }
bool GetDP() const override
{
return GetSignal(PIN_DP);
}
uint8_t GetDAT() const override uint8_t GetDAT() const override
{ {
uint8_t ret_val = 0; uint8_t ret_val = 0;
@ -106,4 +102,4 @@ class DataSample_Raspberry final : public DataSample
private: private:
uint32_t data = 0; uint32_t data = 0;
}; };

View File

@ -1,10 +1,11 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator PiSCSI // SCSI Target Emulator PiSCSI
// for Raspberry Pi // for Raspberry Pi
// //
// Powered by XM6 TypeG Technology. // Powered by XM6 TypeG Technology.
// Copyright (C) 2016-2020 GIMONS // Copyright (C) 2016-2020 GIMONS
// Copyright (C) 2023 Uwe Seimet
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
@ -14,10 +15,11 @@
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/time.h> #include <time.h>
#ifdef __linux__ #ifdef __linux__
#include <sys/epoll.h> #include <sys/epoll.h>
#endif #endif
#include <chrono>
using namespace std; using namespace std;
@ -276,9 +278,12 @@ int GPIOBUS::SendHandShake(uint8_t *buf, int count, int delay_after_bytes)
if (actmode == mode_e::TARGET) { if (actmode == mode_e::TARGET) {
for (i = 0; i < count; i++) { for (i = 0; i < count; i++) {
if (i == delay_after_bytes) { 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"); 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 // Set the DATA signals
@ -403,42 +408,23 @@ bool GPIOBUS::PollSelectEvent()
#endif #endif
} }
//---------------------------------------------------------------------------
//
// Cancel SEL signal event
//
//---------------------------------------------------------------------------
void GPIOBUS::ClearSelectEvent()
{
GPIO_FUNCTION_TRACE
}
//---------------------------------------------------------------------------
//
// Wait for signal change
//
//---------------------------------------------------------------------------
bool GPIOBUS::WaitSignal(int pin, bool ast) bool GPIOBUS::WaitSignal(int pin, bool ast)
{ {
// Get current time const auto now = chrono::steady_clock::now();
const uint32_t now = SysTimer::GetTimerLow();
// Calculate timeout (3000ms)
const uint32_t timeout = 3000 * 1000;
// Wait up to 3 s
do { do {
// Immediately upon receiving a reset
Acquire(); Acquire();
if (GetRST()) {
return false;
}
// Check for the signal edge
if (GetSignal(pin) == ast) { if (GetSignal(pin) == ast) {
return true; 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::seconds>(chrono::steady_clock::now() - now).count()) < 3);
return false; return false;
} }

View File

@ -182,8 +182,6 @@ class GPIOBUS : public BUS
// SEL signal event polling // SEL signal event polling
bool PollSelectEvent() override; bool PollSelectEvent() override;
// Clear SEL signal event
void ClearSelectEvent() override;
protected: protected:
virtual void MakeTable() = 0; virtual void MakeTable() = 0;

View File

@ -410,32 +410,30 @@ void GPIOBUS_Raspberry::SetBSY(bool ast)
// Set BSY signal // Set BSY signal
SetSignal(PIN_BSY, ast); SetSignal(PIN_BSY, ast);
if (actmode == mode_e::TARGET) { if (ast) {
if (ast) { // Turn on ACTIVE signal
// Turn on ACTIVE signal SetControl(PIN_ACT, ACT_ON);
SetControl(PIN_ACT, ACT_ON);
// Set Target signal to output // Set Target signal to output
SetControl(PIN_TAD, TAD_OUT); SetControl(PIN_TAD, TAD_OUT);
SetMode(PIN_BSY, OUT); SetMode(PIN_BSY, OUT);
SetMode(PIN_MSG, OUT); SetMode(PIN_MSG, OUT);
SetMode(PIN_CD, OUT); SetMode(PIN_CD, OUT);
SetMode(PIN_REQ, OUT); SetMode(PIN_REQ, OUT);
SetMode(PIN_IO, OUT); SetMode(PIN_IO, OUT);
} else { } else {
// Turn off the ACTIVE signal // Turn off the ACTIVE signal
SetControl(PIN_ACT, ACT_OFF); SetControl(PIN_ACT, ACT_OFF);
// Set the target signal to input // Set the target signal to input
SetControl(PIN_TAD, TAD_IN); SetControl(PIN_TAD, TAD_IN);
SetMode(PIN_BSY, IN); SetMode(PIN_BSY, IN);
SetMode(PIN_MSG, IN); SetMode(PIN_MSG, IN);
SetMode(PIN_CD, IN); SetMode(PIN_CD, IN);
SetMode(PIN_REQ, IN); SetMode(PIN_REQ, IN);
SetMode(PIN_IO, IN); SetMode(PIN_IO, IN);
}
} }
} }
@ -608,55 +606,33 @@ uint8_t GPIOBUS_Raspberry::GetDAT()
return (uint8_t)data; return (uint8_t)data;
} }
//---------------------------------------------------------------------------
//
// Set data signals
//
//---------------------------------------------------------------------------
void GPIOBUS_Raspberry::SetDAT(uint8_t dat) void GPIOBUS_Raspberry::SetDAT(uint8_t dat)
{ {
// Write to port // Write to ports
#if SIGNAL_CONTROL_MODE == 0 #if SIGNAL_CONTROL_MODE == 0
uint32_t fsel = gpfsel[0]; uint32_t fsel = gpfsel[0];
fsel &= tblDatMsk[0][dat]; fsel &= tblDatMsk[0][dat];
fsel |= tblDatSet[0][dat]; fsel |= tblDatSet[0][dat];
if (fsel != gpfsel[0]) { gpfsel[0] = fsel;
gpfsel[0] = fsel; gpio[GPIO_FSEL_0] = fsel;
gpio[GPIO_FSEL_0] = fsel;
}
fsel = gpfsel[1]; fsel = gpfsel[1];
fsel &= tblDatMsk[1][dat]; fsel &= tblDatMsk[1][dat];
fsel |= tblDatSet[1][dat]; fsel |= tblDatSet[1][dat];
if (fsel != gpfsel[1]) { gpfsel[1] = fsel;
gpfsel[1] = fsel; gpio[GPIO_FSEL_1] = fsel;
gpio[GPIO_FSEL_1] = fsel;
}
fsel = gpfsel[2]; fsel = gpfsel[2];
fsel &= tblDatMsk[2][dat]; fsel &= tblDatMsk[2][dat];
fsel |= tblDatSet[2][dat]; fsel |= tblDatSet[2][dat];
if (fsel != gpfsel[2]) { gpfsel[2] = fsel;
gpfsel[2] = fsel; gpio[GPIO_FSEL_2] = fsel;
gpio[GPIO_FSEL_2] = fsel;
}
#else #else
gpio[GPIO_CLR_0] = tblDatMsk[dat]; gpio[GPIO_CLR_0] = tblDatMsk[dat];
gpio[GPIO_SET_0] = tblDatSet[dat]; gpio[GPIO_SET_0] = tblDatSet[dat];
#endif // SIGNAL_CONTROL_MODE #endif
} }
bool GPIOBUS_Raspberry::GetDP() const
{
return GetSignal(PIN_DP);
}
//---------------------------------------------------------------------------
//
// Create work table
//
//---------------------------------------------------------------------------
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// Signal table // Signal table

View File

@ -137,8 +137,6 @@ class GPIOBUS_Raspberry : public GPIOBUS
// Set REQ signal // Set REQ signal
void SetREQ(bool ast) override; void SetREQ(bool ast) override;
bool GetDP() const override;
// Get DAT signal // Get DAT signal
uint8_t GetDAT() override; uint8_t GetDAT() override;
// Set DAT signal // Set DAT signal
@ -174,12 +172,6 @@ class GPIOBUS_Raspberry : public GPIOBUS
// Set Control Signal // Set Control Signal
void SetMode(int pin, int mode) override; void SetMode(int pin, int mode) override;
// Set SCSI I/O mode // 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; bool GetSignal(int pin) const override;
// Get SCSI input signal value // Get SCSI input signal value
void SetSignal(int pin, bool ast) override; void SetSignal(int pin, bool ast) override;

View File

@ -372,11 +372,6 @@ void GPIOBUS_Virtual::SetREQ(bool ast)
SetSignal(PIN_REQ, ast); SetSignal(PIN_REQ, ast);
} }
bool GPIOBUS_Virtual::GetDP() const
{
return GetSignal(PIN_DP);
}
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// Get data signals // Get data signals

View File

@ -97,8 +97,6 @@ class GPIOBUS_Virtual final : public GPIOBUS
void SetREQ(bool ast) override; void SetREQ(bool ast) override;
// Set REQ signal // Set REQ signal
bool GetDP() const override;
bool WaitREQ(bool ast) override bool WaitREQ(bool ast) override
{ {
return WaitSignal(PIN_REQ, ast); return WaitSignal(PIN_REQ, ast);
@ -120,12 +118,6 @@ class GPIOBUS_Virtual final : public GPIOBUS
// Set Control Signal // Set Control Signal
void SetMode(int pin, int mode) override; void SetMode(int pin, int mode) override;
// Set SCSI I/O mode // 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; bool GetSignal(int pin) const override;
// Get SCSI input signal value // Get SCSI input signal value
void SetSignal(int pin, bool ast) override; void SetSignal(int pin, bool ast) override;

View File

@ -52,9 +52,6 @@ class PinControl
// Set ENB signal // Set ENB signal
virtual void SetENB(bool ast) = 0; virtual void SetENB(bool ast) = 0;
// Get parity signal
virtual bool GetDP() const = 0;
// GPIO pin direction setting // GPIO pin direction setting
virtual void PinConfig(int pin, int mode) = 0; virtual void PinConfig(int pin, int mode) = 0;
// GPIO pin pull up/down resistor setting // GPIO pin pull up/down resistor setting
@ -65,4 +62,4 @@ class PinControl
PinControl() = default; PinControl() = default;
virtual ~PinControl() = default; virtual ~PinControl() = default;
}; };

View File

@ -144,7 +144,7 @@ uint32_t SBC_Version::GetPeripheralAddress(void)
return address; return address;
} }
#elif defined __NetBSD__ #elif defined __NetBSD__ && (!defined(__x86_64__) || defined(__X86__))
uint32_t SBC_Version::GetPeripheralAddress(void) uint32_t SBC_Version::GetPeripheralAddress(void)
{ {
char buf[1024]; char buf[1024];

View File

@ -43,12 +43,6 @@ uint32_t SysTimer::GetTimerLow()
return systimer_ptr->GetTimerLow(); return systimer_ptr->GetTimerLow();
} }
// Get system timer high byte
uint32_t SysTimer::GetTimerHigh()
{
return systimer_ptr->GetTimerHigh();
}
// Sleep for N nanoseconds // Sleep for N nanoseconds
void SysTimer::SleepNsec(uint32_t nsec) void SysTimer::SleepNsec(uint32_t nsec)
{ {

View File

@ -29,8 +29,6 @@ class PlatformSpecificTimer
virtual void Init() = 0; virtual void Init() = 0;
// Get system timer low byte // Get system timer low byte
virtual uint32_t GetTimerLow() = 0; virtual uint32_t GetTimerLow() = 0;
// Get system timer high byte
virtual uint32_t GetTimerHigh() = 0;
// Sleep for N nanoseconds // Sleep for N nanoseconds
virtual void SleepNsec(uint32_t nsec) = 0; virtual void SleepNsec(uint32_t nsec) = 0;
// Sleep for N microseconds // Sleep for N microseconds
@ -48,8 +46,6 @@ class SysTimer
static void Init(); static void Init();
// Get system timer low byte // Get system timer low byte
static uint32_t GetTimerLow(); static uint32_t GetTimerLow();
// Get system timer high byte
static uint32_t GetTimerHigh();
// Sleep for N nanoseconds // Sleep for N nanoseconds
static void SleepNsec(uint32_t nsec); static void SleepNsec(uint32_t nsec);
// Sleep for N microseconds // Sleep for N microseconds
@ -57,7 +53,6 @@ class SysTimer
private: private:
static bool initialized; static bool initialized;
static bool is_allwinnner;
static bool is_raspberry; static bool is_raspberry;
static std::unique_ptr<PlatformSpecificTimer> systimer_ptr; static std::unique_ptr<PlatformSpecificTimer> systimer_ptr;

View File

@ -92,16 +92,6 @@ uint32_t SysTimer_Raspberry::GetTimerLow()
return systaddr[SYST_CLO]; return systaddr[SYST_CLO];
} }
//---------------------------------------------------------------------------
//
// Get system timer high byte
//
//---------------------------------------------------------------------------
uint32_t SysTimer_Raspberry::GetTimerHigh()
{
return systaddr[SYST_CHI];
}
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// Sleep in nanoseconds // Sleep in nanoseconds

View File

@ -32,8 +32,6 @@ class SysTimer_Raspberry : public PlatformSpecificTimer
void Init() override; void Init() override;
// Get system timer low byte // Get system timer low byte
uint32_t GetTimerLow() override; uint32_t GetTimerLow() override;
// Get system timer high byte
uint32_t GetTimerHigh() override;
// Sleep for N nanoseconds // Sleep for N nanoseconds
void SleepNsec(uint32_t nsec) override; void SleepNsec(uint32_t nsec) override;
// Sleep for N microseconds // Sleep for N microseconds

View File

@ -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 "$@"

View File

@ -17,11 +17,9 @@
#include "shared/piscsi_version.h" #include "shared/piscsi_version.h"
#include "controllers/scsi_controller.h" #include "controllers/scsi_controller.h"
#include "devices/device_logger.h" #include "devices/device_logger.h"
#include "devices/device_factory.h"
#include "devices/storage_device.h" #include "devices/storage_device.h"
#include "hal/gpiobus_factory.h" #include "hal/gpiobus_factory.h"
#include "hal/gpiobus.h" #include "hal/gpiobus.h"
#include "hal/systimer.h"
#include "piscsi/piscsi_core.h" #include "piscsi/piscsi_core.h"
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <netinet/in.h> #include <netinet/in.h>
@ -30,6 +28,7 @@
#include <iostream> #include <iostream>
#include <fstream> #include <fstream>
#include <vector> #include <vector>
#include <chrono>
using namespace std; using namespace std;
using namespace filesystem; using namespace filesystem;
@ -685,21 +684,19 @@ bool Piscsi::ShutDown(AbstractController::piscsi_shutdown_mode shutdown_mode)
bool Piscsi::IsNotBusy() const bool Piscsi::IsNotBusy() const
{ {
// Wait until BSY is released as there is a possibility for the // 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) // initiator to assert it while setting the ID (for up to 3 seconds)
if (bus->GetBSY()) { if (bus->GetBSY()) {
const uint32_t now = SysTimer::GetTimerLow(); const auto now = chrono::steady_clock::now();
while ((chrono::duration_cast<chrono::seconds>(chrono::steady_clock::now() - now).count()) < 3) {
bus->Acquire();
// Wait for 3s if (!bus->GetBSY()) {
while ((SysTimer::GetTimerLow() - now) < 3'000'000) { return true;
bus->Acquire(); }
}
if (!bus->GetBSY()) { return false;
return true; }
}
}
return false; return true;
}
return true;
} }

View File

@ -67,7 +67,7 @@ private:
PiscsiImage piscsi_image; PiscsiImage piscsi_image;
PiscsiResponse response; [[no_unique_address]] PiscsiResponse response;
PiscsiService service; PiscsiService service;

View File

@ -10,7 +10,6 @@
#include "shared/piscsi_util.h" #include "shared/piscsi_util.h"
#include "shared/protobuf_util.h" #include "shared/protobuf_util.h"
#include "shared/piscsi_exceptions.h" #include "shared/piscsi_exceptions.h"
#include "devices/device_factory.h"
#include "devices/disk.h" #include "devices/disk.h"
#include "localizer.h" #include "localizer.h"
#include "command_context.h" #include "command_context.h"
@ -530,7 +529,7 @@ bool PiscsiExecutor::SetSectorSize(const CommandContext& context, shared_ptr<Pri
if (size) { if (size) {
const auto disk = dynamic_pointer_cast<Disk>(device); const auto disk = dynamic_pointer_cast<Disk>(device);
if (disk != nullptr && disk->IsSectorSizeConfigurable()) { 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)); return context.ReturnLocalizedError(LocalizationKey::ERROR_BLOCK_SIZE, to_string(size));
} }
} }

View File

@ -11,6 +11,7 @@
#include "hal/bus.h" #include "hal/bus.h"
#include "controllers/controller_manager.h" #include "controllers/controller_manager.h"
#include "devices/device_factory.h"
#include <unordered_set> #include <unordered_set>
class DeviceFactory; class DeviceFactory;
@ -60,7 +61,7 @@ private:
ControllerManager& controller_manager; ControllerManager& controller_manager;
const DeviceFactory device_factory; [[no_unique_address]] const DeviceFactory device_factory;
unordered_set<int> reserved_ids; unordered_set<int> reserved_ids;
}; };

View File

@ -24,26 +24,29 @@ using namespace piscsi_util;
using namespace network_util; using namespace network_util;
using namespace protobuf_util; using namespace protobuf_util;
void PiscsiResponse::GetDeviceProperties(const Device& device, PbDeviceProperties& properties) const void PiscsiResponse::GetDeviceProperties(shared_ptr<Device> device, PbDeviceProperties& properties) const
{ {
properties.set_luns(ControllerManager::GetScsiLunMax()); properties.set_luns(ControllerManager::GetScsiLunMax());
properties.set_read_only(device.IsReadOnly()); properties.set_read_only(device->IsReadOnly());
properties.set_protectable(device.IsProtectable()); properties.set_protectable(device->IsProtectable());
properties.set_stoppable(device.IsStoppable()); properties.set_stoppable(device->IsStoppable());
properties.set_removable(device.IsRemovable()); properties.set_removable(device->IsRemovable());
properties.set_lockable(device.IsLockable()); properties.set_lockable(device->IsLockable());
properties.set_supports_file(device.SupportsFile()); properties.set_supports_file(device->SupportsFile());
properties.set_supports_params(device.SupportsParams()); properties.set_supports_params(device->SupportsParams());
if (device.SupportsParams()) { if (device->SupportsParams()) {
for (const auto& [key, value] : device.GetDefaultParams()) { for (const auto& [key, value] : device->GetDefaultParams()) {
auto& map = *properties.mutable_default_params(); auto& map = *properties.mutable_default_params();
map[key] = value; map[key] = value;
} }
} }
for (const auto& block_size : device_factory.GetSectorSizes(device.GetType())) { shared_ptr<Disk> disk = dynamic_pointer_cast<Disk>(device);
properties.add_block_sizes(block_size); 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(); auto type_properties = device_types_info.add_properties();
type_properties->set_type(type); type_properties->set_type(type);
const auto device = device_factory.CreateDevice(type, 0, ""); 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 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> device, PbDevice& pb_device, const string& default_folder) const
{ {
pb_device.set_id(device.GetId()); pb_device.set_id(device->GetId());
pb_device.set_unit(device.GetLun()); pb_device.set_unit(device->GetLun());
pb_device.set_vendor(device.GetVendor()); pb_device.set_vendor(device->GetVendor());
pb_device.set_product(device.GetProduct()); pb_device.set_product(device->GetProduct());
pb_device.set_revision(device.GetRevision()); pb_device.set_revision(device->GetRevision());
pb_device.set_type(device.GetType()); pb_device.set_type(device->GetType());
GetDeviceProperties(device, *pb_device.mutable_properties()); GetDeviceProperties(device, *pb_device.mutable_properties());
auto status = pb_device.mutable_status(); auto status = pb_device.mutable_status();
status->set_protected_(device.IsProtected()); status->set_protected_(device->IsProtected());
status->set_stopped(device.IsStopped()); status->set_stopped(device->IsStopped());
status->set_removed(device.IsRemoved()); status->set_removed(device->IsRemoved());
status->set_locked(device.IsLocked()); status->set_locked(device->IsLocked());
if (device.SupportsParams()) { if (device->SupportsParams()) {
for (const auto& [key, value] : device.GetParams()) { for (const auto& [key, value] : device->GetParams()) {
SetParam(pb_device, key, value); SetParam(pb_device, key, value);
} }
} }
if (const auto disk = dynamic_cast<const Disk*>(&device); disk) { if (const auto disk = dynamic_pointer_cast<const Disk>(device); disk) {
pb_device.set_block_size(device.IsRemoved()? 0 : disk->GetSectorSizeInBytes()); pb_device.set_block_size(device->IsRemoved()? 0 : disk->GetSectorSizeInBytes());
pb_device.set_block_count(device.IsRemoved() ? 0: disk->GetBlockCount()); pb_device.set_block_count(device->IsRemoved() ? 0: disk->GetBlockCount());
} }
const auto storage_device = dynamic_cast<const StorageDevice *>(&device); const auto storage_device = dynamic_pointer_cast<const StorageDevice>(device);
if (storage_device != nullptr) { 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<shared_ptr<PrimaryDevice>>&
{ {
for (const auto& device : devices) { for (const auto& device : devices) {
PbDevice *pb_device = server_info.mutable_devices_info()->add_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_set<shared_ptr<PrimaryDevice
for (const auto& [id, lun] : id_sets) { for (const auto& [id, lun] : id_sets) {
for (const auto& d : devices) { for (const auto& d : devices) {
if (d->GetId() == id && d->GetLun() == lun) { if (d->GetId() == id && d->GetLun() == lun) {
GetDevice(*d, *devices_info->add_devices(), default_folder); GetDevice(d, *devices_info->add_devices(), default_folder);
break; break;
} }
} }

View File

@ -25,7 +25,7 @@ class PiscsiResponse
{ {
public: public:
PiscsiResponse() = default; PiscsiResponse() { }
~PiscsiResponse() = default; ~PiscsiResponse() = default;
bool GetImageFile(PbImageFile&, const string&, const string&) const; bool GetImageFile(PbImageFile&, const string&, const string&) const;
@ -47,11 +47,10 @@ private:
inline static const vector<string> EMPTY_VECTOR; inline static const vector<string> EMPTY_VECTOR;
// TODO Try to get rid of this field by having the device instead of the factory providing the device data [[no_unique_address]] const DeviceFactory device_factory;
const DeviceFactory device_factory;
void GetDeviceProperties(const Device&, PbDeviceProperties&) const; void GetDeviceProperties(shared_ptr<Device>, PbDeviceProperties&) const;
void GetDevice(const Device&, PbDevice&, const string&) const; void GetDevice(shared_ptr<Device>, PbDevice&, const string&) const;
void GetDeviceTypeProperties(PbDeviceTypesInfo&, PbDeviceType) const; void GetDeviceTypeProperties(PbDeviceTypesInfo&, PbDeviceType) const;
void GetAvailableImages(PbImageFilesInfo&, const string&, const string&, const string&, int) const; void GetAvailableImages(PbImageFilesInfo&, const string&, const string&, const string&, int) const;
void GetAvailableImages(PbServerInfo&, const string&, const string&, const string&, int) const; void GetAvailableImages(PbServerInfo&, const string&, const string&, const string&, int) const;

View File

@ -42,7 +42,8 @@ string PiscsiService::Init(const callback& cb, int port)
server.sin_family = PF_INET; server.sin_family = PF_INET;
server.sin_port = htons((uint16_t)port); server.sin_port = htons((uint16_t)port);
server.sin_addr.s_addr = INADDR_ANY; server.sin_addr.s_addr = INADDR_ANY;
if (bind(service_socket, reinterpret_cast<const sockaddr *>(&server), sizeof(sockaddr_in)) < 0) { //NOSONAR bit_cast is not supported by the bullseye compiler if (bind(service_socket, reinterpret_cast<const sockaddr*>(&server), //NOSONAR bit_cast is not supported by the bullseye compiler
static_cast<socklen_t>(sizeof(sockaddr_in))) < 0) {
Stop(); Stop();
return "Port " + to_string(port) + " is in use, is piscsi already running?"; return "Port " + to_string(port) + " is in use, is piscsi already running?";
} }

View File

@ -14,7 +14,7 @@
// The following should be updated for each release // The following should be updated for each release
const int piscsi_major_version = 23; // Last two digits of year const int piscsi_major_version = 23; // Last two digits of year
const int piscsi_minor_version = 11; // Month const int piscsi_minor_version = 11; // Month
const int piscsi_patch_version = 1; // Patch number - increment for each update const int piscsi_patch_version = -1; // Patch number - increment for each update
using namespace std; using namespace std;

View File

@ -39,41 +39,6 @@ TEST(DeviceFactoryTest, GetTypeForFile)
EXPECT_EQ(device_factory.GetTypeForFile("test.iso.suffix"), UNDEFINED); EXPECT_EQ(device_factory.GetTypeForFile("test.iso.suffix"), UNDEFINED);
} }
TEST(DeviceFactoryTest, GetSectorSizes)
{
DeviceFactory device_factory;
unordered_set<uint32_t> 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) TEST(DeviceFactoryTest, GetExtensionMapping)
{ {
DeviceFactory device_factory; DeviceFactory device_factory;

View File

@ -774,14 +774,8 @@ TEST(DiskTest, SectorSize)
{ {
MockDisk disk; MockDisk disk;
unordered_set<uint32_t> sizes = { 512, 1024 };
disk.SetSectorSizes(sizes);
EXPECT_TRUE(disk.IsSectorSizeConfigurable()); EXPECT_TRUE(disk.IsSectorSizeConfigurable());
sizes.clear();
disk.SetSectorSizes(sizes);
EXPECT_FALSE(disk.IsSectorSizeConfigurable());
disk.SetSectorSizeShiftCount(9); disk.SetSectorSizeShiftCount(9);
EXPECT_EQ(9, disk.GetSectorSizeShiftCount()); EXPECT_EQ(9, disk.GetSectorSizeShiftCount());
EXPECT_EQ(512, disk.GetSectorSizeInBytes()); EXPECT_EQ(512, disk.GetSectorSizeInBytes());
@ -815,13 +809,12 @@ TEST(DiskTest, SectorSize)
TEST(DiskTest, ConfiguredSectorSize) 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_EQ(512, disk.GetConfiguredSectorSize());
EXPECT_FALSE(disk.SetConfiguredSectorSize(device_factory, 1234)); EXPECT_FALSE(disk.SetConfiguredSectorSize(1234));
EXPECT_EQ(512, disk.GetConfiguredSectorSize()); EXPECT_EQ(512, disk.GetConfiguredSectorSize());
} }

View File

@ -219,16 +219,3 @@ TEST(GpiobusRaspberry, GetREQ)
bus.Acquire(); bus.Acquire();
EXPECT_EQ(false, bus.GetREQ()); 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());
}

View File

@ -53,7 +53,6 @@ public:
MOCK_METHOD(void, SetENB, (bool), (override)); MOCK_METHOD(void, SetENB, (bool), (override));
MOCK_METHOD(uint8_t, GetDAT, (), (override)); MOCK_METHOD(uint8_t, GetDAT, (), (override));
MOCK_METHOD(void, SetDAT, (uint8_t), (override)); MOCK_METHOD(void, SetDAT, (uint8_t), (override));
MOCK_METHOD(bool, GetDP, (), (const override));
MOCK_METHOD(uint32_t, Acquire, (), (override)); MOCK_METHOD(uint32_t, Acquire, (), (override));
MOCK_METHOD(int, CommandHandShake, (vector<uint8_t>&), (override)); MOCK_METHOD(int, CommandHandShake, (vector<uint8_t>&), (override));
MOCK_METHOD(int, ReceiveHandShake, (uint8_t *, int), (override)); MOCK_METHOD(int, ReceiveHandShake, (uint8_t *, int), (override));
@ -61,13 +60,11 @@ public:
MOCK_METHOD(bool, GetSignal, (int), (const override)); MOCK_METHOD(bool, GetSignal, (int), (const override));
MOCK_METHOD(void, SetSignal, (int, bool), (override)); MOCK_METHOD(void, SetSignal, (int, bool), (override));
MOCK_METHOD(bool, PollSelectEvent, (), (override)); MOCK_METHOD(bool, PollSelectEvent, (), (override));
MOCK_METHOD(void, ClearSelectEvent, (), (override));
MOCK_METHOD(unique_ptr<DataSample>, GetSample, (uint64_t), (override)); MOCK_METHOD(unique_ptr<DataSample>, GetSample, (uint64_t), (override));
MOCK_METHOD(void, PinConfig, (int, int), (override)); MOCK_METHOD(void, PinConfig, (int, int), (override));
MOCK_METHOD(void, PullConfig, (int , int ), (override)); MOCK_METHOD(void, PullConfig, (int , int ), (override));
MOCK_METHOD(void, SetControl, (int , bool ), (override)); MOCK_METHOD(void, SetControl, (int , bool ), (override));
MOCK_METHOD(void, SetMode, (int , int ), (override)); MOCK_METHOD(void, SetMode, (int , int ), (override));
MOCK_METHOD(int, GetMode, (int ), (override));
MockBus() = default; MockBus() = default;
~MockBus() override = default; ~MockBus() override = default;
@ -352,7 +349,7 @@ public:
MOCK_METHOD(void, FlushCache, (), (override)); MOCK_METHOD(void, FlushCache, (), (override));
MOCK_METHOD(void, Open, (), (override)); MOCK_METHOD(void, Open, (), (override));
MockDisk() : Disk(SCHD, 0) {} MockDisk() : Disk(SCHD, 0, { 512, 1024, 2048, 4096 }) {}
~MockDisk() override = default; ~MockDisk() override = default;
}; };
@ -363,10 +360,15 @@ class MockSCSIHD : public SCSIHD //NOSONAR Ignore inheritance hierarchy depth in
FRIEND_TEST(ScsiHdTest, FinalizeSetup); FRIEND_TEST(ScsiHdTest, FinalizeSetup);
FRIEND_TEST(ScsiHdTest, GetProductData); FRIEND_TEST(ScsiHdTest, GetProductData);
FRIEND_TEST(ScsiHdTest, SetUpModePages); FRIEND_TEST(ScsiHdTest, SetUpModePages);
FRIEND_TEST(PiscsiExecutorTest, SetSectorSize); FRIEND_TEST(ScsiHdTest, GetSectorSizes);
FRIEND_TEST(ScsiHdTest, ModeSelect); 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<uint32_t>& 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 class MockSCSIHD_NEC : public SCSIHD_NEC //NOSONAR Ignore inheritance hierarchy depth in unit tests
@ -382,8 +384,10 @@ class MockSCSIHD_NEC : public SCSIHD_NEC //NOSONAR Ignore inheritance hierarchy
class MockSCSICD : public SCSICD //NOSONAR Ignore inheritance hierarchy depth in unit tests class MockSCSICD : public SCSICD //NOSONAR Ignore inheritance hierarchy depth in unit tests
{ {
FRIEND_TEST(ScsiCdTest, GetSectorSizes);
FRIEND_TEST(ScsiCdTest, SetUpModePages); FRIEND_TEST(ScsiCdTest, SetUpModePages);
FRIEND_TEST(ScsiCdTest, ReadToc); FRIEND_TEST(ScsiCdTest, ReadToc);
FRIEND_TEST(ScsiCdTest, ModeSelect);
using SCSICD::SCSICD; using SCSICD::SCSICD;
}; };

View File

@ -252,8 +252,6 @@ TEST(PiscsiExecutorTest, Attach)
TEST(PiscsiExecutorTest, Insert) TEST(PiscsiExecutorTest, Insert)
{ {
DeviceFactory device_factory;
auto bus = make_shared<MockBus>(); auto bus = make_shared<MockBus>();
ControllerManager controller_manager; ControllerManager controller_manager;
auto [controller, device] = CreateDevice(SCHD); auto [controller, device] = CreateDevice(SCHD);
@ -500,13 +498,17 @@ TEST(PiscsiExecutorTest, SetSectorSize)
CommandContext context(command, "", ""); CommandContext context(command, "", "");
unordered_set<uint32_t> sizes; unordered_set<uint32_t> sizes;
auto hd = make_shared<MockSCSIHD>(0, sizes, false); auto hd = make_shared<MockSCSIHD>(sizes);
EXPECT_FALSE(executor.SetSectorSize(context, hd, 512)); EXPECT_FALSE(executor.SetSectorSize(context, hd, 512));
sizes.insert(512); sizes.insert(512);
hd = make_shared<MockSCSIHD>(0, sizes, false); hd = make_shared<MockSCSIHD>(sizes);
EXPECT_TRUE(executor.SetSectorSize(context, hd, 0)); EXPECT_TRUE(executor.SetSectorSize(context, hd, 0));
EXPECT_FALSE(executor.SetSectorSize(context, hd, 1)); EXPECT_FALSE(executor.SetSectorSize(context, hd, 1));
EXPECT_FALSE(executor.SetSectorSize(context, hd, 512));
sizes.insert(1024);
hd = make_shared<MockSCSIHD>(sizes);
EXPECT_TRUE(executor.SetSectorSize(context, hd, 512)); EXPECT_TRUE(executor.SetSectorSize(context, hd, 512));
} }

View File

@ -46,6 +46,11 @@ TEST(ScsiCommandUtilTest, ModeSelect6)
Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list)))) Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list))))
<< "Unsupported page 0 was not rejected"; << "Unsupported page 0 was not rejected";
// Page 1
buf[12] = 0x01;
EXPECT_NO_THROW(ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512))
<< "Page 1 is supported";
// Page 3 (Format Device Page) // Page 3 (Format Device Page)
buf[12] = 0x03; buf[12] = 0x03;
EXPECT_THAT([&] { ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512); }, EXPECT_THAT([&] { ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512); },
@ -62,7 +67,25 @@ TEST(ScsiCommandUtilTest, ModeSelect6)
Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list)))) Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list))))
<< "Not enough command parameters"; << "Not enough command parameters";
EXPECT_FALSE(ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512).empty()); // check length computation
buf[3] = 8;
buf[10] = 2;
buf[12] = 1;
buf[13] = 10;
buf[14] = 0x24;
buf[24] = 0;
EXPECT_NO_THROW(ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512))
<< "Multi-page length computation";
// check length computation
buf[3] = 8;
buf[10] = 12;
buf[12] = 0;
buf[13] = 0;
buf[14] = 0;
buf[24] = 0;
EXPECT_NO_THROW(ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, 12, 512))
<< "Empty ModeSelect6";
} }
TEST(ScsiCommandUtilTest, ModeSelect10) TEST(ScsiCommandUtilTest, ModeSelect10)
@ -111,8 +134,6 @@ TEST(ScsiCommandUtilTest, ModeSelect10)
Property(&scsi_exception::get_sense_key, sense_key::illegal_request), Property(&scsi_exception::get_sense_key, sense_key::illegal_request),
Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list)))) Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list))))
<< "Not enough command parameters"; << "Not enough command parameters";
EXPECT_FALSE(ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 512).empty());
} }
TEST(ScsiCommandUtilTest, EnrichFormatPage) TEST(ScsiCommandUtilTest, EnrichFormatPage)

View File

@ -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"); 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) TEST(ScsiCdTest, SetUpModePages)
{ {
map<int, vector<byte>> pages; map<int, vector<byte>> pages;
MockSCSICD cd(0, {}); MockSCSICD cd(0);
// Non changeable // Non changeable
cd.SetUpModePages(pages, 0x3f, false); cd.SetUpModePages(pages, 0x3f, false);
@ -51,10 +62,10 @@ TEST(ScsiCdTest, SetUpModePages)
TEST(ScsiCdTest, Open) TEST(ScsiCdTest, Open)
{ {
MockSCSICD cd_iso(0, {}); MockSCSICD cd_iso(0);
MockSCSICD cd_cue(0, {}); MockSCSICD cd_cue(0);
MockSCSICD cd_raw(0, {}); MockSCSICD cd_raw(0);
MockSCSICD cd_physical(0, {}); MockSCSICD cd_physical(0);
EXPECT_THROW(cd_iso.Open(), io_exception) << "Missing filename"; EXPECT_THROW(cd_iso.Open(), io_exception) << "Missing filename";
@ -111,8 +122,7 @@ TEST(ScsiCdTest, Open)
TEST(ScsiCdTest, ReadToc) TEST(ScsiCdTest, ReadToc)
{ {
auto controller = make_shared<MockAbstractController>(); auto controller = make_shared<MockAbstractController>();
const unordered_set<uint32_t> sector_sizes; auto cd = make_shared<MockSCSICD>(0);
auto cd = make_shared<MockSCSICD>(0, sector_sizes);
EXPECT_TRUE(cd->Init({})); EXPECT_TRUE(cd->Init({}));
controller->AddDevice(cd); controller->AddDevice(cd);
@ -123,3 +133,26 @@ TEST(ScsiCdTest, ReadToc)
// Further testing requires filesystem access // Further testing requires filesystem access
} }
TEST(ScsiCdTest, ModeSelect)
{
MockSCSICD cd(0);
vector<int> cmd(6);
vector<uint8_t> buf(255);
cd.SetSectorSizeInBytes(2048);
// PF
cmd[1] = 0x10;
// Length
buf[3] = 0x08;
// 2048 bytes per sector
buf[10] = 0x08;
// Page 3 (Device Format Page)
buf[12] = 0x01;
EXPECT_NO_THROW(cd.ModeSelect(scsi_command::eCmdModeSelect6, cmd, buf, 255)) << "MODE SELECT(6) with sector size 2048 is supported";
// 512 bytes per sector
buf[10] = 0x02;
EXPECT_NO_THROW(cd.ModeSelect(scsi_command::eCmdModeSelect6, cmd, buf, 255)) << "MODE SELECT(6) with sector size 512 is supported";
}

View File

@ -30,14 +30,14 @@ TEST(ScsiHdTest, Inquiry)
TEST(ScsiHdTest, SupportsSaveParameters) TEST(ScsiHdTest, SupportsSaveParameters)
{ {
MockSCSIHD hd(0, {}, false); MockSCSIHD hd(0, false);
EXPECT_TRUE(hd.SupportsSaveParameters()); EXPECT_TRUE(hd.SupportsSaveParameters());
} }
TEST(ScsiHdTest, FinalizeSetup) TEST(ScsiHdTest, FinalizeSetup)
{ {
MockSCSIHD hd(0, {}, false); MockSCSIHD hd(0, false);
hd.SetSectorSizeInBytes(1024); hd.SetSectorSizeInBytes(1024);
EXPECT_THROW(hd.FinalizeSetup(0), io_exception) << "Device has 0 blocks"; EXPECT_THROW(hd.FinalizeSetup(0), io_exception) << "Device has 0 blocks";
@ -45,9 +45,9 @@ TEST(ScsiHdTest, FinalizeSetup)
TEST(ScsiHdTest, GetProductData) TEST(ScsiHdTest, GetProductData)
{ {
MockSCSIHD hd_kb(0, {}, false); MockSCSIHD hd_kb(0, false);
MockSCSIHD hd_mb(0, {}, false); MockSCSIHD hd_mb(0, false);
MockSCSIHD hd_gb(0, {}, false); MockSCSIHD hd_gb(0, false);
const path filename = CreateTempFile(1); const path filename = CreateTempFile(1);
hd_kb.SetFilename(string(filename)); hd_kb.SetFilename(string(filename));
@ -73,10 +73,23 @@ TEST(ScsiHdTest, GetProductData)
remove(filename); 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) TEST(ScsiHdTest, SetUpModePages)
{ {
map<int, vector<byte>> pages; map<int, vector<byte>> pages;
MockSCSIHD hd(0, {}, false); MockSCSIHD hd(0, false);
// Non changeable // Non changeable
hd.SetUpModePages(pages, 0x3f, false); hd.SetUpModePages(pages, 0x3f, false);
@ -90,7 +103,7 @@ TEST(ScsiHdTest, SetUpModePages)
TEST(ScsiHdTest, ModeSelect) TEST(ScsiHdTest, ModeSelect)
{ {
MockSCSIHD hd(0, { 512 }, false); MockSCSIHD hd({ 512 });
vector<int> cmd(10); vector<int> cmd(10);
vector<uint8_t> buf(255); vector<uint8_t> buf(255);

View File

@ -28,15 +28,28 @@ TEST(ScsiMoTest, Inquiry)
TEST(ScsiMoTest, SupportsSaveParameters) TEST(ScsiMoTest, SupportsSaveParameters)
{ {
map<int, vector<byte>> pages; map<int, vector<byte>> pages;
MockSCSIMO mo(0, {}); MockSCSIMO mo(0);
EXPECT_TRUE(mo.SupportsSaveParameters()); 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) TEST(ScsiMoTest, SetUpModePages)
{ {
map<int, vector<byte>> pages; map<int, vector<byte>> pages;
MockSCSIMO mo(0, {}); MockSCSIMO mo(0);
// Non changeable // Non changeable
mo.SetUpModePages(pages, 0x3f, false); mo.SetUpModePages(pages, 0x3f, false);
@ -51,7 +64,7 @@ TEST(ScsiMoTest, SetUpModePages)
TEST(ScsiMoTest, TestAddVendorPage) TEST(ScsiMoTest, TestAddVendorPage)
{ {
map<int, vector<byte>> pages; map<int, vector<byte>> pages;
MockSCSIMO mo(0, {}); MockSCSIMO mo(0);
mo.SetReady(true); mo.SetReady(true);
mo.SetUpModePages(pages, 0x21, false); mo.SetUpModePages(pages, 0x21, false);
@ -122,7 +135,7 @@ TEST(ScsiMoTest, TestAddVendorPage)
TEST(ScsiMoTest, ModeSelect) TEST(ScsiMoTest, ModeSelect)
{ {
MockSCSIMO mo(0, { 1024, 2048 }); MockSCSIMO mo(0);
vector<int> cmd(10); vector<int> cmd(10);
vector<uint8_t> buf(255); vector<uint8_t> buf(255);

View File

@ -40,10 +40,11 @@ The following environment variables are available when using Docker Compose:
| `WEB_HTTP_PORT` | 8080 | | `WEB_HTTP_PORT` | 8080 |
| `WEB_HTTPS_PORT` | 8443 | | `WEB_HTTPS_PORT` | 8443 |
| `WEB_LOG_LEVEL` | info | | `WEB_LOG_LEVEL` | info |
| `BACKEND_HOST` | backend | | `BACKEND_HOST` | backend |
| `BACKEND_PORT` | 6868 | | `BACKEND_PORT` | 6868 |
| `BACKEND_PASSWORD` | *[None]* | | `BACKEND_PASSWORD` | *[None]* |
| `BACKEND_LOG_LEVEL` | debug | | `BACKEND_LOG_LEVEL` | debug |
| `RESET_VENV` | *[None]* |
**Examples:** **Examples:**
@ -57,6 +58,12 @@ Start the web UI with the log level set to debug:
WEB_LOG_LEVEL=debug docker compose up WEB_LOG_LEVEL=debug docker compose up
``` ```
Force resetting & reinstalling Python web `venv` directory:
```
RESET_VENV=1 docker compose up
```
## Volumes ## Volumes
When using Docker Compose the following volumes will be mounted automatically: When using Docker Compose the following volumes will be mounted automatically:

View File

@ -18,7 +18,7 @@ FROM debian:bullseye-slim AS runner
USER root USER root
WORKDIR /home/pi 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 COPY docker/backend/piscsi_wrapper.sh /usr/local/bin/piscsi_wrapper.sh
RUN chmod +x /usr/local/bin/* RUN chmod +x /usr/local/bin/*
RUN mkdir -p /home/pi/images RUN mkdir -p /home/pi/images

View File

@ -36,6 +36,7 @@ services:
- "127.0.0.1:${WEB_HTTPS_PORT:-8443}:443" - "127.0.0.1:${WEB_HTTPS_PORT:-8443}:443"
environment: environment:
- BACKEND_PASSWORD=${BACKEND_PASSWORD:-} - BACKEND_PASSWORD=${BACKEND_PASSWORD:-}
- RESET_VENV=${RESET_VENV:-}
init: true init: true
command: [ command: [
"--backend-host=${BACKEND_HOST:-backend}", "--backend-host=${BACKEND_HOST:-backend}",

View File

@ -4,7 +4,7 @@ ARG OS_VERSION=bullseye
FROM "debian:${OS_VERSION}-slim" FROM "debian:${OS_VERSION}-slim"
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y --no-install-recommends sudo systemd rsyslog procps man-db wget git \ && apt-get install -y --no-install-recommends sudo systemd rsyslog procps man-db wget git gcc \
&& apt-get clean \ && apt-get clean \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*

View File

@ -76,7 +76,7 @@ SECRET_FILE="$HOME/.config/piscsi/secret"
FILE_SHARE_PATH="$HOME/shared_files" FILE_SHARE_PATH="$HOME/shared_files"
FILE_SHARE_NAME="Pi File Server" FILE_SHARE_NAME="Pi File Server"
APT_PACKAGES_COMMON="build-essential git protobuf-compiler bridge-utils ca-certificates" APT_PACKAGES_COMMON="build-essential git protobuf-compiler bridge-utils ca-certificates rsyslog"
APT_PACKAGES_BACKEND="libspdlog-dev libpcap-dev libprotobuf-dev protobuf-compiler libgmock-dev clang" APT_PACKAGES_BACKEND="libspdlog-dev libpcap-dev libprotobuf-dev protobuf-compiler libgmock-dev clang"
APT_PACKAGES_PYTHON="python3 python3-dev python3-pip python3-venv python3-setuptools python3-wheel libev-dev libevdev2" APT_PACKAGES_PYTHON="python3 python3-dev python3-pip python3-venv python3-setuptools python3-wheel libev-dev libevdev2"
APT_PACKAGES_WEB="nginx-light genisoimage man2html hfsutils dosfstools kpartx unzip unar disktype gettext" APT_PACKAGES_WEB="nginx-light genisoimage man2html hfsutils dosfstools kpartx unzip unar disktype gettext"
@ -92,7 +92,7 @@ function initialChecks() {
} }
# Only to be used for pi-gen automated install # Only to be used for pi-gen automated install
function cacheSudo() { function sudoCache() {
echo "Caching sudo password" echo "Caching sudo password"
echo raspberry | sudo -v -S echo raspberry | sudo -v -S
} }
@ -948,8 +948,9 @@ function installSamba() {
# Installs and configures Webmin # Installs and configures Webmin
function installWebmin() { function installWebmin() {
WEBMIN_PATH="/usr/share/webmin" WEBMIN_PATH="/usr/share/webmin"
WEBMIN_MODULE_CONFIG="/etc/webmin/netatalk2/config" WEBMIN_NETATALK_MODULE_CONFIG="/etc/webmin/netatalk2/config"
WEBMIN_MODULE_VERSION="1.0" WEBMIN_NETATALK_MODULE_VERSION="1.0"
WEBMIN_VSFTPD_MODULE_VERSION="2023-12-10"
if [ -d "$WEBMIN_PATH" ]; then if [ -d "$WEBMIN_PATH" ]; then
echo echo
@ -967,25 +968,30 @@ function installWebmin() {
echo "Installing packages..." echo "Installing packages..."
sudo apt-get install curl libcgi-session-perl --no-install-recommends --assume-yes </dev/null sudo apt-get install curl libcgi-session-perl --no-install-recommends --assume-yes </dev/null
curl -o setup-repos.sh https://raw.githubusercontent.com/webmin/webmin/master/setup-repos.sh curl -o setup-repos.sh https://raw.githubusercontent.com/webmin/webmin/master/setup-repos.sh
sudo sh setup-repos.sh sudo sh setup-repos.sh -f
rm setup-repos.sh rm setup-repos.sh
sudo apt-get install webmin --install-recommends </dev/null sudo apt-get install webmin --no-install-recommends --assume-yes </dev/null
echo echo
echo "Downloading and installing Webmin module..." echo "Downloading and installing Webmin modules..."
if [[ -f "$WEBMIN_MODULE_CONFIG" ]]; then if [[ -f "$WEBMIN_NETATALK_MODULE_CONFIG" ]]; then
echo "$WEBMIN_MODULE_CONFIG already exists; will not modify..." echo "$WEBMIN_NETATALK_MODULE_CONFIG already exists; will not modify..."
WEBMIN_MODULE_FLAG=1 WEBMIN_MODULE_FLAG=1
fi fi
rm netatalk2-wbm.tgz 2> /dev/null || true rm netatalk2-wbm.tgz 2> /dev/null || true
wget -O netatalk2-wbm.tgz "https://github.com/Netatalk/netatalk-webmin/releases/download/netatalk2-$WEBMIN_MODULE_VERSION/netatalk2-wbm-$WEBMIN_MODULE_VERSION.tgz" </dev/null wget -O netatalk2-wbm.tgz "https://github.com/Netatalk/netatalk-webmin/releases/download/netatalk2-$WEBMIN_NETATALK_MODULE_VERSION/netatalk2-wbm-$WEBMIN_NETATALK_MODULE_VERSION.tgz" </dev/null
sudo "$WEBMIN_PATH/install-module.pl" netatalk2-wbm.tgz sudo "$WEBMIN_PATH/install-module.pl" netatalk2-wbm.tgz
if [[ ! $WEBMIN_MODULE_FLAG ]]; then if [[ ! $WEBMIN_MODULE_FLAG ]]; then
echo "Modifying $WEBMIN_MODULE_CONFIG..." echo "Modifying $WEBMIN_NETATALK_MODULE_CONFIG..."
sudo sed -i 's@/usr/sbin@/usr/local/sbin@' "$WEBMIN_MODULE_CONFIG" sudo sed -i 's@/usr/sbin@/usr/local/sbin@' "$WEBMIN_NETATALK_MODULE_CONFIG"
fi fi
rm netatalk2-wbm.tgz || true rm netatalk2-wbm.tgz || true
rm vsftpd.wbm.gz 2> /dev/null || true
wget -O vsftpd.wbm.tgz "https://github.com/rdmark/vsftpd-webmin/releases/download/$WEBMIN_VSFTPD_MODULE_VERSION/vsftpd-$WEBMIN_VSFTPD_MODULE_VERSION.wbm.gz" </dev/null
sudo "$WEBMIN_PATH/install-module.pl" vsftpd.wbm.tgz
rm vsftpd.wbm.tgz || true
} }
# updates configuration files and installs packages needed for the OLED screen script # updates configuration files and installs packages needed for the OLED screen script
@ -1111,7 +1117,7 @@ function installPiscsiCtrlBoard() {
if [[ $SKIP_PACKAGES ]]; then if [[ $SKIP_PACKAGES ]]; then
echo "Skipping package installation" echo "Skipping package installation"
else else
sudo apt-get update && sudo apt-get install libjpeg-dev libpng-dev libopenjp2-7-dev i2c-tools raspi-config --assume-yes --no-install-recommends </dev/null sudo apt-get update && sudo apt-get install libjpeg-dev libpng-dev libopenjp2-7-dev i2c-tools raspi-config python3-rpi.gpio --assume-yes --no-install-recommends </dev/null
# install python packages through apt that need compilation # install python packages through apt that need compilation
sudo apt-get install python3-cbor2 --assume-yes --no-install-recommends </dev/null sudo apt-get install python3-cbor2 --assume-yes --no-install-recommends </dev/null
fi fi
@ -1261,7 +1267,6 @@ function runChoice() {
echo "Detected piscsi control board service; will run the installation steps for the control board ui." echo "Detected piscsi control board service; will run the installation steps for the control board ui."
installPiscsiCtrlBoard installPiscsiCtrlBoard
fi fi
cachePipPackages
installPiscsiWebInterface installPiscsiWebInterface
installWebInterfaceService installWebInterfaceService
showServiceStatus "piscsi-oled" showServiceStatus "piscsi-oled"
@ -1465,7 +1470,8 @@ function runChoice() {
echo "This script will make the following changes to your system:" echo "This script will make the following changes to your system:"
echo "- Add a 3rd party apt repository" echo "- Add a 3rd party apt repository"
echo "- Install and start the Webmin webapp" echo "- Install and start the Webmin webapp"
echo "- Install the netatalk2 Webmin module" echo "- Install the netatalk2 Webmin module"
echo "- Install the vsftpd Webmin module"
installWebmin installWebmin
echo "Install Webmin - Complete!" echo "Install Webmin - Complete!"
echo "The Webmin webapp should now be listening to port 10000 on this system" echo "The Webmin webapp should now be listening to port 10000 on this system"
@ -1490,9 +1496,6 @@ function runChoice() {
installWebInterfaceService installWebInterfaceService
echo "Automated install of the PiSCSI Service $(CONNECT_TYPE) complete!" echo "Automated install of the PiSCSI Service $(CONNECT_TYPE) complete!"
;; ;;
-h|--help|h|help)
showMenu
;;
*) *)
echo "${1} is not a valid option, exiting..." echo "${1} is not a valid option, exiting..."
exit 1 exit 1
@ -1503,8 +1506,8 @@ function runChoice() {
function readChoice() { function readChoice() {
choice=-1 choice=-1
until [ $choice -ge "0" ] && ([ $choice -eq "99" ] || [ $choice -le "17" ]) ; do until [ $choice -ge "1" ] && ([ $choice -eq "99" ] || [ $choice -le "17" ]) ; do
echo -n "Enter your choice (0-17) or CTRL-C to exit: " echo -n "Enter your choice (1-17) or CTRL-C to exit: "
read -r choice read -r choice
done done
@ -1513,7 +1516,13 @@ function readChoice() {
# Shows the interactive main menu of the script # Shows the interactive main menu of the script
function showMenu() { function showMenu() {
echo "For command line options, rerun with ./easyinstall.sh --help"
echo "Board Type: $CONNECT_TYPE | Compiler: $COMPILER | Compiler Cores: $CORES" echo "Board Type: $CONNECT_TYPE | Compiler: $COMPILER | Compiler Cores: $CORES"
if [[ $SKIP_MAKE_CLEAN ]]; then
echo "Skip 'make clean': YES"
else
echo "Skip 'make clean': NO (will compile from scratch every time!)"
fi
echo "" echo ""
echo "Choose among the following options:" echo "Choose among the following options:"
echo "INSTALL/UPDATE PISCSI" echo "INSTALL/UPDATE PISCSI"
@ -1537,7 +1546,7 @@ function showMenu() {
echo "EXPERIMENTAL FEATURES" echo "EXPERIMENTAL FEATURES"
echo " 15) Share the images dir over AppleShare (requires Netatalk)" echo " 15) Share the images dir over AppleShare (requires Netatalk)"
echo " 16) Compile PiSCSI binaries" echo " 16) Compile PiSCSI binaries"
echo " 17) Install Webmin to manage Netatalk and Samba" echo " 17) Install Webmin to manage the system and companion apps"
} }
# parse arguments passed to the script # parse arguments passed to the script
@ -1585,6 +1594,19 @@ while [ "$1" != "" ]; do
-l | --skip_make_clean) -l | --skip_make_clean)
SKIP_MAKE_CLEAN=1 SKIP_MAKE_CLEAN=1
;; ;;
--help)
echo "Usage: ./easyinstall.sh [options]"
echo
echo "-c=TYPE, --connect_type=TYPE Connect type (FULLSPEC, STANDARD, AIBOM, GAMERNIUM)"
echo "-r=CHOICE, --run_choice=CHOICE Choose a menu option (1 to 16)"
echo "-j=CORES, --cores=CORES Compile on this many cores in parallel"
echo "-t=TOKEN, --token=TOKEN Token password for protecting PiSCSI"
echo "-h, --headless Don't ask questions (use with -r=CHOICE)"
echo "-g, --with_gcc Compile with g++ instead of clang++"
echo "-s, --skip_packages Don't install Debian packages"
echo "-l, --skip_make_clean Don't recompile from scratch every time"
exit
;;
*) *)
echo "ERROR: Unknown parameter \"$PARAM\"" echo "ERROR: Unknown parameter \"$PARAM\""
exit 1 exit 1

5
ide_setup/README Normal file
View File

@ -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.

View File

@ -0,0 +1,193 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<profiles version="1">
<profile kind="CodeFormatterProfile" name="PiSCSI" version="1">
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_exception_specification" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_pointer_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.format_header_comment" value="true"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.indent_statements_compare_to_body" value="true"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_structured_binding_name_list" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_colon_in_base_clause" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_linkage_declaration" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.alignment_for_conditional_expression" value="34"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.indent_empty_lines" value="false"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="true"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_structured_binding_name_list" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.alignment_for_binary_expression" value="20"/>
<setting id="org.eclipse.cdt.core.formatter.brace_position_for_method_declaration" value="next_line"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_pointer_in_declarator_list" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_expression_list" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_base_clause" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_base_types" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
<setting id="org.eclipse.cdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.comment.line_up_line_comment_in_blocks_on_first_column" value="false"/>
<setting id="org.eclipse.cdt.core.formatter.indent_body_declarations_compare_to_namespace_header" value="false"/>
<setting id="org.eclipse.cdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
<setting id="org.eclipse.cdt.core.formatter.indent_access_specifier_extra_spaces" value="0"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_lambda_return" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.comment.never_indent_line_comments_on_first_column" value="true"/>
<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_expression_list" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.brace_position_for_block" value="end_of_line"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_bracket" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.alignment_for_base_clause_in_type_declaration" value="80"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_angle_bracket_in_template_parameters" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.alignment_for_member_access" value="0"/>
<setting id="org.eclipse.cdt.core.formatter.insert_new_line_after_colon_in_constructor_initializer_list" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.brace_position_for_namespace_declaration" value="next_line"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
<setting id="org.eclipse.cdt.core.formatter.lineSplit" value="120"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.comment_formatter_off_tag" value="@formatter:off"/>
<setting id="org.eclipse.cdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_brackets" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_angle_bracket_in_template_arguments" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_bracket" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_angle_bracket_in_template_arguments" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_template_arguments" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.alignment_for_compact_if" value="16"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.alignment_for_declarator_list" value="16"/>
<setting id="org.eclipse.cdt.core.formatter.alignment_for_expressions_in_array_initializer" value="20"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_between_empty_parens_in_exception_specification" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.continuation_indentation_for_array_initializer" value="4"/>
<setting id="org.eclipse.cdt.core.formatter.alignment_for_lambda_expression" value="20"/>
<setting id="org.eclipse.cdt.core.formatter.comment.min_distance_between_code_and_line_comment" value="1"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_bracket" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.alignment_for_constructor_initializer_list" value="0"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_exception_specification" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_declarator_list" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.format_line_comment" value="true"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_angle_bracket_in_template_parameters" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.format_block_comment" value="true"/>
<setting id="org.eclipse.cdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_closing_angle_bracket_in_template_parameters" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.compact_else_if" value="true"/>
<setting id="org.eclipse.cdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/>
<setting id="org.eclipse.cdt.core.formatter.alignment_for_overloaded_left_shift_chain" value="20"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_identifier_in_function_declaration" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_new_line_in_empty_block" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.comment_formatter_on_tag" value="@formatter:on"/>
<setting id="org.eclipse.cdt.core.formatter.indent_body_declarations_compare_to_linkage" value="false"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_lambda_return" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_declarator_list" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.indent_statements_compare_to_block" value="true"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_brace_in_namespace_declaration" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.indent_access_specifier_compare_to_type_header" value="false"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_template_parameters" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_pointer_in_declarator_list" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.alignment_for_conditional_expression_chain" value="18"/>
<setting id="org.eclipse.cdt.core.formatter.tabulation.char" value="space"/>
<setting id="org.eclipse.cdt.core.formatter.insert_new_line_after_template_declaration" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_ref_qualifier_in_structured_binding" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_colon_in_constructor_initializer_list" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.brace_position_for_type_declaration" value="next_line"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.continuation_indentation" value="0"/>
<setting id="org.eclipse.cdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_structured_binding_name_list" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_structured_binding_name_list" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
<setting id="org.eclipse.cdt.core.formatter.indentation.size" value="4"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_angle_bracket_in_template_parameters" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.alignment_for_expression_list" value="0"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_base_types" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
<setting id="org.eclipse.cdt.core.formatter.indent_label_compare_to_statements" value="true"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
<setting id="org.eclipse.cdt.core.formatter.indent_declaration_compare_to_template_header" value="false"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.use_comment_formatter_tag" value="true"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_angle_bracket_in_template_arguments" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.alignment_for_arguments_in_method_invocation" value="20"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_exception_specification" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.join_wrapped_lines" value="false"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.alignment_for_assignment" value="16"/>
<setting id="org.eclipse.cdt.core.formatter.brace_position_for_linkage_declaration" value="next_line"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_comma_in_template_arguments" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_after_closing_angle_bracket_in_template_arguments" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_comma_in_template_parameters" value="do not insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.indent_body_declarations_compare_to_access_specifier" value="true"/>
<setting id="org.eclipse.cdt.core.formatter.alignment_for_enumerator_list" value="48"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_opening_structured_binding_name_list" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_pointer_in_method_declaration" value="insert"/>
<setting id="org.eclipse.cdt.core.formatter.tabulation.size" value="4"/>
<setting id="org.eclipse.cdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
</profile>
</profiles>

View File

@ -1,10 +1,10 @@
#adafruit-circuitpython-busdevice==5.1.1 luma.core==2.4.1
#adafruit-circuitpython-framebuf==1.4.8 luma.oled==3.8.1
#adafruit-circuitpython-ssd1306==2.12.3
luma-oled==3.8.1
Pillow==10.0.1 Pillow==10.0.1
RPi.GPIO==0.7.0
protobuf==3.19.5 protobuf==3.19.5
unidecode==1.3.2 pyftdi==0.55.0
pyserial==3.5
pyusb==1.2.1
smbus==1.1.post2 smbus==1.1.post2
smbus2==0.4.3
Unidecode==1.3.2

View File

@ -106,8 +106,9 @@ class CtrlBoardMenuUpdateEventHandler(Observer):
handler_function(info_object) handler_function(info_object)
except AttributeError: except AttributeError:
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
log.error( log.debug(
"Handler function not found or returned an error. Skipping.", "Handler function [%s] not found or returned an error. Skipping.",
str(handler_function_name),
) )
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
@ -194,7 +195,7 @@ class CtrlBoardMenuUpdateEventHandler(Observer):
"""Method handles the rotary button press in the profile selection menu """Method handles the rotary button press in the profile selection menu
for selecting a profile to load.""" for selecting a profile to load."""
if info_object is not None and "name" in info_object: 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"]) result = file_cmd.read_config(file_name=info_object["name"])
if result["status"] is True: if result["status"] is True:
self._menu_controller.show_message("Profile loaded!") self._menu_controller.show_message("Profile loaded!")

View File

@ -28,12 +28,7 @@ class CtrlBoardMenuBuilder(MenuBuilder):
def __init__(self, piscsi_cmd: PiscsiCmds): def __init__(self, piscsi_cmd: PiscsiCmds):
super().__init__() super().__init__()
self._piscsi_client = piscsi_cmd self._piscsi_client = piscsi_cmd
self.file_cmd = FileCmds( self.file_cmd = FileCmds(piscsi=piscsi_cmd)
sock_cmd=piscsi_cmd.sock_cmd,
piscsi=piscsi_cmd,
token=piscsi_cmd.token,
locale=piscsi_cmd.locale,
)
def build(self, name: str, context_object=None) -> Menu: def build(self, name: str, context_object=None) -> Menu:
if name == CtrlBoardMenuBuilder.SCSI_ID_MENU: if name == CtrlBoardMenuBuilder.SCSI_ID_MENU:
@ -48,7 +43,7 @@ class CtrlBoardMenuBuilder(MenuBuilder):
return self.create_device_info_menu(context_object) return self.create_device_info_menu(context_object)
log = logging.getLogger(__name__) 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) return self.create_scsi_id_list_menu(context_object)
@ -142,7 +137,7 @@ class CtrlBoardMenuBuilder(MenuBuilder):
def create_images_menu(self, context_object=None): def create_images_menu(self, context_object=None):
"""Creates a sub menu showing all the available images""" """Creates a sub menu showing all the available images"""
menu = Menu(CtrlBoardMenuBuilder.IMAGES_MENU) 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}) menu.add_entry("Return", {"context": self.IMAGES_MENU, "action": self.ACTION_RETURN})
images = images_info["files"] images = images_info["files"]
sorted_images = sorted(images, key=lambda d: d["name"]) sorted_images = sorted(images, key=lambda d: d["name"])

View File

@ -23,7 +23,7 @@ class Cycler:
self._menu_controller = menu_controller self._menu_controller = menu_controller
self.sock_cmd = sock_cmd self.sock_cmd = sock_cmd
self.piscsi_cmd = piscsi_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.cycle_entries = self.populate_cycle_entries()
self.return_string = return_string self.return_string = return_string
self.return_entry = return_entry self.return_entry = return_entry

View File

@ -30,7 +30,7 @@ class MenuRenderer(ABC):
self.draw = ImageDraw.Draw(self.image) self.draw = ImageDraw.Draw(self.image)
self.font = ImageFont.truetype(config.font_path, size=config.font_size) self.font = ImageFont.truetype(config.font_path, size=config.font_size)
# just a sample text to work with the font height # just a sample text to work with the font height
_, self.font_height = self.font.getsize("ABCabc") self.font_height = self.font.getbbox("ABCabc")[3]
self.cursor_position = 0 self.cursor_position = 0
self.frame_start_row = 0 self.frame_start_row = 0
self.render_timestamp = None self.render_timestamp = None
@ -153,7 +153,8 @@ class MenuRenderer(ABC):
def draw_fullsceen_message(self, text: str): def draw_fullsceen_message(self, text: str):
"""Draws a fullscreen message, i.e., a full-screen message.""" """Draws a fullscreen message, i.e., a full-screen message."""
font_width, font_height = self.font.getsize(text) font_width = self.font.getlength(text)
font_height = self.font.getbbox(text)[3]
centered_width = (self.disp.width - font_width) / 2 centered_width = (self.disp.width - font_width) / 2
centered_height = (self.disp.height - font_height) / 2 centered_height = (self.disp.height - font_height) / 2
@ -171,7 +172,7 @@ class MenuRenderer(ABC):
def draw_mini_message(self, text: str): def draw_mini_message(self, text: str):
"""Draws a fullscreen message, i.e., a message covering only the center portion of """Draws a fullscreen message, i.e., a message covering only the center portion of
the screen. The remaining areas stay visible.""" the screen. The remaining areas stay visible."""
font_width, _ = self.font.getsize(text) font_width = self.font.getlength(text)
centered_width = (self.disp.width - font_width) / 2 centered_width = (self.disp.width - font_width) / 2
centered_height = (self.disp.height - self.font_height) / 2 centered_height = (self.disp.height - self.font_height) / 2
@ -270,7 +271,7 @@ class MenuRenderer(ABC):
def setup_horizontal_scrolling(self, text): def setup_horizontal_scrolling(self, text):
"""Configure horizontal scrolling based on the configured screen dimensions.""" """Configure horizontal scrolling based on the configured screen dimensions."""
font_width, _ = self.font.getsize(text) font_width = self.font.getlength(text)
self._current_line_horizontal_overlap = font_width - self.disp.width self._current_line_horizontal_overlap = font_width - self.disp.width
def update(self): def update(self):

View File

@ -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"

View File

@ -58,6 +58,15 @@ div.footer div.theme-change-hint {
margin-bottom: 15px; margin-bottom: 15px;
} }
div.login-status {
text-align: right;
}
div.login-status a {
color: white;
text-decoration: underline;
}
div.logged-in { div.logged-in {
background-color: green; background-color: green;
} }

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-external-link"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>

After

Width:  |  Height:  |  Size: 388 B

View File

@ -282,18 +282,6 @@ div.header div.login-form-title {
display: none; 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) { @media (max-width: 900px) {
div.header { div.header {
flex-wrap: wrap; flex-wrap: wrap;
@ -663,10 +651,11 @@ table#attached-devices td.actions {
table#attached-devices td.parameters form { table#attached-devices td.parameters form {
display: flex; display: flex;
align-items: center;
} }
table#attached-devices td.parameters form label { table#attached-devices td.parameters form label {
display: none; padding: 0 0.5rem 0 0;
} }
table#attached-devices td.parameters form select { table#attached-devices td.parameters form select {
@ -775,6 +764,11 @@ section#files p {
margin-top: 1rem; margin-top: 1rem;
} }
section#files details.subdir {
padding-left: 1rem;
padding-right: 1rem;
}
section#files details.subdir summary.dirname { section#files details.subdir summary.dirname {
text-decoration: underline; text-decoration: underline;
font-family: monospace; 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, section#attach-devices table th:last-child,
@ -876,6 +870,10 @@ section#attach-devices form {
display: block; display: block;
} }
section#attach-devices table form select.table-dropdown {
width: 16rem;
}
@media (max-width: 900px) { @media (max-width: 900px) {
section#attach-devices table tr th:nth-child(2), section#attach-devices table tr th:nth-child(2),
section#attach-devices table tr td:nth-child(2) { section#attach-devices table tr td:nth-child(2) {
@ -971,6 +969,10 @@ section#services li.disabled {
background: url("icons/cloud-off.svg") no-repeat left center; 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 Drives page

View File

@ -115,23 +115,25 @@
<ul> <ul>
<li>{{ _("If you want to add a service, run the easyinstall.sh script and choose the one to install.") }}</li> <li>{{ _("If you want to add a service, run the easyinstall.sh script and choose the one to install.") }}</li>
<li>{{ _("In order to manage the services in the Web UI, you may install Webmin as well.") }}</li> <li>{{ _("In order to manage the services in the Web UI, you may install Webmin as well.") }}</li>
<li>{{ _("To browse the modern web, install a vintage web proxy such as <a href=\"%(url)s\" target=\"_blank\">Macproxy</a>.", url="https://github.com/PiSCSI/piscsi/wiki/Vintage-Web-Proxy#macproxy") }}
</li>
</ul> </ul>
</details> </details>
<ul class="service_status"> <ul class="service_status">
{% if netatalk_configured %} {% if netatalk_configured %}
<li class="service-item enabled"> <li class="service-item enabled">
{{ _("Mac AFP file sharing is enabled.") }} {{ _("Mac AFP file sharing is enabled.") }}
{% else %} {% else %}
<li class="service-item disabled"> <li class="service-item disabled">
{{ _("Mac AFP file sharing is disabled.") }} {{ _("Mac AFP file sharing is disabled.") }}
{% endif %} {% endif %}
</li> </li>
{% if webmin_configured %} {% if webmin_configured %}
<li> <ul>
<a href="https://{{ env["ip_addr"] }}:10000/netatalk2/" target=\"_blank\"> <li class="service-item extlink">
{{ _("Manage the AFP server") }} <a href="https://{{ env["ip_addr"] }}:10000/netatalk2/" target=\"_blank\">{{ _("Manage the AFP server") }}</a>
</a> </li>
</li> </ul>
{% endif %} {% endif %}
{% if samba_configured %} {% if samba_configured %}
<li class="service-item enabled"> <li class="service-item enabled">
@ -142,11 +144,11 @@
{% endif %} {% endif %}
</li> </li>
{% if webmin_configured %} {% if webmin_configured %}
<li> <ul>
<a href="https://{{ env["ip_addr"] }}:10000/samba/" target=\"_blank\"> <li class="service-item extlink">
{{ _("Manage the SMB server") }} <a href="https://{{ env["ip_addr"] }}:10000/samba/" target=\"_blank\">{{ _("Manage the SMB server") }}</a>
</a> </li>
</li> </ul>
{% endif %} {% endif %}
{% if ftp_configured %} {% if ftp_configured %}
<li class="service-item enabled"> <li class="service-item enabled">
@ -156,6 +158,13 @@
{{ _("FTP file sharing is disabled.") }} {{ _("FTP file sharing is disabled.") }}
{% endif %} {% endif %}
</li> </li>
{% if webmin_configured %}
<ul>
<li class="service-item extlink">
<a href="https://{{ env["ip_addr"] }}:10000/vsftpd/" target=\"_blank\">{{ _("Manage the FTP server") }}</a>
</li>
</ul>
{% endif %}
{% if macproxy_configured %} {% if macproxy_configured %}
<li class="service-item enabled"> <li class="service-item enabled">
{{ _("Vintage web proxy is running at %(ip_addr)s (default port 5000)", ip_addr=env['ip_addr']) }} {{ _("Vintage web proxy is running at %(ip_addr)s (default port 5000)", ip_addr=env['ip_addr']) }}
@ -164,6 +173,11 @@
{{ _("Vintage web proxy is disabled.") }} {{ _("Vintage web proxy is disabled.") }}
{% endif %} {% endif %}
</li> </li>
{% if webmin_configured %}
<li class="service-item extlink">
<a href="https://{{ env["ip_addr"] }}:10000/" target=\"_blank\">{{ _("Manage PiSCSI services & Linux with Webmin") }}</a>
</li>
{% endif %}
</ul> </ul>
</section> </section>

View File

@ -27,37 +27,31 @@
<body class="{{ body_classes|join(' ') }}"> <body class="{{ body_classes|join(' ') }}">
<div class="header"> <div class="header">
{% if env["auth_active"] %} {% if env["logged_in"] or not env["auth_active"] %}
<div align="center" class="login-status logged-in">
{% if env["logged_in"] %} {% if env["logged_in"] %}
<div align="center" class="login-status logged-in"> <span class="logged-in-as-text">{{ _("Logged in as <em>%(username)s</em>", username=env["username"]) }}</span>
<span class="logged-in-as-text">{{ _("Logged in as <em>%(username)s</em>", username=env["username"]) }}</span> <span class="separator">-</span>
<span class="separator">-</span> <span class="log-out-button"><a href="/logout">{{ _("Log Out") }}</a></span>
<span class="log-out-button"><a href="/logout">{{ _("Log Out") }}</a></span> <span class="separator">-</span>
<span class="separator">-</span>
<span class="admin-button"><a href="/sys/admin">{{ _("Settings") }}</a></span>
</div>
{% else %}
<div align="center" class="login-status logged-out">
<form method="POST" action="/login">
<div class="login-form-title">{{ _("Log in to use Web Interface") }}</div>
<span>
<label for="username">{{ _("Username:") }}</label>
<input type="text" name="username" id="username">
</span>
<span>
<label for="password">{{ _("Password:") }}</label>
<input type="password" name="password" id="password">
</span>
<input type="submit" value="Login">
</form>
</div>
{% endif %} {% endif %}
<span class="admin-button"><a href="/sys/admin">{{ _("Settings") }}</a></span>
</div>
{% else %} {% else %}
<div align="center" class="login-status authentication-disabled"> <div align="center" class="login-status logged-out">
<span class="authentication-disabled-text">{{ _("Web Interface Authentication Disabled") }}</span> <form method="POST" action="/login">
<span class="separator">-</span> <div class="login-form-title">{{ _("Log in to use Web Interface") }}</div>
<span class="wiki-help-text">{{ _("See <a href=\"%(url)s\" target=\"_blank\">Wiki</a> for more information", url="https://github.com/PiSCSI/piscsi/wiki/Web-Interface#enable-authentication") }}</span> <span>
</div> <label for="username">{{ _("Username:") }}</label>
<input type="text" name="username" id="username">
</span>
<span>
<label for="password">{{ _("Password:") }}</label>
<input type="password" name="password" id="password">
</span>
<input type="submit" value="Login">
</form>
</div>
{% endif %} {% endif %}
<div align="center" class="title"> <div align="center" class="title">

View File

@ -24,6 +24,7 @@
</ul> </ul>
</details> </details>
{% if env["cfg_dir_exists"] %}
<p> <p>
<form action="/config/action" method="post" id="config-actions"> <form action="/config/action" method="post" id="config-actions">
<label for="config_load_name">{{ _("File Name:") }}</label> <label for="config_load_name">{{ _("File Name:") }}</label>
@ -54,6 +55,13 @@
<input type="submit" value="{{ _("Save") }}"> <input type="submit" value="{{ _("Save") }}">
</form> </form>
</p> </p>
{% else %}
<div class="notice">
{{ _("Please create the PiSCSI configuration dir to use configurations:")}} {{ CFG_DIR }}
</div>
{% endif %}
<table id="attached-devices" border="black" cellpadding="3" summary="List of attached devices"> <table id="attached-devices" border="black" cellpadding="3" summary="List of attached devices">
<tbody> <tbody>
@ -82,13 +90,12 @@
{% endif %} {% endif %}
<td class="name" align="center">{{ device.device_name }}</td> <td class="name" align="center">{{ device.device_name }}</td>
<td class="parameters"> <td class="parameters">
{% if "No Media" in device.status %} {% if "No Media" in device.status %}
<form action="/scsi/attach" method="post"> <form action="/scsi/attach" method="post">
<label for="device_list_file_name_{{ device.id }}_{{ device.unit }}">{{ _("File:") }}</label>
<input name="scsi_id" type="hidden" value="{{ device.id }}"> <input name="scsi_id" type="hidden" value="{{ device.id }}">
<input name="unit" type="hidden" value="{{ device.unit }}"> <input name="unit" type="hidden" value="{{ device.unit }}">
<input name="type" type="hidden" value="{{ device.device_type }}"> <input name="type" type="hidden" value="{{ device.device_type }}">
<input name="file_size" type="hidden" value="{{ device.size }}">
<label for="device_list_file_name_{{ device.id }}_{{ device.unit }}">{{ _("File name") }}</label>
<select type="select" name="file_name" id="device_list_file_name_{{ device.id }}_{{ device.unit }}"> <select type="select" name="file_name" id="device_list_file_name_{{ device.id }}_{{ device.unit }}">
{% for f in files|sort(attribute='name') %} {% for f in files|sort(attribute='name') %}
{% if device.device_type == "SCCD" %} {% if device.device_type == "SCCD" %}
@ -106,7 +113,7 @@
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</select> </select>
<input type="submit" value="{{ _("Attach") }}"> <input type="submit" value="{{ _("Insert") }}">
</form> </form>
{% else %} {% else %}
{% if device.params %} {% if device.params %}
@ -120,7 +127,14 @@
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% elif device.file %} {% elif device.file %}
<span class="filename">{{ device.file }}</span> <form action="/scsi/eject" method="post" onsubmit="return confirm('{{ _("Eject Disk? WARNING: On Mac OS, eject the Disk in the Finder instead!") }}')">
<label>{{ device.file }}</label>
{% if device.device_type in REMOVABLE_DEVICE_TYPES and "No Media" not in device.status %}
<input name="scsi_id" type="hidden" value="{{ device.id }}">
<input name="unit" type="hidden" value="{{ device.unit }}">
<input type="submit" value="{{ _("Eject") }}">
{% endif %}
</form>
{% endif %} {% endif %}
{% endif %} {% endif %}
</td> </td>
@ -135,13 +149,6 @@
</td> </td>
<td class="actions" align="center"> <td class="actions" align="center">
{% if device.id in scsi_ids["occupied_ids"] %} {% if device.id in scsi_ids["occupied_ids"] %}
{% if device.device_type in REMOVABLE_DEVICE_TYPES and "No Media" not in device.status %}
<form action="/scsi/eject" method="post" onsubmit="return confirm('{{ _("Eject Disk? WARNING: On Mac OS, eject the Disk in the Finder instead!") }}')">
<input name="scsi_id" type="hidden" value="{{ device.id }}">
<input name="unit" type="hidden" value="{{ device.unit }}">
<input type="submit" value="{{ _("Eject") }}">
</form>
{% endif %}
<form action="/scsi/detach" method="post" onsubmit="return confirm('{{ _("Detach Device?") }}')"> <form action="/scsi/detach" method="post" onsubmit="return confirm('{{ _("Detach Device?") }}')">
<input name="scsi_id" type="hidden" value="{{ device.id }}"> <input name="scsi_id" type="hidden" value="{{ device.id }}">
<input name="unit" type="hidden" value="{{ device.unit }}"> <input name="unit" type="hidden" value="{{ device.unit }}">
@ -209,12 +216,8 @@
</ul> </ul>
</details> </details>
{% if not files|length: %} {% if env["image_dir_exists"] %}
<div class="notice"> {% if files|length %}
{{ _("The images directory is currently empty.") }}
</div>
{% else %}
<div> <div>
{% for subdir, group in formatted_image_files.items() %} {% for subdir, group in formatted_image_files.items() %}
@ -309,7 +312,6 @@
{% else %} {% else %}
<form action="/scsi/attach" method="post" class="file-attach"> <form action="/scsi/attach" method="post" class="file-attach">
<input name="file_name" type="hidden" value="{{ file['name'] }}"> <input name="file_name" type="hidden" value="{{ file['name'] }}">
<input name="file_size" type="hidden" value="{{ file['size'] }}">
<label for="image_list_scsi_id_{{ file["name"] }}">{{ _("ID") }}</label> <label for="image_list_scsi_id_{{ file["name"] }}">{{ _("ID") }}</label>
<select name="scsi_id" id="image_list_scsi_id_{{ file["name"] }}"> <select name="scsi_id" id="image_list_scsi_id_{{ file["name"] }}">
{% for id in scsi_ids["valid_ids"] %} {% for id in scsi_ids["valid_ids"] %}
@ -370,8 +372,170 @@
</details> </details>
{% endfor %} {% endfor %}
</div> </div>
{% else %}
<div class="notice">
{{ _("The images directory is currently empty.") }}
</div>
{% endif %} {% endif %}
<p><small>{{ _("%(disk_space)s MiB disk space remaining for images", disk_space=env["free_disk_space"]) }}</small></p> <p>
<small>{{ _("%(disk_space)s MiB disk space remaining for images", disk_space=env["free_disk_space"]) }}</small>
</p>
{% else %}
<div class="notice">
{{ _("Please create the PiSCSI images dir to work with disk images:")}} {{ env["image_dir"] }}
</div>
{% endif %}
</section>
<hr/>
<section id="attach-devices">
<details>
<summary class="heading">
{{ _("Attach Device") }}
</summary>
<ul>
</li>
{% if bridge_configured %}
<li>{{ _("The <tt>piscsi_bridge</tt> network bridge is active and ready to be used by an emulated network adapter!") }}</li>
{% else %}
<li>{{ _("Please configure the <tt>piscsi_bridge</tt> network bridge before attaching an emulated network adapter!") }}</li>
{% endif %}
<li>{{ _("Read more about <a href=\"%(url)s\" target=\"_blank\">supported device types</a> on the wiki.", url="https://github.com/PiSCSI/piscsi/wiki/Supported-Device-Types") }}
</li>
</ul>
</details>
<table border="black" cellpadding="3" summary="List of peripheral devices">
<tr>
<th scope="col">{{ _("Device") }}</th>
<th scope="col">{{ _("Key") }}</th>
<th scope="col">{{ _("Actions") }}</th>
</tr>
{% for type in device_types.keys() %}
<tr>
<td>
{% if device_types[type]["name"] == type %}
{% if type in REMOVABLE_DEVICE_TYPES %}
<div>{{ _("Unknown Removable Disk Drive") }}</div>
{% elif type in DISK_DEVICE_TYPES %}
<div>{{ _("Unknown Fixed Disk Drive") }}</div>
{% else %}
<div>{{ _("Unknown Device") }}</div>
{% endif %}
{% else %}
<div>{{ device_types[type]["name"] }}</div>
{% endif %}
</td>
<td>
<div>{{ type }}</div>
</td>
<td>
<form action="/scsi/attach" method="post" class="device-attach">
<input name="type" type="hidden" value="{{ type }}">
{% for key, value in device_types[type]["params"] | dictsort %}
<label for="param_{{ type }}_{{ key }}">{{ key }}:</label>
{% if value.isnumeric() %}
<input name="param_{{ key }}" id="param_{{ type }}_{{ key }}" type="number" value="{{ value }}">
{% elif key == "interface" %}
<select name="param_{{ key }}" id="param_{{ type }}_{{ key }}">
{% for if in netinfo["ifs"] %}
<option value="{{ if }}">
{{ if }}
</option>
{% endfor %}
</select>
{% else %}
<input name="param_{{ key }}" id="param_{{ type }}_{{ key }}" type="text" size="{{ value|length }}" placeholder="{{ value }}">
{% endif %}
{% endfor %}
{% if type in DISK_DEVICE_TYPES %}
<label for="{{ type }}_drive_name">{{ _("Identify as:") }}</label>
<select name="drive_name" id="{{ type }}_drive_name" class="table-dropdown">
<option value="">
{{ _("Generic device") }}
</option>
{% if type == "SCHD" %}
{% for drive in drive_properties["hd_conf"] | sort(attribute='name') %}
<option value="{{ drive.name }}">
{{ drive.name }}
</option>
{% endfor %}
{% endif %}
{% if type == "SCCD" %}
{% for drive in drive_properties["cd_conf"] | sort(attribute='name') %}
<option value="{{ drive.name }}">
{{ drive.name }}
</option>
{% endfor %}
{% endif %}
{% if type == "SCRM" %}
{% for drive in drive_properties["rm_conf"] | sort(attribute='name') %}
<option value="{{ drive.name }}">
{{ drive.name }}
</option>
{% endfor %}
{% endif %}
{% if type == "SCMO" %}
{% for drive in drive_properties["mo_conf"] | sort(attribute='name') %}
<option value="{{ drive.name }}">
{{ drive.name }}
</option>
{% endfor %}
{% endif %}
</select>
<label for="{{ type }}_image_file_name">{{ _("Image file:") }}</label>
<select name="file_name" id="{{ type }}_image_file_name" class="table-dropdown" {% if type not in REMOVABLE_DEVICE_TYPES %}required{% endif %}>
<option value="" selected {% if type not in REMOVABLE_DEVICE_TYPES %}disabled{% endif %}>
{% if type in REMOVABLE_DEVICE_TYPES %}
{{ _("None") }}
{% else %}
{{ _("Choose a file...") }}
{% endif %}
</option>
{% for f in files|sort(attribute='name') %}
{% if type == "SCHD" %}
{% if f["name"].lower().endswith(env['hd_suffixes']) %}
<option value="{{ f["name"] }}">{{ f["name"].replace(env["image_dir"], '') }}</option>
{% endif %}
{% elif type == "SCCD" %}
{% if f["name"].lower().endswith(env['cd_suffixes']) %}
<option value="{{ f["name"] }}">{{ f["name"].replace(env["image_dir"], '') }}</option>
{% endif %}
{% elif type == "SCRM" %}
{% if f["name"].lower().endswith(env['rm_suffixes']) %}
<option value="{{ f["name"] }}">{{ f["name"].replace(env["image_dir"], '') }}</option>
{% endif %}
{% elif type == "SCMO" %}
{% if f["name"].lower().endswith(env['mo_suffixes']) %}
<option value="{{ f["name"] }}">{{ f["name"].replace(env["image_dir"], '') }}</option>
{% endif %}
{% else %}
<option value="{{ f["name"] }}">{{ f["name"].replace(env["image_dir"], '') }}</option>
{% endif %}
{% endfor %}
</select>
{% endif %}
<label for="{{ type }}_scsi_id">{{ _("ID") }}</label>
<select name="scsi_id" id="{{ type }}_scsi_id">
{% for id in scsi_ids["valid_ids"] %}
<option value="{{ id }}"{% if id == scsi_ids["recommended_id"] %} selected{% endif %}>
{{ id }}
</option>
{% endfor %}
</select>
<label for="{{ type }}_unit">{{ _("LUN") }}</label>
<input class="lun" name="unit" id="{{ type }}_unit" type="number" value="0" min="0" max="31" step="1" size="3">
<input type="submit" value="{{ _("Attach") }}" title="{{ _("Attach") }}">
</form>
</td>
</tr>
{% endfor %}
</table>
</section> </section>
<hr/> <hr/>
@ -432,6 +596,69 @@
<hr/> <hr/>
<section id="create-image">
<details>
<summary class="heading">
{{ _("Create Empty Disk Image") }}
</summary>
<ul>
<li>{{ _("Please refer to <a href=\"%(url)s\" target=\"_blank\">wiki documentation</a> to learn more about the supported image file types.", url="https://github.com/PiSCSI/piscsi/wiki/Supported-Device-Types#image-types") }}</li>
<li>{{ _("It is not recommended to use the Lido hard disk driver with the Macintosh Plus.") }}</li>
</ul>
</details>
<form action="/files/create" method="post">
<label for="image_create_file_name">{{ _("File Name:") }}</label>
<input name="file_name" id="image_create_file_name" required="" type="text">
<label for="image_create_type">{{ _("Type:") }}</label>
<select name="type" id="image_create_type">
{% for key, value in image_suffixes_to_create.items() %}
<option value="{{ key }}">
{{ value }} [.{{ key }}]
</option>
{% endfor %}
</select>
<label for="image_create_size">{{ _("Size:") }}</label>
<input name="size" id="image_create_size" type="number" placeholder="{{ _("MiB") }}" min="1" max="262144" required>
<label for="image_create_drive_name">{{ _("Identify as:") }}</label>
<select name="drive_name" id="image_create_drive_name">
<option value="">
{{ _("Generic device") }}
</option>
{% for drive in drive_properties["hd_conf"] | sort(attribute='name') %}
<option value="{{ drive.name }}">
{{ drive.name }}
</option>
{% endfor %}
</select>
<label for="drive_format">{{ _("Format as:") }}</label>
<select name="drive_format" id="drive_format">
<option value="">
{{ _("Unformatted") }}
</option>
<option value="Lido 7.56">
HFS + Lido
</option>
<option value="SpeedTools 3.6">
HFS + SpeedTools
</option>
<option value="FAT16">
FAT16
</option>
<option value="FAT32">
FAT32
</option>
</select>
<input type="submit" value="{{ _("Create") }}">
</form>
</section>
<section id="create-drive">
<a href="/drive/list"><p>{{ _("Create Disk Image With Properties") }}</p></a>
</section>
<hr/>
<section id="create-iso"> <section id="create-iso">
<details> <details>
<summary class="heading"> <summary class="heading">
@ -507,166 +734,4 @@
<hr/> <hr/>
<section id="create-image">
<details>
<summary class="heading">
{{ _("Create Empty Disk Image") }}
</summary>
<ul>
<li>{{ _("Please refer to <a href=\"%(url)s\" target=\"_blank\">wiki documentation</a> to learn more about the supported image file types.", url="https://github.com/PiSCSI/piscsi/wiki/Supported-Device-Types#image-types") }}</li>
<li>{{ _("It is not recommended to use the Lido hard disk driver with the Macintosh Plus.") }}</li>
</ul>
</details>
<form action="/files/create" method="post">
<label for="image_create_file_name">{{ _("File Name:") }}</label>
<input name="file_name" id="image_create_file_name" required="" type="text">
<label for="image_create_type">{{ _("Type:") }}</label>
<select name="type" id="image_create_type">
{% for key, value in image_suffixes_to_create.items() %}
<option value="{{ key }}">
{{ value }} [.{{ key }}]
</option>
{% endfor %}
</select>
<label for="image_create_size">{{ _("Size:") }}</label>
<input name="size" id="image_create_size" type="number" placeholder="{{ _("MiB") }}" min="1" max="262144" required>
<label for="image_create_drive_name">{{ _("Masquerade as:") }}</label>
<select name="drive_name" id="image_create_drive_name">
<option value="">
{{ _("None") }}
</option>
{% for drive in drive_properties["hd_conf"] | sort(attribute='name') %}
<option value="{{ drive.name }}">
{{ drive.name }}
</option>
{% endfor %}
</select>
<label for="drive_format">{{ _("Format as:") }}</label>
<select name="drive_format" id="drive_format">
<option value="">
{{ _("None") }}
</option>
<option value="Lido 7.56">
HFS + Lido
</option>
<option value="SpeedTools 3.6">
HFS + SpeedTools
</option>
<option value="FAT16">
FAT16
</option>
<option value="FAT32">
FAT32
</option>
</select>
<input type="submit" value="{{ _("Create") }}">
</form>
</section>
<section id="create-drive">
<a href="/drive/list"><p>{{ _("Create Disk Image With Properties") }}</p></a>
</section>
<hr/>
<section id="attach-devices">
<details>
<summary class="heading">
{{ _("Attach Peripheral Device") }}
</summary>
<ul>
</li>
{% if bridge_configured %}
<li>{{ _("The <tt>piscsi_bridge</tt> network bridge is active and ready to be used by an emulated network adapter!") }}</li>
{% else %}
<li>{{ _("Please configure the <tt>piscsi_bridge</tt> network bridge before attaching an emulated network adapter!") }}</li>
{% endif %}
<li>{{ _("To browse the modern web, install a vintage web proxy such as <a href=\"%(url)s\" target=\"_blank\">Macproxy</a>.", url="https://github.com/PiSCSI/piscsi/wiki/Vintage-Web-Proxy#macproxy") }}</li>
</li>
<li>{{ _("Read more about <a href=\"%(url)s\" target=\"_blank\">supported device types</a> on the wiki.", url="https://github.com/PiSCSI/piscsi/wiki/Supported-Device-Types") }}
</li>
</ul>
</details>
<table border="black" cellpadding="3" summary="List of peripheral devices">
<tr>
<th scope="col">{{ _("Device") }}</th>
<th scope="col">{{ _("Key") }}</th>
<th scope="col">{{ _("Parameters and Actions") }}</th>
</tr>
{% for type in REMOVABLE_DEVICE_TYPES + PERIPHERAL_DEVICE_TYPES %}
<tr>
<td>
<div>{{ device_types[type]["name"] }}</div>
</td>
<td>
<div>{{ type }}</div>
</td>
<td>
<form action="/scsi/attach_device" method="post" class="device-attach">
<input name="type" type="hidden" value="{{ type }}">
{% for key, value in device_types[type]["params"] | dictsort %}
<label for="param_{{ type }}_{{ key }}">{{ key }}:</label>
{% if value.isnumeric() %}
<input name="param_{{ key }}" id="param_{{ type }}_{{ key }}" type="number" value="{{ value }}">
{% elif key == "interface" %}
<select name="param_{{ key }}" id="param_{{ type }}_{{ key }}">
{% for if in netinfo["ifs"] %}
<option value="{{ if }}">
{{ if }}
</option>
{% endfor %}
</select>
{% else %}
<input name="param_{{ key }}" id="param_{{ type }}_{{ key }}" type="text" size="{{ value|length }}" placeholder="{{ value }}">
{% endif %}
{% endfor %}
{% if type in REMOVABLE_DEVICE_TYPES %}
<label for="{{ type }}_drive_name">{{ _("Masquerade as:") }}</label>
<select name="drive_name" id="{{ type }}_drive_name">
<option value="">
{{ _("None") }}
</option>
{% if type == "SCCD" %}
{% for drive in drive_properties["cd_conf"] | sort(attribute='name') %}
<option value="{{ drive.name }}">
{{ drive.name }}
</option>
{% endfor %}
{% endif %}
{% if type == "SCRM" %}
{% for drive in drive_properties["rm_conf"] | sort(attribute='name') %}
<option value="{{ drive.name }}">
{{ drive.name }}
</option>
{% endfor %}
{% endif %}
{% if type == "SCMO" %}
{% for drive in drive_properties["mo_conf"] | sort(attribute='name') %}
<option value="{{ drive.name }}">
{{ drive.name }}
</option>
{% endfor %}
{% endif %}
</select>
{% endif %}
<label for="{{ type }}_scsi_id">{{ _("ID") }}</label>
<select name="scsi_id" id="{{ type }}_scsi_id">
{% for id in scsi_ids["valid_ids"] %}
<option value="{{ id }}"{% if id == scsi_ids["recommended_id"] %} selected{% endif %}>
{{ id }}
</option>
{% endfor %}
</select>
<label for="{{ type }}_unit">{{ _("LUN") }}</label>
<input class="lun" name="unit" id="{{ type }}_unit" type="number" value="0" min="0" max="31" step="1" size="3">
<input type="submit" value="{{ _("Attach") }}" title="{{ _("Attach") }}">
</form>
</td>
</tr>
{% endfor %}
</table>
</section>
<hr/>
{% endblock content %} {% endblock content %}

View File

@ -41,8 +41,8 @@
<script <script
type="application/javascript" type="application/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.9.3/min/dropzone.min.js" src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/6.0.0-beta.2/dropzone-min.js"
integrity="sha384-PwiT+fWTPpIySx6DrH1FKraKo+LvVpOClsjx0TSdMYTKi7BR1hR149f4VHLUUnfA" integrity="sha384-apgrFcysJQwl0SG2LvOoA/UOUUVW0bOoBmKVZOeMZkMCvWmeO+Cnw/r3P4rmbIh9"
crossorigin="anonymous" crossorigin="anonymous"
></script> ></script>
@ -78,6 +78,7 @@
b: "{{ _("B") }}" b: "{{ _("B") }}"
} }
} }
Dropzone.discover()
</script> </script>
<noscript> <noscript>

View File

@ -1309,7 +1309,7 @@ msgstr ""
#: src/templates/index.html:594 #: src/templates/index.html:594
msgid "Key" msgid "Key"
msgstr "Taste" msgstr "Kürzel"
#: src/templates/index.html:595 #: src/templates/index.html:595
msgid "Parameters and Actions" msgid "Parameters and Actions"

View File

@ -46,7 +46,6 @@ from return_code_mapper import ReturnCodeMapper
from socket_cmds_flask import SocketCmdsFlask from socket_cmds_flask import SocketCmdsFlask
from web_utils import ( from web_utils import (
working_dirs_exist,
sort_and_format_devices, sort_and_format_devices,
get_valid_scsi_ids, get_valid_scsi_ids,
map_device_types_and_names, map_device_types_and_names,
@ -125,6 +124,9 @@ def get_env_info():
"image_dir": server_info["image_dir"], "image_dir": server_info["image_dir"],
"image_root_dir": Path(server_info["image_dir"]).name, "image_root_dir": Path(server_info["image_dir"]).name,
"shared_root_dir": Path(FILE_SERVER_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"]), "cd_suffixes": tuple(server_info["sccd"]),
"rm_suffixes": tuple(server_info["scrm"]), "rm_suffixes": tuple(server_info["scrm"]),
"mo_suffixes": tuple(server_info["scmo"]), "mo_suffixes": tuple(server_info["scmo"]),
@ -219,7 +221,6 @@ def index():
Sets up data structures for and renders the index page Sets up data structures for and renders the index page
""" """
server_info = piscsi_cmd.get_server_info() server_info = piscsi_cmd.get_server_info()
working_dirs_exist((server_info["image_dir"], CFG_DIR))
devices = piscsi_cmd.list_devices() devices = piscsi_cmd.list_devices()
device_types = map_device_types_and_names(piscsi_cmd.get_device_types()["device_types"]) 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 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( return response(
template="drives.html", template="drives.html",
page_title=_("PiSCSI Create Drive"), 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 Sets up the data structures and kicks off the rendering of the file uploading page
""" """
server_info = piscsi_cmd.get_server_info() server_info = piscsi_cmd.get_server_info()
working_dirs_exist((server_info["image_dir"], CFG_DIR))
return response( return response(
template="upload.html", template="upload.html",
@ -544,7 +541,6 @@ def show_diskinfo():
if not safe_path["status"]: if not safe_path["status"]:
return response(error=True, message=safe_path["msg"]) return response(error=True, message=safe_path["msg"])
server_info = piscsi_cmd.get_server_info() 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) returncode, diskinfo = sys_cmd.get_diskinfo(Path(server_info["image_dir"]) / file_name)
if returncode == 0: if returncode == 0:
return response( return response(
@ -647,16 +643,17 @@ def log_level():
return response(error=True, message=process["msg"]) return response(error=True, message=process["msg"])
@APP.route("/scsi/attach_device", methods=["POST"]) @APP.route("/scsi/attach", methods=["POST"])
@login_required @login_required
def attach_device(): 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") scsi_id = request.form.get("scsi_id")
unit = request.form.get("unit") unit = request.form.get("unit")
device_type = request.form.get("type") device_type = request.form.get("type")
drive_name = request.form.get("drive_name") drive_name = request.form.get("drive_name")
file_name = request.form.get("file_name")
if not scsi_id: if not scsi_id:
return response(error=True, message=_("No SCSI ID specified")) return response(error=True, message=_("No SCSI ID specified"))
@ -690,11 +687,29 @@ def attach_device():
"device_type": device_type, "device_type": device_type,
"params": params, "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: if drive_props:
kwargs["vendor"] = drive_props["vendor"] kwargs["vendor"] = drive_props["vendor"]
kwargs["product"] = drive_props["product"] kwargs["product"] = drive_props["product"]
kwargs["revision"] = drive_props["revision"] kwargs["revision"] = drive_props["revision"]
kwargs["block_size"] = drive_props["block_size"] 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 = piscsi_cmd.attach_device(scsi_id, **kwargs)
process = ReturnCodeMapper.add_msg(process) process = ReturnCodeMapper.add_msg(process)
@ -711,70 +726,6 @@ def attach_device():
return response(error=True, message=process["msg"]) 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"]) @APP.route("/scsi/detach_all", methods=["POST"])
@login_required @login_required
def detach_all_devices(): def detach_all_devices():
@ -940,7 +891,27 @@ def download_to_iso():
local_file = request.form.get("file") local_file = request.form.get("file")
if iso_type == "HFS": 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": elif iso_type == "ISO-9660 Level 1":
iso_args = ["-iso-level", "1"] iso_args = ["-iso-level", "1"]
elif iso_type == "ISO-9660 Level 2": elif iso_type == "ISO-9660 Level 2":

View File

@ -8,26 +8,13 @@ from pathlib import Path
from ua_parser import user_agent_parser from ua_parser import user_agent_parser
from re import findall from re import findall
from flask import request, abort from flask import request
from flask_babel import _ from flask_babel import _
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from piscsi.sys_cmds import SysCmds 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): def get_valid_scsi_ids(devices, reserved_ids):
""" """
Takes a list of (dict)s devices, and list of (int)s reserved_ids. Takes a list of (dict)s devices, and list of (int)s reserved_ids.

View File

@ -36,6 +36,12 @@ if [ $ERROR = 1 ] ; then
exit 1 exit 1
fi 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 # Test for two known broken venv states
if test -e venv; then if test -e venv; then
GOOD_VENV=true GOOD_VENV=true

View File

@ -8,6 +8,41 @@ FILE_SIZE_1_MIB = 1048576
STATUS_SUCCESS = "success" STATUS_SUCCESS = "success"
STATUS_ERROR = "error" 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") @pytest.fixture(scope="function")
def create_test_image(request, http_client): 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}" file_name = f"{file_prefix}.{image_type}"
response = http_client.post( response = http_client.post(
"/files/create", CREATE_ENDPOINT,
data={ data={
"file_name": file_prefix, "file_name": file_prefix,
"type": image_type, "type": image_type,
@ -42,7 +77,7 @@ def create_test_image(request, http_client):
def delete(): def delete():
for image in images: 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: if response.status_code != 200 or response.json()["status"] != STATUS_SUCCESS:
warnings.warn( warnings.warn(
f"Failed to auto-delete file created with create_test_image fixture: {image}" 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") @pytest.fixture(scope="function")
def delete_file(http_client): def delete_file(http_client):
def delete(file_name): 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: if response.status_code != 200 or response.json()["status"] != STATUS_SUCCESS:
warnings.warn(f"Failed to delete file via delete_file fixture: {file_name}") 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") @pytest.fixture(scope="function")
def detach_devices(http_client): def detach_devices(http_client):
def detach(): def detach():
response = http_client.post("/scsi/detach_all") response = http_client.post(DETACH_ALL_ENDPOINT)
if response.json()["status"] == STATUS_SUCCESS: if response.json()["status"] == STATUS_SUCCESS:
return True return True
raise Exception("Failed to detach SCSI devices") raise Exception("Failed to detach SCSI devices")

View File

@ -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): 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 # Note: This test depends on the piscsi group existing and 'username' a member the group
response = http_client_unauthenticated.post( response = http_client_unauthenticated.post(
"/login", LOGIN_ENDPOINT,
data={ data={
"username": pytestconfig.getoption("piscsi_username"), "username": pytestconfig.getoption("piscsi_username"),
"password": pytestconfig.getoption("piscsi_password"), "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"] assert "env" in response_data["data"]
# route("/login", methods=["POST"])
def test_login_with_invalid_credentials(http_client_unauthenticated): def test_login_with_invalid_credentials(http_client_unauthenticated):
response = http_client_unauthenticated.post( response = http_client_unauthenticated.post(
"/login", LOGIN_ENDPOINT,
data={ data={
"username": "__INVALID_USER__", "username": "__INVALID_USER__",
"password": "__INVALID_PASS__", "password": "__INVALID_PASS__",
@ -38,7 +36,6 @@ def test_login_with_invalid_credentials(http_client_unauthenticated):
) )
# route("/logout")
def test_logout(http_client): def test_logout(http_client):
response = http_client.get("/logout") response = http_client.get(LOGOUT_ENDPOINT)
assert response.status_code == 200 assert response.status_code == 200

View File

@ -2,20 +2,24 @@ import pytest
from conftest import ( from conftest import (
SCSI_ID, SCSI_ID,
FILE_SIZE_1_MIB,
STATUS_SUCCESS, 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_device_with_image(http_client, create_test_image, detach_devices):
def test_attach_image(http_client, create_test_image, detach_devices):
test_image = create_test_image() test_image = create_test_image()
response = http_client.post( response = http_client.post(
"/scsi/attach", ATTACH_ENDPOINT,
data={ data={
"file_name": test_image, "file_name": test_image,
"file_size": FILE_SIZE_1_MIB,
"scsi_id": SCSI_ID, "scsi_id": SCSI_ID,
"unit": 0, "unit": 0,
"type": "SCHD", "type": "SCHD",
@ -26,14 +30,13 @@ def test_attach_image(http_client, create_test_image, detach_devices):
assert response.status_code == 200 assert response.status_code == 200
assert response_data["status"] == STATUS_SUCCESS assert response_data["status"] == STATUS_SUCCESS
assert response_data["messages"][0]["message"] == ( 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 # Cleanup
detach_devices() detach_devices()
# route("/scsi/attach_device", methods=["POST"])
@pytest.mark.parametrize( @pytest.mark.parametrize(
"device_name,device_config", "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 device_config["unit"] = 0
response = http_client.post( response = http_client.post(
"/scsi/attach_device", ATTACH_ENDPOINT,
data=device_config, data=device_config,
) )
@ -105,15 +108,13 @@ def test_attach_device(env, http_client, detach_devices, device_name, device_con
detach_devices() detach_devices()
# route("/scsi/detach", methods=["POST"])
def test_detach_device(http_client, create_test_image): def test_detach_device(http_client, create_test_image):
test_image = create_test_image() test_image = create_test_image()
http_client.post( http_client.post(
"/scsi/attach", ATTACH_ENDPOINT,
data={ data={
"file_name": test_image, "file_name": test_image,
"file_size": FILE_SIZE_1_MIB,
"scsi_id": SCSI_ID, "scsi_id": SCSI_ID,
"unit": 0, "unit": 0,
"type": "SCHD", "type": "SCHD",
@ -121,7 +122,7 @@ def test_detach_device(http_client, create_test_image):
) )
response = http_client.post( response = http_client.post(
"/scsi/detach", DETACH_ENDPOINT,
data={ data={
"scsi_id": SCSI_ID, "scsi_id": SCSI_ID,
"unit": 0, "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" 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): def test_detach_all_devices(http_client, create_test_image, list_attached_images):
test_images = [] test_images = []
scsi_ids = [4, 5, 6] 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) test_images.append(test_image)
http_client.post( http_client.post(
"/scsi/attach", ATTACH_ENDPOINT,
data={ data={
"file_name": test_image, "file_name": test_image,
"file_size": FILE_SIZE_1_MIB,
"scsi_id": scsi_id, "scsi_id": scsi_id,
"unit": 0, "unit": 0,
"type": "SCHD", "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 assert list_attached_images() == test_images
response = http_client.post("/scsi/detach_all") response = http_client.post(DETACH_ALL_ENDPOINT)
response_data = response.json() response_data = response.json()
assert response.status_code == 200 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() == [] assert list_attached_images() == []
# route("/scsi/eject", methods=["POST"])
def test_eject_device(http_client, create_test_image, detach_devices): def test_eject_device(http_client, create_test_image, detach_devices):
test_image = create_test_image() test_image = create_test_image()
http_client.post( http_client.post(
"/scsi/attach", ATTACH_ENDPOINT,
data={ data={
"file_name": test_image, "file_name": test_image,
"file_size": FILE_SIZE_1_MIB,
"scsi_id": SCSI_ID, "scsi_id": SCSI_ID,
"unit": 0, "unit": 0,
"type": "SCCD", # CD-ROM "type": "SCCD", # CD-ROM
@ -181,7 +178,7 @@ def test_eject_device(http_client, create_test_image, detach_devices):
) )
response = http_client.post( response = http_client.post(
"/scsi/eject", EJECT_ENDPOINT,
data={ data={
"scsi_id": SCSI_ID, "scsi_id": SCSI_ID,
"unit": 0, "unit": 0,
@ -198,15 +195,13 @@ def test_eject_device(http_client, create_test_image, detach_devices):
detach_devices() detach_devices()
# route("/scsi/info", methods=["POST"])
def test_show_device_info(http_client, create_test_image, detach_devices): def test_show_device_info(http_client, create_test_image, detach_devices):
test_image = create_test_image() test_image = create_test_image()
http_client.post( http_client.post(
"/scsi/attach", ATTACH_ENDPOINT,
data={ data={
"file_name": test_image, "file_name": test_image,
"file_size": FILE_SIZE_1_MIB,
"scsi_id": SCSI_ID, "scsi_id": SCSI_ID,
"unit": 0, "unit": 0,
"type": "SCHD", "type": "SCHD",
@ -214,7 +209,7 @@ def test_show_device_info(http_client, create_test_image, detach_devices):
) )
response = http_client.post( response = http_client.post(
"/scsi/info", INFO_ENDPOINT,
) )
response_data = response.json() response_data = response.json()
@ -228,13 +223,11 @@ def test_show_device_info(http_client, create_test_image, detach_devices):
detach_devices() detach_devices()
# route("/scsi/reserve", methods=["POST"])
# route("/scsi/release", methods=["POST"])
def test_reserve_and_release_device(http_client): def test_reserve_and_release_device(http_client):
scsi_id = 0 scsi_id = 0
response = http_client.post( response = http_client.post(
"/scsi/reserve", RESERVE_ENDPOINT,
data={ data={
"scsi_id": scsi_id, "scsi_id": scsi_id,
"memo": "TEST", "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}" assert response_data["messages"][0]["message"] == f"Reserved SCSI ID {scsi_id}"
response = http_client.post( response = http_client.post(
"/scsi/release", RELEASE_ENDPOINT,
data={ data={
"scsi_id": scsi_id, "scsi_id": scsi_id,
}, },

View File

@ -5,16 +5,26 @@ import os
from conftest import ( from conftest import (
FILE_SIZE_1_MIB, FILE_SIZE_1_MIB,
STATUS_SUCCESS, 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): def test_create_file(http_client, list_files, delete_file):
file_prefix = str(uuid.uuid4()) file_prefix = str(uuid.uuid4())
file_name = f"{file_prefix}.hds" file_name = f"{file_prefix}.hds"
response = http_client.post( response = http_client.post(
"/files/create", CREATE_ENDPOINT,
data={ data={
"file_name": file_prefix, "file_name": file_prefix,
"type": "hds", "type": "hds",
@ -34,13 +44,12 @@ def test_create_file(http_client, list_files, delete_file):
delete_file(file_name) delete_file(file_name)
# route("/files/create", methods=["POST"])
def test_create_file_with_properties(http_client, list_files, delete_file): def test_create_file_with_properties(http_client, list_files, delete_file):
file_prefix = str(uuid.uuid4()) file_prefix = str(uuid.uuid4())
file_name = f"{file_prefix}.hds" file_name = f"{file_prefix}.hds"
response = http_client.post( response = http_client.post(
"/files/create", CREATE_ENDPOINT,
data={ data={
"file_name": file_prefix, "file_name": file_prefix,
"type": "hds", "type": "hds",
@ -64,13 +73,12 @@ def test_create_file_with_properties(http_client, list_files, delete_file):
delete_file(file_name) delete_file(file_name)
# route("/files/create", methods=["POST"])
def test_create_file_and_format_hfs(http_client, list_files, delete_file): def test_create_file_and_format_hfs(http_client, list_files, delete_file):
file_prefix = str(uuid.uuid4()) file_prefix = str(uuid.uuid4())
file_name = f"{file_prefix}.hda" file_name = f"{file_prefix}.hda"
response = http_client.post( response = http_client.post(
"/files/create", CREATE_ENDPOINT,
data={ data={
"file_name": file_prefix, "file_name": file_prefix,
"type": "hda", "type": "hda",
@ -91,7 +99,6 @@ def test_create_file_and_format_hfs(http_client, list_files, delete_file):
delete_file(file_name) delete_file(file_name)
# route("/files/create", methods=["POST"])
def test_create_file_and_format_fat(env, http_client, list_files, delete_file): def test_create_file_and_format_fat(env, http_client, list_files, delete_file):
if env["is_docker"]: if env["is_docker"]:
pytest.skip("Test not supported in Docker environment.") 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" file_name = f"{file_prefix}.hdr"
response = http_client.post( response = http_client.post(
"/files/create", CREATE_ENDPOINT,
data={ data={
"file_name": file_prefix, "file_name": file_prefix,
"type": "hdr", "type": "hdr",
@ -120,13 +127,12 @@ def test_create_file_and_format_fat(env, http_client, list_files, delete_file):
delete_file(file_name) delete_file(file_name)
# route("/files/rename", methods=["POST"])
def test_rename_file(http_client, create_test_image, list_files, delete_file): def test_rename_file(http_client, create_test_image, list_files, delete_file):
original_file = create_test_image(auto_delete=False) original_file = create_test_image(auto_delete=False)
renamed_file = f"{uuid.uuid4()}.rename" renamed_file = f"{uuid.uuid4()}.rename"
response = http_client.post( response = http_client.post(
"/files/rename", RENAME_ENDPOINT,
data={"file_name": original_file, "new_file_name": renamed_file}, 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) delete_file(renamed_file)
# route("/files/copy", methods=["POST"])
def test_copy_file(http_client, create_test_image, list_files, delete_file): def test_copy_file(http_client, create_test_image, list_files, delete_file):
original_file = create_test_image() original_file = create_test_image()
copy_file = f"{uuid.uuid4()}.copy" copy_file = f"{uuid.uuid4()}.copy"
response = http_client.post( response = http_client.post(
"/files/copy", COPY_ENDPOINT,
data={ data={
"file_name": original_file, "file_name": original_file,
"copy_file_name": copy_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) delete_file(copy_file)
# route("/files/delete", methods=["POST"])
def test_delete_file(http_client, create_test_image, list_files): def test_delete_file(http_client, create_test_image, list_files):
file_name = create_test_image(auto_delete=False) 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() 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() assert file_name not in list_files()
# route("/files/extract_image", methods=["POST"])
@pytest.mark.parametrize( @pytest.mark.parametrize(
"archive_file_name,image_file_name", "archive_file_name,image_file_name",
[ [
@ -205,7 +208,7 @@ def test_extract_file(
) )
http_client.post( http_client.post(
"/files/download_url", DOWNLOAD_URL_ENDPOINT,
data={ data={
"destination": "disk_images", "destination": "disk_images",
"images_subdir": "", "images_subdir": "",
@ -214,7 +217,7 @@ def test_extract_file(
) )
response = http_client.post( response = http_client.post(
"/files/extract_image", EXTRACT_IMAGE_ENDPOINT,
data={ data={
"archive_file": archive_file_name, "archive_file": archive_file_name,
"archive_members": image_file_name, "archive_members": image_file_name,
@ -233,7 +236,6 @@ def test_extract_file(
delete_file(image_file_name) delete_file(image_file_name)
# route("/files/upload", methods=["POST"])
def test_upload_file(http_client, delete_file): def test_upload_file(http_client, delete_file):
file_name = f"{uuid.uuid4()}.test" 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))} file_data = {"file": (file_name, file.read(chunk_size))}
response = http_client.post( response = http_client.post(
"/files/upload", UPLOAD_ENDPOINT,
data=form_data, data=form_data,
files=file_data, files=file_data,
) )
@ -283,11 +285,10 @@ def test_upload_file(http_client, delete_file):
delete_file(file_name) delete_file(file_name)
# route("/files/download_image", methods=["POST"])
def test_download_image(http_client, create_test_image): def test_download_image(http_client, create_test_image):
file_name = 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.status_code == 200
assert response.headers["content-type"] == "application/octet-stream" 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) 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): def test_download_properties(http_client, list_files, delete_file):
file_prefix = str(uuid.uuid4()) file_prefix = str(uuid.uuid4())
file_name = f"{file_prefix}.hds" file_name = f"{file_prefix}.hds"
response = http_client.post( response = http_client.post(
"/files/create", CREATE_ENDPOINT,
data={ data={
"file_name": file_prefix, "file_name": file_prefix,
"type": "hds", "type": "hds",
@ -321,7 +321,7 @@ def test_download_properties(http_client, list_files, delete_file):
) )
assert file_name in list_files() 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.status_code == 200
assert response.headers["content-type"] == "application/octet-stream" 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) delete_file(file_name)
# route("/files/download_url", methods=["POST"])
def test_download_url_to_dir(env, httpserver, http_client, list_files, delete_file): def test_download_url_to_dir(env, httpserver, http_client, list_files, delete_file):
file_name = str(uuid.uuid4()) file_name = str(uuid.uuid4())
http_path = f"/images/{file_name}" 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( response = http_client.post(
"/files/download_url", DOWNLOAD_URL_ENDPOINT,
data={ data={
"destination": "disk_images", "destination": "disk_images",
"images_subdir": subdir, "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) delete_file(file_name)
# route("/files/create_iso", methods=["POST"])
def test_create_iso_from_url( def test_create_iso_from_url(
httpserver, httpserver,
http_client, http_client,
@ -392,7 +390,7 @@ def test_create_iso_from_url(
) )
response = http_client.post( response = http_client.post(
"/files/create_iso", CREATE_ISO_ENDPOINT,
data={ data={
"type": ISO_TYPE, "type": ISO_TYPE,
"url": url, "url": url,
@ -414,7 +412,6 @@ def test_create_iso_from_url(
delete_file(iso_file_name) delete_file(iso_file_name)
# route("/files/create_iso", methods=["POST"])
def test_create_iso_from_local_file( def test_create_iso_from_local_file(
http_client, http_client,
create_test_image, create_test_image,
@ -427,7 +424,7 @@ def test_create_iso_from_local_file(
ISO_TYPE = "HFS" ISO_TYPE = "HFS"
response = http_client.post( response = http_client.post(
"/files/create_iso", CREATE_ISO_ENDPOINT,
data={ data={
"type": ISO_TYPE, "type": ISO_TYPE,
"file": test_file_name, "file": test_file_name,
@ -449,12 +446,11 @@ def test_create_iso_from_local_file(
delete_file(iso_file_name) delete_file(iso_file_name)
# route("/files/diskinfo", methods=["POST"])
def test_show_diskinfo(http_client, create_test_image): def test_show_diskinfo(http_client, create_test_image):
test_image = create_test_image() test_image = create_test_image()
response = http_client.post( response = http_client.post(
"/files/diskinfo", DISKINFO_ENDPOINT,
data={ data={
"file_name": test_image, "file_name": test_image,
}, },

View File

@ -3,10 +3,16 @@ import uuid
from conftest import ( from conftest import (
FILE_SIZE_1_MIB, FILE_SIZE_1_MIB,
STATUS_SUCCESS, 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): def test_index(http_client):
response = http_client.get("/") response = http_client.get("/")
response_data = response.json() response_data = response.json()
@ -16,9 +22,8 @@ def test_index(http_client):
assert "devices" in response_data["data"] assert "devices" in response_data["data"]
# route("/env")
def test_get_env_info(http_client): def test_get_env_info(http_client):
response = http_client.get("/env") response = http_client.get(ENV_ENDPOINT)
response_data = response.json() response_data = response.json()
assert response.status_code == 200 assert response.status_code == 200
@ -26,17 +31,15 @@ def test_get_env_info(http_client):
assert "running_env" in response_data["data"] assert "running_env" in response_data["data"]
# route("/pwa/<path:pwa_path>")
def test_pwa_route(http_client): 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.status_code == 200
assert response.headers["content-disposition"] == "inline; filename=favicon.ico" assert response.headers["content-disposition"] == "inline; filename=favicon.ico"
# route("/drive/list", methods=["GET"])
def test_show_named_drive_presets(http_client): 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() response_data = response.json()
prev_drive = {"name": ""} prev_drive = {"name": ""}
@ -57,12 +60,11 @@ def test_show_named_drive_presets(http_client):
assert "files" in response_data["data"] assert "files" in response_data["data"]
# route("/drive/cdrom", methods=["POST"])
def test_create_cdrom_properties_file(env, http_client): def test_create_cdrom_properties_file(env, http_client):
file_name = f"{uuid.uuid4()}.iso" file_name = f"{uuid.uuid4()}.iso"
response = http_client.post( response = http_client.post(
"/drive/cdrom", DRIVE_CDROM_ENDPOINT,
data={ data={
"drive_name": "Sony CDU-8012", "drive_name": "Sony CDU-8012",
"file_name": file_name, "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): def test_create_image_with_properties_file(http_client, delete_file):
file_prefix = str(uuid.uuid4()) file_prefix = str(uuid.uuid4())
file_name = f"{file_prefix}.hds" file_name = f"{file_prefix}.hds"
response = http_client.post( response = http_client.post(
"/drive/create", DRIVE_CREATE_ENDPOINT,
data={ data={
"drive_name": "Miniscribe M8425", "drive_name": "Miniscribe M8425",
"size": FILE_SIZE_1_MIB, "size": FILE_SIZE_1_MIB,
@ -105,16 +106,14 @@ def test_create_image_with_properties_file(http_client, delete_file):
delete_file(file_name) delete_file(file_name)
# route("/sys/manpage", methods=["POST"])
def test_show_manpage(http_client): def test_show_manpage(http_client):
response = http_client.get("/sys/manpage?app=piscsi") response = http_client.get(MANPAGE_ENDPOINT)
response_data = response.json() response_data = response.json()
assert response.status_code == 200 assert response.status_code == 200
assert "piscsi" in response_data["data"]["manpage"] assert "piscsi" in response_data["data"]["manpage"]
# route("/healthcheck", methods=["GET"])
def test_healthcheck(http_client): def test_healthcheck(http_client):
response = http_client.get("/healthcheck") response = http_client.get(HEALTHCHECK_ENDPOINT)
assert response.status_code == 200 assert response.status_code == 200

View File

@ -1,10 +1,20 @@
import pytest import pytest
import uuid 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( @pytest.mark.parametrize(
"locale,confirm_message", "locale,confirm_message",
[ [
@ -18,7 +28,7 @@ from conftest import STATUS_SUCCESS
) )
def test_set_language(http_client, locale, confirm_message): def test_set_language(http_client, locale, confirm_message):
response = http_client.post( response = http_client.post(
"/language", LANGUAGE_ENDPOINT,
data={ data={
"locale": locale, "locale": locale,
}, },
@ -31,11 +41,10 @@ def test_set_language(http_client, locale, confirm_message):
assert response_data["messages"][0]["message"] == 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"]) @pytest.mark.parametrize("level", ["trace", "debug", "info", "warn", "err", "off"])
def test_set_log_level(http_client, level): def test_set_log_level(http_client, level):
response = http_client.post( response = http_client.post(
"/logs/level", LOG_LEVEL_ENDPOINT,
data={ data={
"level": level, "level": level,
}, },
@ -49,17 +58,16 @@ def test_set_log_level(http_client, level):
# Cleanup # Cleanup
http_client.post( http_client.post(
"/logs/level", LOG_LEVEL_ENDPOINT,
data={ data={
"level": "debug", "level": "debug",
}, },
) )
# route("/logs/show", methods=["POST"])
def test_show_logs(http_client): def test_show_logs(http_client):
response = http_client.post( response = http_client.post(
"/logs/show", LOG_SHOW_ENDPOINT,
data={ data={
"lines": 100, "lines": 100,
"scope": "piscsi", "scope": "piscsi",
@ -73,8 +81,6 @@ def test_show_logs(http_client):
assert response_data["data"]["scope"] == "piscsi" 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): def test_save_load_and_delete_configs(env, http_client):
config_name = str(uuid.uuid4()) config_name = str(uuid.uuid4())
config_json_file = f"{config_name}.json" 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 # Save the initial state to a config
response = http_client.post( response = http_client.post(
"/config/save", CONFIG_SAVE_ENDPOINT,
data={ data={
"name": config_name, "name": config_name,
}, },
@ -104,7 +110,7 @@ def test_save_load_and_delete_configs(env, http_client):
# Modify the state # Modify the state
http_client.post( http_client.post(
"/scsi/reserve", RESERVE_ENDPOINT,
data={ data={
"scsi_id": reserved_scsi_id, "scsi_id": reserved_scsi_id,
"memo": reservation_memo, "memo": reservation_memo,
@ -115,7 +121,7 @@ def test_save_load_and_delete_configs(env, http_client):
# Load the saved config # Load the saved config
response = http_client.post( response = http_client.post(
"/config/action", CONFIG_ACTION_ENDPOINT,
data={ data={
"name": config_json_file, "name": config_json_file,
"load": True, "load": True,
@ -135,7 +141,7 @@ def test_save_load_and_delete_configs(env, http_client):
# Delete the saved config # Delete the saved config
response = http_client.post( response = http_client.post(
"/config/action", CONFIG_ACTION_ENDPOINT,
data={ data={
"name": config_json_file, "name": config_json_file,
"delete": True, "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"] assert config_json_file not in http_client.get("/").json()["data"]["config_files"]
# route("/config/save", methods=["POST"]) def test_download_configs(env, http_client):
# route("/config/action", methods=["POST"])
def test_download_configs(env, http_client, delete_file):
config_name = str(uuid.uuid4()) config_name = str(uuid.uuid4())
config_json_file = f"{config_name}.json" config_json_file = f"{config_name}.json"
# Save the initial state to a config # Save the initial state to a config
response = http_client.post( response = http_client.post(
"/config/save", CONFIG_SAVE_ENDPOINT,
data={ data={
"name": config_name, "name": config_name,
}, },
@ -172,7 +176,7 @@ def test_download_configs(env, http_client, delete_file):
# Download the saved config # Download the saved config
response = http_client.post( response = http_client.post(
"/config/action", CONFIG_ACTION_ENDPOINT,
data={ data={
"name": config_json_file, "name": config_json_file,
"send": True, "send": True,
@ -185,7 +189,7 @@ def test_download_configs(env, http_client, delete_file):
# Delete the saved config # Delete the saved config
response = http_client.post( response = http_client.post(
"/config/action", CONFIG_ACTION_ENDPOINT,
data={ data={
"name": config_json_file, "name": config_json_file,
"delete": True, "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"] assert config_json_file not in http_client.get("/").json()["data"]["config_files"]
# route("/theme", methods=["POST"])
@pytest.mark.parametrize( @pytest.mark.parametrize(
"theme", "theme",
[ [
@ -206,7 +209,7 @@ def test_download_configs(env, http_client, delete_file):
) )
def test_set_theme(http_client, theme): def test_set_theme(http_client, theme):
response = http_client.post( response = http_client.post(
"/theme", THEME_ENDPOINT,
data={ data={
"name": theme, "name": theme,
}, },
@ -219,7 +222,6 @@ def test_set_theme(http_client, theme):
assert response_data["messages"][0]["message"] == f"Theme changed to '{theme}'." assert response_data["messages"][0]["message"] == f"Theme changed to '{theme}'."
# route("/theme", methods=["GET"])
@pytest.mark.parametrize( @pytest.mark.parametrize(
"theme", "theme",
[ [
@ -229,7 +231,7 @@ def test_set_theme(http_client, theme):
) )
def test_set_theme_via_query_string(http_client, theme): def test_set_theme_via_query_string(http_client, theme):
response = http_client.get( response = http_client.get(
"/theme", THEME_ENDPOINT,
params={ params={
"name": theme, "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}'." assert response_data["messages"][0]["message"] == f"Theme changed to '{theme}'."
# route("/sys/rename", methods=["POST"])
def test_rename_system(env, http_client): def test_rename_system(env, http_client):
new_name = "SYSTEM NAME TEST" new_name = "SYSTEM NAME TEST"
response = http_client.get("/env") response = http_client.get(ENV_ENDPOINT)
response_data = response.json() response_data = response.json()
old_name = response_data["data"]["system_name"] old_name = response_data["data"]["system_name"]
response = http_client.post( response = http_client.post(
"/sys/rename", SYS_RENAME_ENDPOINT,
data={ data={
"system_name": new_name, "system_name": new_name,
}, },
@ -264,13 +265,13 @@ def test_rename_system(env, http_client):
assert response_data["status"] == STATUS_SUCCESS assert response_data["status"] == STATUS_SUCCESS
assert response_data["messages"][0]["message"] == f"System name changed to '{new_name}'." 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() response_data = response.json()
assert response_data["data"]["system_name"] == new_name assert response_data["data"]["system_name"] == new_name
response = http_client.post( response = http_client.post(
"/sys/rename", SYS_RENAME_ENDPOINT,
data={ data={
"system_name": old_name, "system_name": old_name,
}, },
@ -282,7 +283,7 @@ def test_rename_system(env, http_client):
assert response_data["status"] == STATUS_SUCCESS assert response_data["status"] == STATUS_SUCCESS
assert response_data["messages"][0]["message"] == f"System name changed to '{old_name}'." 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() response_data = response.json()
assert response_data["data"]["system_name"] == old_name assert response_data["data"]["system_name"] == old_name