diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 20e3089e..202a2fdf 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @akuker @erichelgeson @rdmark +* @akuker @rdmark diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 0dc35b82..c828582e 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -4,6 +4,7 @@ - Which github revision of software: - Which board version: - Which computer is the PiSCSI connected to: +- Which OS you are using (output of 'lsb_release -a'): # Describe the issue diff --git a/.github/workflows/build_code.yml b/.github/workflows/build_code.yml index 0970e9f5..e0c38852 100644 --- a/.github/workflows/build_code.yml +++ b/.github/workflows/build_code.yml @@ -7,6 +7,19 @@ on: - 'cpp/**' - '.github/workflows/build_code.yml' - '.github/workflows/arm_cross_compile.yml' + pull_request: + paths: + - 'cpp/**' + - '.github/workflows/build_code.yml' + - '.github/workflows/arm_cross_compile.yml' + types: + - assigned + - opened + - synchronize + - reopened + branches: + - 'develop' + - 'main' jobs: fullspec: diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml index 33a84537..7c3eac8c 100644 --- a/.github/workflows/cpp.yml +++ b/.github/workflows/cpp.yml @@ -1,11 +1,20 @@ -name: C++ Tests/Analysis +name: C++ Tests; Full Static Analysis on: workflow_dispatch: push: paths: - 'cpp/**' + - 'python/**' - '.github/workflows/cpp.yml' + pull_request: + paths: + - 'cpp/**' + - 'python/**' + - '.github/workflows/cpp.yml' + branches: + - 'develop' + - 'main' env: APT_PACKAGES: libspdlog-dev libpcap-dev libevdev2 libev-dev protobuf-compiler libgtest-dev libgmock-dev @@ -40,10 +49,10 @@ jobs: env: SOURCES: cpp BUILD_WRAPPER_OUT_DIR: "$HOME/.build_wrapper_out" # Directory where build-wrapper output will be placed - SONAR_SCANNER_VERSION: 4.7.0.2747 + SONAR_SCANNER_VERSION: 5.0.1.3006 SONAR_SERVER_URL: "https://sonarcloud.io" SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - SONAR_PROJECT_KEY: "akuker_PISCSI" + SONAR_PROJECT_KEY: "akuker-PISCSI" SONAR_ORGANIZATION: "piscsi" steps: - uses: actions/checkout@v3 @@ -106,4 +115,4 @@ jobs: --define sonar.coverage.exclusions="cpp/**/test/**" --define sonar.cpd.exclusions="cpp/**/test/**" --define sonar.inclusions="cpp/**,python/**" - --define sonar.python.version=3.7,3.9 + --define sonar.python.version=3.9,3.11 diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml index 69da8317..c2f0c086 100644 --- a/.github/workflows/web.yml +++ b/.github/workflows/web.yml @@ -8,6 +8,15 @@ on: - 'python/common/**' - '.github/workflows/web.yml' - 'easyinstall.sh' + pull_request: + paths: + - 'python/web/**' + - 'python/common/**' + - '.github/workflows/web.yml' + - 'easyinstall.sh' + branches: + - 'develop' + - 'main' jobs: backend_checks: @@ -123,8 +132,12 @@ jobs: id: npm - name: Stylelint - run: npx stylelint src/static/themes/modern/style.css + run: | + npx stylelint src/static/themes/modern/style.css + npx stylelint src/static/themes/classic/style.css - name: Prettier - run: npx prettier --check src/static/themes/modern/style.css + run: | + npx prettier --check src/static/themes/modern/style.css + npx prettier --check src/static/themes/classic/style.css if: success() || failure() && steps.npm.outcome == 'success' diff --git a/README.md b/README.md index f58de439..3747d6f4 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Please check out the full story with much more detail on the [wiki](https://gith # How do I contribute? PiSCSI is using the Gitflow Workflow. A quick overview: -- The *master* branch should always reflect the contents of the last stable release +- The *main* branch should always reflect the contents of the last stable release - The *develop* branch should contain the latest tested & approved updates. Pull requests should be used to merge changes into develop. - The rest of the feature branches are for developing new features - A tag will be created for each "release". The releases will be named .. where the release number is incremented for each subsequent release tagged in the same calendar month. The first release of the month of January 2021 is called "21.01.01", the second one in the same month "21.01.02" and so on. diff --git a/cpp/Makefile b/cpp/Makefile index 1a5f95b0..c5d0b83d 100644 --- a/cpp/Makefile +++ b/cpp/Makefile @@ -7,8 +7,6 @@ CROSS_COMPILE = CXX = $(CROSS_COMPILE)g++ -AR = $(CROSS_COMPILE)ar -RANLIB = $(CROSS_COMPILE)ranlib ## DEBUG=1 : A Debug build includes the debugger symbols ## and disables compiler optimization. Typically, @@ -17,29 +15,20 @@ DEBUG ?= 0 ifeq ($(DEBUG), 1) # Debug compiler flags CXXFLAGS += -O0 -g -Wall -Wextra -DDEBUG - BUILD_TYPE = Debug else # Release compiler flags CXXFLAGS += -O3 -Wall -Werror -Wextra -DNDEBUG - BUILD_TYPE = Release endif ifeq ("$(shell uname -s)","Linux") # -Wno-psabi might not work on non-Linux platforms CXXFLAGS += -Wno-psabi endif -# Depending on the GCC version the compilation flags differ -GCCVERSION10 := $(shell expr `$(CXX) -dumpversion` \>= 10) - -CXXFLAGS += -std=c++17 -iquote . -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE -MD -MP +CXXFLAGS += -std=c++20 -iquote . -D_FILE_OFFSET_BITS=64 -DFMT_HEADER_ONLY -DSPDLOG_FMT_EXTERNAL -MD -MP ## EXTRA_FLAGS : Can be used to pass special purpose flags CXXFLAGS += $(EXTRA_FLAGS) -ifeq "$(GCCVERSION10)" "1" - CXXFLAGS += -DFMT_HEADER_ONLY -endif - ## CONNECT_TYPE=FULLSPEC : Specify the type of PiSCSI board type ## that you are using. The typical options are @@ -50,7 +39,7 @@ endif CONNECT_TYPE ?= FULLSPEC ifdef CONNECT_TYPE -CXXFLAGS += -DCONNECT_TYPE_$(CONNECT_TYPE) + CXXFLAGS += -DCONNECT_TYPE_$(CONNECT_TYPE) endif PISCSI = piscsi @@ -78,54 +67,55 @@ BIN_ALL = \ $(BINDIR)/$(PISCSI) \ $(BINDIR)/$(SCSICTL) \ $(BINDIR)/$(SCSIMON) \ - $(BINDIR)/$(SCSIDUMP) \ $(BINDIR)/$(SCSILOOP) +# scsidump requires initiator support +ifeq ($(CONNECT_TYPE), FULLSPEC) + BIN_ALL += $(BINDIR)/$(SCSIDUMP) +endif + SRC_PROTOC = piscsi_interface.proto SRC_GENERATED = $(GENERATED_DIR)/piscsi_interface.pb.cpp SRC_PROTOBUF = \ - shared/protobuf_util.cpp \ - shared/protobuf_serializer.cpp + shared/protobuf_util.cpp SRC_SHARED = \ shared/piscsi_version.cpp \ - shared/piscsi_util.cpp + shared/piscsi_util.cpp \ + shared/network_util.cpp -SRC_PISCSI_CORE = $(shell find ./piscsi -name '*.cpp') +SRC_PISCSI_CORE = $(shell find ./piscsi -name '*.cpp' | grep -v piscsi.cpp) SRC_PISCSI_CORE += $(shell find ./controllers -name '*.cpp') SRC_PISCSI_CORE += $(shell find ./devices -name '*.cpp') SRC_PISCSI_CORE += $(shell find ./hal -name '*.cpp') -SRC_PISCSI = piscsi.cpp +SRC_PISCSI = piscsi/piscsi.cpp -SRC_SCSIMON = scsimon.cpp -SRC_SCSIMON += $(shell find ./monitor -name '*.cpp') +SRC_SCSIMON = scsimon/scsimon.cpp +SRC_SCSIMON += $(shell find ./scsimon -name '*.cpp' | grep -v scsimon.cpp) SRC_SCSIMON += $(shell find ./hal -name '*.cpp') -SRC_SCSICTL_CORE = $(shell find ./scsictl -name '*.cpp') +SRC_SCSICTL_CORE = $(shell find ./scsictl -name '*.cpp' | grep -v scsictl.cpp) -SRC_SCSICTL = scsictl.cpp +SRC_SCSICTL = scsictl/scsictl.cpp -SRC_SCSIDUMP = scsidump.cpp -SRC_SCSIDUMP += $(shell find ./scsidump -name '*.cpp') +SRC_SCSIDUMP = scsidump/scsidump.cpp +SRC_SCSIDUMP += $(shell find ./scsidump -name '*.cpp' | grep -v scsidump.cpp) SRC_SCSIDUMP += $(shell find ./hal -name '*.cpp') SRC_PISCSI_TEST = $(shell find ./test -name '*.cpp') -SRC_PISCSI_TEST += $(shell find ./scsidump -name '*.cpp') -SRC_PISCSI_TEST += $(shell find ./monitor -name '*.cpp') +SRC_PISCSI_TEST += $(shell find ./scsidump -name '*.cpp' | grep -v scsidump.cpp) -SRC_SCSILOOP = scsiloop.cpp -SRC_SCSILOOP += $(shell find ./scsiloop -name '*.cpp') +SRC_SCSILOOP = scsiloop/scsiloop.cpp +SRC_SCSILOOP += $(shell find ./scsiloop -name '*.cpp' | grep -v scsiloop.cpp) SRC_SCSILOOP += $(shell find ./hal -name '*.cpp') -vpath %.h ./ ./shared ./controllers ./devices ./monitor ./hal \ - ./hal/boards ./hal/pi_defs ./piscsi ./scsictl ./scsidump \ - ./scsiloop -vpath %.cpp ./ ./shared ./controllers ./devices ./monitor ./hal \ - ./hal/boards ./hal/pi_defs ./piscsi ./scsictl ./scsidump \ - ./scsiloop ./test +vpath %.h ./shared ./controllers ./devices ./scsimon ./hal \ + ./hal/pi_defs ./piscsi ./scsictl ./scsidump ./scsiloop +vpath %.cpp ./shared ./controllers ./devices ./scsimon ./hal \ + ./hal/pi_defs ./piscsi ./scsictl ./scsidump ./scsiloop ./test vpath %.o ./$(OBJDIR) vpath ./$(BINDIR) @@ -142,6 +132,22 @@ OBJ_SHARED := $(addprefix $(OBJDIR)/,$(notdir $(SRC_SHARED:%.cpp=%.o))) OBJ_PROTOBUF := $(addprefix $(OBJDIR)/,$(notdir $(SRC_PROTOBUF:%.cpp=%.o))) OBJ_GENERATED := $(addprefix $(OBJDIR)/,$(notdir $(SRC_GENERATED:%.cpp=%.o))) +BINARIES = $(USR_LOCAL_BIN)/$(SCSICTL) \ + $(USR_LOCAL_BIN)/$(PISCSI) \ + $(USR_LOCAL_BIN)/$(SCSIMON) \ + $(USR_LOCAL_BIN)/$(SCSILOOP) +ifeq ($(CONNECT_TYPE), FULLSPEC) + BINARIES += $(USR_LOCAL_BIN)/$(SCSIDUMP) +endif + +MAN_PAGES = $(MAN_PAGE_DIR)/piscsi.1 \ + $(MAN_PAGE_DIR)/scsictl.1 \ + $(MAN_PAGE_DIR)/scsimon.1 \ + $(MAN_PAGE_DIR)/scsiloop.1 +ifeq ($(CONNECT_TYPE), FULLSPEC) + MAN_PAGES += $(MAN_PAGE_DIR)/scsidump.1 +endif + GENERATED_DIR := generated # For the unit tests, the following functions will be "wrapped" by the linker, meaning the @@ -167,7 +173,7 @@ $(SRC_GENERATED) : $(SRC_PROTOC) protoc --cpp_out=$(GENERATED_DIR) $(SRC_PROTOC) mv $(GENERATED_DIR)/piscsi_interface.pb.cc $@ -$(OBJ_GENERATED) : $(SRC_GENERATED) +$(OBJ_GENERATED) : $(SRC_GENERATED) | $(OBJDIR) $(CXX) $(CXXFLAGS) -c $< -o $@ ## Build Targets: @@ -180,10 +186,11 @@ $(OBJ_GENERATED) : $(SRC_GENERATED) ## Note that you have to run 'make clean' before switching ## between coverage and non-coverage builds. .DEFAULT_GOAL := all -.PHONY: all ALL docs test coverage lcov -all: $(BIN_ALL) docs +.PHONY: all docs test coverage lcov -test: $(BINDIR)/$(PISCSI_TEST) +all: $(SRC_GENERATED) $(BIN_ALL) docs + +test: $(SRC_GENERATED) $(BINDIR)/$(PISCSI_TEST) $(BINDIR)/$(PISCSI_TEST) coverage: CXXFLAGS += --coverage @@ -198,10 +205,10 @@ docs: $(DOC_DIR)/piscsi_man_page.txt $(DOC_DIR)/scsictl_man_page.txt $(DOC_DIR)/ $(SRC_PISCSI_CORE) $(SRC_SCSICTL_CORE) : $(OBJ_GENERATED) -$(BINDIR)/$(PISCSI): $(SRC_GENERATED) $(OBJ_PISCSI_CORE) $(OBJ_PISCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR) - $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_PISCSI_CORE) $(OBJ_PISCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lpcap -lprotobuf -lstdc++fs +$(BINDIR)/$(PISCSI): $(OBJ_GENERATED) $(OBJ_PISCSI_CORE) $(OBJ_PISCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR) + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_PISCSI_CORE) $(OBJ_PISCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lpcap -lprotobuf -$(BINDIR)/$(SCSICTL): $(SRC_GENERATED) $(OBJ_SCSICTL_CORE) $(OBJ_SCSICTL) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR) +$(BINDIR)/$(SCSICTL): $(OBJ_GENERATED) $(OBJ_SCSICTL_CORE) $(OBJ_SCSICTL) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR) $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_SCSICTL_CORE) $(OBJ_SCSICTL) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lprotobuf $(BINDIR)/$(SCSIDUMP): $(OBJ_SCSIDUMP) $(OBJ_SHARED) | $(BINDIR) @@ -213,8 +220,8 @@ $(BINDIR)/$(SCSIMON): $(OBJ_SCSIMON) $(OBJ_SHARED) | $(BINDIR) $(BINDIR)/$(SCSILOOP): $(OBJ_SHARED) $(OBJ_SCSILOOP) | $(BINDIR) $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_SHARED) $(OBJ_SCSILOOP) -$(BINDIR)/$(PISCSI_TEST): $(SRC_GENERATED) $(OBJ_PISCSI_CORE) $(OBJ_SCSICTL_CORE) $(OBJ_PISCSI_TEST) $(OBJ_SCSICTL_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR) - $(CXX) $(CXXFLAGS) $(LDFLAGS) $(TEST_WRAPS) -o $@ $(OBJ_PISCSI_CORE) $(OBJ_SCSICTL_CORE) $(OBJ_PISCSI_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lpcap -lprotobuf -lstdc++fs -lgmock -lgtest +$(BINDIR)/$(PISCSI_TEST): $(OBJ_GENERATED) $(OBJ_PISCSI_CORE) $(OBJ_SCSICTL_CORE) $(OBJ_PISCSI_TEST) $(OBJ_SCSICTL_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR) + $(CXX) $(CXXFLAGS) $(LDFLAGS) $(TEST_WRAPS) -o $@ $(OBJ_PISCSI_CORE) $(OBJ_SCSICTL_CORE) $(OBJ_PISCSI_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lpcap -lprotobuf -lgmock -lgtest # Phony rules for building individual utilities .PHONY: $(PISCSI) $(SCSICTL) $(SCSIDUMP) $(SCSIMON) $(PISCSI_TEST) $(SCSILOOP) @@ -245,16 +252,8 @@ clean: ## * sudo systemctl start piscsi .PHONY: install install: \ - $(MAN_PAGE_DIR)/piscsi.1 \ - $(MAN_PAGE_DIR)/scsictl.1 \ - $(MAN_PAGE_DIR)/scsimon.1 \ - $(MAN_PAGE_DIR)/scsiloop.1 \ - $(MAN_PAGE_DIR)/scsidump.1 \ - $(USR_LOCAL_BIN)/$(SCSICTL) \ - $(USR_LOCAL_BIN)/$(PISCSI) \ - $(USR_LOCAL_BIN)/$(SCSIMON) \ - $(USR_LOCAL_BIN)/$(SCSILOOP) \ - $(USR_LOCAL_BIN)/$(SCSIDUMP) \ + $(MAN_PAGES) \ + $(BINARIES) \ $(SYSTEMD_CONF) \ $(RSYSLOG_CONF) \ $(RSYSLOG_LOG) diff --git a/cpp/controllers/abstract_controller.cpp b/cpp/controllers/abstract_controller.cpp index 17e91984..36242a6c 100644 --- a/cpp/controllers/abstract_controller.cpp +++ b/cpp/controllers/abstract_controller.cpp @@ -3,16 +3,22 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "shared/piscsi_exceptions.h" #include "devices/primary_device.h" #include "abstract_controller.h" +#include using namespace scsi_defs; +AbstractController::AbstractController(BUS& bus, int target_id, int max_luns) : bus(bus), target_id(target_id), max_luns(max_luns) +{ + device_logger.SetIdAndLun(target_id, -1); +} + void AbstractController::AllocateCmd(size_t size) { if (size > ctrl.cmd.size()) { @@ -40,9 +46,8 @@ unordered_set> AbstractController::GetDevices() const { unordered_set> devices; - for (const auto& [id, lun] : luns) { - devices.insert(lun); - } + // "luns | views:values" is not supported by the bullseye compiler + ranges::transform(luns, inserter(devices, devices.begin()), [] (const auto& l) { return l.second; } ); return devices; } @@ -56,100 +61,66 @@ void AbstractController::Reset() { SetPhase(phase_t::busfree); - ctrl.status = status::GOOD; - ctrl.message = 0x00; - ctrl.blocks = 0; - ctrl.next = 0; - ctrl.offset = 0; - ctrl.length = 0; + ctrl = {}; + + SetByteTransfer(false); // Reset all LUNs - for (const auto& [lun, device] : luns) { + for (const auto& [_, device] : luns) { device->Reset(); } + + GetBus().Reset(); } -void AbstractController::ProcessPhase() +void AbstractController::ProcessOnController(int id_data) { - switch (GetPhase()) { - case phase_t::busfree: - BusFree(); - break; + device_logger.SetIdAndLun(GetTargetId(), -1); - case phase_t::selection: - Selection(); - break; + const int initiator_id = ExtractInitiatorId(id_data); + if (initiator_id != UNKNOWN_INITIATOR_ID) { + LogTrace("++++ Starting processing for initiator ID " + to_string(initiator_id)); + } + else { + LogTrace("++++ Starting processing for unknown initiator ID"); + } - case phase_t::dataout: - DataOut(); - break; - - case phase_t::datain: - DataIn(); - break; - - case phase_t::command: - Command(); - break; - - case phase_t::status: - Status(); - break; - - case phase_t::msgout: - MsgOut(); - break; - - case phase_t::msgin: - MsgIn(); - break; - - default: - throw scsi_exception(sense_key::ABORTED_COMMAND); - break; + while (Process(initiator_id)) { + // Handle bus phases until the bus is free for the next command } } bool AbstractController::AddDevice(shared_ptr device) { - if (device->GetLun() < 0 || device->GetLun() >= GetMaxLuns() || HasDeviceForLun(device->GetLun())) { + const int lun = device->GetLun(); + + if (lun < 0 || lun >= GetMaxLuns() || HasDeviceForLun(lun) || device->GetController()) { return false; } - luns[device->GetLun()] = device; - device->SetController(shared_from_this()); + luns[lun] = device; + device->SetController(this); return true; } -bool AbstractController::RemoveDevice(shared_ptr device) +bool AbstractController::RemoveDevice(PrimaryDevice& device) { - device->SetController(nullptr); + device.CleanUp(); - return luns.erase(device->GetLun()) == 1; + return luns.erase(device.GetLun()) == 1; } bool AbstractController::HasDeviceForLun(int lun) const { - return luns.find(lun) != luns.end(); + return luns.contains(lun); } int AbstractController::ExtractInitiatorId(int id_data) const { - int initiator_id = UNKNOWN_INITIATOR_ID; - - if (int tmp = id_data - (1 << target_id); tmp) { - initiator_id = 0; - for (int j = 0; j < 8; j++) { - tmp >>= 1; - if (tmp) { - initiator_id++; - } - else { - break; - } - } + if (const int id_data_without_target = id_data - (1 << target_id); id_data_without_target) { + return static_cast(log2(id_data_without_target & -id_data_without_target)); } - return initiator_id; + return UNKNOWN_INITIATOR_ID; } diff --git a/cpp/controllers/abstract_controller.h b/cpp/controllers/abstract_controller.h index ae718bbf..820b98f3 100644 --- a/cpp/controllers/abstract_controller.h +++ b/cpp/controllers/abstract_controller.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // Base class for device controllers // @@ -14,17 +14,19 @@ #include "shared/scsi.h" #include "hal/bus.h" #include "phase_handler.h" -#include "controller_manager.h" +#include "devices/device_logger.h" #include #include +#include #include #include +#include using namespace std; class PrimaryDevice; -class AbstractController : public PhaseHandler, public enable_shared_from_this +class AbstractController : public PhaseHandler { public: @@ -37,19 +39,19 @@ public: RESTART_PI }; - AbstractController(shared_ptr controller_manager, int target_id, int max_luns) - : controller_manager(controller_manager), target_id(target_id), max_luns(max_luns) {} + AbstractController(BUS&, int, int); ~AbstractController() override = default; - virtual void Error(scsi_defs::sense_key, scsi_defs::asc = scsi_defs::asc::NO_ADDITIONAL_SENSE_INFORMATION, - scsi_defs::status = scsi_defs::status::CHECK_CONDITION) = 0; + virtual void Error(scsi_defs::sense_key, scsi_defs::asc = scsi_defs::asc::no_additional_sense_information, + scsi_defs::status = scsi_defs::status::check_condition) = 0; virtual void Reset(); virtual int GetInitiatorId() const = 0; // Get requested LUN based on IDENTIFY message, with LUN from the CDB as fallback virtual int GetEffectiveLun() const = 0; - virtual void ScheduleShutdown(piscsi_shutdown_mode) = 0; + void ScheduleShutdown(piscsi_shutdown_mode mode) { shutdown_mode = mode; } + piscsi_shutdown_mode GetShutdownMode() const { return shutdown_mode; } int GetTargetId() const { return target_id; } int GetMaxLuns() const { return max_luns; } @@ -58,52 +60,59 @@ public: unordered_set> GetDevices() const; shared_ptr GetDeviceForLun(int) const; bool AddDevice(shared_ptr); - bool RemoveDevice(shared_ptr); + bool RemoveDevice(PrimaryDevice&); bool HasDeviceForLun(int) const; - int ExtractInitiatorId(int) const; + void ProcessOnController(int); // TODO These should probably be extracted into a new TransferHandler class void AllocateBuffer(size_t); - vector& GetBuffer() { return ctrl.buffer; } - scsi_defs::status GetStatus() const { return ctrl.status; } + auto& GetBuffer() { return ctrl.buffer; } + auto GetStatus() const { return ctrl.status; } void SetStatus(scsi_defs::status s) { ctrl.status = s; } - uint32_t GetLength() const { return ctrl.length; } + auto GetLength() const { return ctrl.length; } void SetLength(uint32_t l) { ctrl.length = l; } - uint32_t GetBlocks() const { return ctrl.blocks; } + bool HasBlocks() const { return ctrl.blocks; } void SetBlocks(uint32_t b) { ctrl.blocks = b; } void DecrementBlocks() { --ctrl.blocks; } - uint64_t GetNext() const { return ctrl.next; } + auto GetNext() const { return ctrl.next; } void SetNext(uint64_t n) { ctrl.next = n; } void IncrementNext() { ++ctrl.next; } int GetMessage() const { return ctrl.message; } void SetMessage(int m) { ctrl.message = m; } - vector& GetCmd() { return ctrl.cmd; } - int GetCmd(int index) const { return ctrl.cmd[index]; } + auto GetCmd() const { return ctrl.cmd; } + int GetCmdByte(int index) const { return ctrl.cmd[index]; } bool IsByteTransfer() const { return is_byte_transfer; } void SetByteTransfer(bool); - uint32_t GetBytesToTransfer() const { return bytes_to_transfer; } + auto GetBytesToTransfer() const { return bytes_to_transfer; } void SetBytesToTransfer(uint32_t b) { bytes_to_transfer = b; } protected: - shared_ptr GetControllerManager() const { return controller_manager.lock(); } - inline BUS& GetBus() const { return controller_manager.lock()->GetBus(); } + BUS& GetBus() const { return bus; } - scsi_defs::scsi_command GetOpcode() const { return static_cast(ctrl.cmd[0]); } + auto GetOpcode() const { return static_cast(ctrl.cmd[0]); } int GetLun() const { return (ctrl.cmd[1] >> 5) & 0x07; } - void ProcessPhase(); - void AllocateCmd(size_t); + void SetCmdByte(int index, int value) { ctrl.cmd[index] = value; } + // TODO These should probably be extracted into a new TransferHandler class bool HasValidLength() const { return ctrl.length != 0; } int GetOffset() const { return ctrl.offset; } void ResetOffset() { ctrl.offset = 0; } void UpdateOffsetAndLength() { ctrl.offset += ctrl.length; ctrl.length = 0; } + void LogTrace(const string& s) const { device_logger.Trace(s); } + void LogDebug(const string& s) const { device_logger.Debug(s); } + void LogInfo(const string& s) const { device_logger.Info(s); } + void LogWarn(const string& s) const { device_logger.Warn(s); } + void LogError(const string& s) const { device_logger.Error(s); } + private: + int ExtractInitiatorId(int) const; + using ctrl_t = struct _ctrl_t { // Command data, dynamically resized if required vector cmd = vector(16); @@ -121,7 +130,9 @@ private: ctrl_t ctrl = {}; - weak_ptr controller_manager; + BUS& bus; + + DeviceLogger device_logger; // Logical units of this controller mapped to their LUN numbers unordered_map> luns; @@ -132,4 +143,6 @@ private: bool is_byte_transfer = false; uint32_t bytes_to_transfer = 0; + + piscsi_shutdown_mode shutdown_mode = piscsi_shutdown_mode::NONE; }; diff --git a/cpp/controllers/controller_manager.cpp b/cpp/controllers/controller_manager.cpp index 9f877cdb..3b55833d 100644 --- a/cpp/controllers/controller_manager.cpp +++ b/cpp/controllers/controller_manager.cpp @@ -3,29 +3,37 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "devices/device_factory.h" #include "devices/primary_device.h" #include "scsi_controller.h" #include "controller_manager.h" using namespace std; -bool ControllerManager::AttachToScsiController(int id, shared_ptr device) +shared_ptr ControllerManager::CreateScsiController(BUS& bus, int id) const { - auto controller = FindController(id); - if (controller != nullptr) { + auto controller = make_shared(bus, id); + controller->Init(); + + return controller; +} + +bool ControllerManager::AttachToController(BUS& bus, int id, shared_ptr device) +{ + if (auto controller = FindController(id); controller != nullptr) { + if (controller->HasDeviceForLun(device->GetLun())) { + return false; + } + return controller->AddDevice(device); } - // If there is no LUN yet the first LUN must be LUN 0 - if (device->GetLun() == 0) { - controller = make_shared(shared_from_this(), id); - - if (controller->AddDevice(device)) { + // If this is LUN 0 create a new controller + if (!device->GetLun()) { + if (auto controller = CreateScsiController(bus, id); controller->AddDevice(device)) { controllers[id] = controller; return true; @@ -35,20 +43,37 @@ bool ControllerManager::AttachToScsiController(int id, shared_ptr return false; } -bool ControllerManager::DeleteController(shared_ptr controller) +bool ControllerManager::DeleteController(const AbstractController& controller) { - return controllers.erase(controller->GetTargetId()) == 1; -} - -shared_ptr ControllerManager::IdentifyController(int data) const -{ - for (const auto& [id, controller] : controllers) { - if (data & (1 << controller->GetTargetId())) { - return controller; - } + for (const auto& device : controller.GetDevices()) { + device->CleanUp(); } - return nullptr; + return controllers.erase(controller.GetTargetId()) == 1; +} + +void ControllerManager::DeleteAllControllers() +{ + unordered_set> values; + ranges::transform(controllers, inserter(values, values.begin()), [] (const auto& controller) { return controller.second; } ); + + for (const auto& controller : values) { + DeleteController(*controller); + } + + assert(controllers.empty()); +} + +AbstractController::piscsi_shutdown_mode ControllerManager::ProcessOnController(int id_data) const +{ + if (const auto& it = ranges::find_if(controllers, [&] (const auto& c) { return (id_data & (1 << c.first)); } ); + it != controllers.end()) { + (*it).second->ProcessOnController(id_data); + + return (*it).second->GetShutdownMode(); + } + + return AbstractController::piscsi_shutdown_mode::NONE; } shared_ptr ControllerManager::FindController(int target_id) const @@ -57,11 +82,15 @@ shared_ptr ControllerManager::FindController(int target_id) return it == controllers.end() ? nullptr : it->second; } +bool ControllerManager::HasController(int target_id) const { + return controllers.contains(target_id); +} + unordered_set> ControllerManager::GetAllDevices() const { unordered_set> devices; - for (const auto& [id, controller] : controllers) { + for (const auto& [_, controller] : controllers) { const auto& d = controller->GetDevices(); devices.insert(d.begin(), d.end()); } @@ -69,12 +98,12 @@ unordered_set> ControllerManager::GetAllDevices() cons return devices; } -void ControllerManager::DeleteAllControllers() +bool ControllerManager::HasDeviceForIdAndLun(int id, int lun) const { - controllers.clear(); + return GetDeviceForIdAndLun(id, lun) != nullptr; } -shared_ptr ControllerManager::GetDeviceByIdAndLun(int id, int lun) const +shared_ptr ControllerManager::GetDeviceForIdAndLun(int id, int lun) const { if (const auto& controller = FindController(id); controller != nullptr) { return controller->GetDeviceForLun(lun); diff --git a/cpp/controllers/controller_manager.h b/cpp/controllers/controller_manager.h index dd145e07..43f6177c 100644 --- a/cpp/controllers/controller_manager.h +++ b/cpp/controllers/controller_manager.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // Keeps track of and manages the controllers // @@ -11,36 +11,41 @@ #pragma once +#include "hal/bus.h" +#include "controllers/abstract_controller.h" #include #include #include -#include "hal/bus.h" using namespace std; -class AbstractController; +class ScsiController; class PrimaryDevice; -class ControllerManager : public enable_shared_from_this +class ControllerManager { - BUS& bus; - - unordered_map> controllers; - public: - explicit ControllerManager(BUS& bus) : bus(bus) {} + ControllerManager() = default; ~ControllerManager() = default; - // Maximum number of controller devices - static const int DEVICE_MAX = 8; - - inline BUS& GetBus() const { return bus; } - bool AttachToScsiController(int, shared_ptr); - bool DeleteController(shared_ptr); - shared_ptr IdentifyController(int) const; - shared_ptr FindController(int) const; - unordered_set> GetAllDevices() const; + bool AttachToController(BUS&, int, shared_ptr); + bool DeleteController(const AbstractController&); void DeleteAllControllers(); - shared_ptr GetDeviceByIdAndLun(int, int) const; + AbstractController::piscsi_shutdown_mode ProcessOnController(int) const; + shared_ptr FindController(int) const; + bool HasController(int) const; + unordered_set> GetAllDevices() const; + bool HasDeviceForIdAndLun(int, int) const; + shared_ptr GetDeviceForIdAndLun(int, int) const; + + static int GetScsiIdMax() { return 8; } + static int GetScsiLunMax() { return 32; } + +private: + + shared_ptr CreateScsiController(BUS&, int) const; + + // Controllers mapped to their device IDs + unordered_map> controllers; }; diff --git a/cpp/controllers/phase_handler.cpp b/cpp/controllers/phase_handler.cpp new file mode 100644 index 00000000..664bd095 --- /dev/null +++ b/cpp/controllers/phase_handler.cpp @@ -0,0 +1,22 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator PiSCSI +// for Raspberry Pi +// +// Copyright (C) 2023 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include "phase_handler.h" + +void PhaseHandler::Init() +{ + phase_executors[phase_t::busfree] = [this] () { BusFree(); }; + phase_executors[phase_t::selection] = [this] () { Selection(); }; + phase_executors[phase_t::dataout] = [this] () { DataOut(); }; + phase_executors[phase_t::datain] = [this] () { DataIn(); }; + phase_executors[phase_t::command] = [this] () { Command(); }; + phase_executors[phase_t::status] = [this] () { Status(); }; + phase_executors[phase_t::msgout] = [this] () { MsgOut(); }; + phase_executors[phase_t::msgin] = [this] () { MsgIn(); }; +} diff --git a/cpp/controllers/phase_handler.h b/cpp/controllers/phase_handler.h index a50adf39..949c122d 100644 --- a/cpp/controllers/phase_handler.h +++ b/cpp/controllers/phase_handler.h @@ -3,15 +3,18 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once #include "shared/scsi.h" +#include "shared/piscsi_exceptions.h" +#include +#include - using namespace scsi_defs; +using namespace scsi_defs; class PhaseHandler { @@ -22,6 +25,8 @@ public: PhaseHandler() = default; virtual ~PhaseHandler() = default; + void Init(); + virtual void BusFree() = 0; virtual void Selection() = 0; virtual void Command() = 0; @@ -31,7 +36,7 @@ public: virtual void MsgIn() = 0; virtual void MsgOut() = 0; - virtual phase_t Process(int) = 0; + virtual bool Process(int) = 0; protected: @@ -45,4 +50,18 @@ protected: bool IsDataOut() const { return phase == phase_t::dataout; } bool IsMsgIn() const { return phase == phase_t::msgin; } bool IsMsgOut() const { return phase == phase_t::msgout; } + + void ProcessPhase() const + { + try { + phase_executors.at(phase)(); + } + catch(const out_of_range&) { + throw scsi_exception(sense_key::aborted_command); + } + } + +private: + + unordered_map> phase_executors; }; diff --git a/cpp/controllers/scsi_controller.cpp b/cpp/controllers/scsi_controller.cpp index 20aaa136..09752595 100644 --- a/cpp/controllers/scsi_controller.cpp +++ b/cpp/controllers/scsi_controller.cpp @@ -6,7 +6,7 @@ // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) // Copyright (C) 2014-2020 GIMONS // Copyright (C) akuker -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // Licensed under the BSD 3-Clause License. // See LICENSE file in the project root folder. @@ -16,7 +16,9 @@ #include "shared/piscsi_exceptions.h" #include "hal/gpiobus.h" #include "hal/systimer.h" -#include "devices/interfaces/byte_writer.h" +#include "controllers/controller_manager.h" +#include "devices/scsi_host_bridge.h" +#include "devices/scsi_daynaport.h" #include "devices/mode_page_device.h" #include "devices/disk.h" #include "scsi_controller.h" @@ -28,11 +30,8 @@ using namespace scsi_defs; -ScsiController::ScsiController(shared_ptr controller_manager, int target_id) - : AbstractController(controller_manager, target_id, LUN_MAX) +ScsiController::ScsiController(BUS& bus, int target_id) : AbstractController(bus, target_id, ControllerManager::GetScsiLunMax()) { - logger.SetIdAndLun(target_id, -1); - // The initial buffer size will default to either the default buffer size OR // the size of an Ethernet message, whichever is larger. AllocateBuffer(std::max(DEFAULT_BUFFER_SIZE, ETH_FRAME_LEN + 16 + ETH_FCS_LEN)); @@ -44,26 +43,21 @@ void ScsiController::Reset() execstart = 0; identified_lun = -1; + initiator_id = UNKNOWN_INITIATOR_ID; - scsi.atnmsg = false; - scsi.msc = 0; - scsi.msb = {}; - - SetByteTransfer(false); + scsi = {}; } -phase_t ScsiController::Process(int id) +bool ScsiController::Process(int id) { GetBus().Acquire(); if (GetBus().GetRST()) { - logger.Warn("RESET signal received!"); + LogWarn("RESET signal received!"); Reset(); - GetBus().Reset(); - - return GetPhase(); + return false; } initiator_id = id; @@ -72,23 +66,20 @@ phase_t ScsiController::Process(int id) ProcessPhase(); } catch(const scsi_exception&) { - // Any exception should have been handled during the phase processing - logger.Error("Unhandled SCSI error, resetting controller and bus and entering bus free phase"); + LogError("Unhandled SCSI error, resetting controller and bus and entering bus free phase"); Reset(); - GetBus().Reset(); BusFree(); } - return GetPhase(); + return !IsBusFree(); } void ScsiController::BusFree() { if (!IsBusFree()) { - logger.Trace("Bus free phase"); - + LogTrace("Bus Free phase"); SetPhase(phase_t::busfree); GetBus().SetREQ(false); @@ -98,7 +89,7 @@ void ScsiController::BusFree() GetBus().SetBSY(false); // Initialize status and message - SetStatus(status::GOOD); + SetStatus(status::good); SetMessage(0x00); // Initialize ATN message reception status @@ -108,39 +99,6 @@ void ScsiController::BusFree() SetByteTransfer(false); - if (shutdown_mode != piscsi_shutdown_mode::NONE) { - // Prepare the shutdown by flushing all caches - for (const auto& device : GetControllerManager()->GetAllDevices()) { - device->FlushCache(); - } - } - - // When the bus is free PiSCSI or the Pi may be shut down. - // This code has to be executed in the bus free phase and thus has to be located in the controller. - switch(shutdown_mode) { - case piscsi_shutdown_mode::STOP_PISCSI: - logger.Info("PiSCSI shutdown requested"); - exit(EXIT_SUCCESS); - break; - - case piscsi_shutdown_mode::STOP_PI: - logger.Info("Raspberry Pi shutdown requested"); - if (system("init 0") == -1) { - logger.Error("Raspberry Pi shutdown failed: " + string(strerror(errno))); - } - break; - - case piscsi_shutdown_mode::RESTART_PI: - logger.Info("Raspberry Pi restart requested"); - if (system("init 6") == -1) { - logger.Error("Raspberry Pi restart failed: " + string(strerror(errno))); - } - break; - - default: - break; - } - return; } @@ -153,18 +111,7 @@ void ScsiController::BusFree() void ScsiController::Selection() { if (!IsSelection()) { - // A different device controller was selected - if (int id = 1 << GetTargetId(); (static_cast(GetBus().GetDAT()) & id) == 0) { - return; - } - - // Abort if there is no LUN for this controller - if (!GetLunCount()) { - return; - } - - logger.Trace("Selection phase"); - + LogTrace("Selection phase"); SetPhase(phase_t::selection); // Raise BSY and respond @@ -174,6 +121,8 @@ void ScsiController::Selection() // Selection completed if (!GetBus().GetSEL() && GetBus().GetBSY()) { + LogTrace("Selection completed"); + // Message out phase if ATN=1, otherwise command phase if (GetBus().GetATN()) { MsgOut(); @@ -186,8 +135,7 @@ void ScsiController::Selection() void ScsiController::Command() { if (!IsCommand()) { - logger.Trace("Command phase"); - + LogTrace("Command phase"); SetPhase(phase_t::command); GetBus().SetMSG(false); @@ -198,9 +146,9 @@ void ScsiController::Command() if (actual_count == 0) { stringstream s; s << "Received unknown command: $" << setfill('0') << setw(2) << hex << GetBuffer()[0]; - logger.Trace(s.str()); + LogTrace(s.str()); - Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE); + Error(sense_key::illegal_request, asc::invalid_command_operation_code); return; } @@ -210,16 +158,16 @@ void ScsiController::Command() if (actual_count != command_byte_count) { stringstream s; s << "Command byte count mismatch for command $" << setfill('0') << setw(2) << hex << GetBuffer()[0]; - logger.Error(s.str() + ": expected " + to_string(command_byte_count) + " bytes, received" + LogError(s.str() + ": expected " + to_string(command_byte_count) + " bytes, received" + to_string(actual_count) + " byte(s)"); - Error(sense_key::ABORTED_COMMAND); + Error(sense_key::aborted_command); return; } // Command data transfer AllocateCmd(command_byte_count); for (int i = 0; i < command_byte_count; i++) { - GetCmd()[i] = GetBuffer()[i]; + SetCmdByte(i, GetBuffer()[i]); } SetLength(0); @@ -230,13 +178,15 @@ void ScsiController::Command() void ScsiController::Execute() { - stringstream s; - s << "Controller is executing " << command_mapping.find(GetOpcode())->second.second << ", CDB $" - << setfill('0') << hex; - for (int i = 0; i < BUS::GetCommandByteCount(static_cast(GetOpcode())); i++) { - s << setw(2) << GetCmd(i); - } - logger.Debug(s.str()); + if (spdlog::get_level() == spdlog::level::trace) { + stringstream s; + s << "Controller is executing " << command_mapping.find(GetOpcode())->second.second << ", CDB $" + << setfill('0') << hex; + for (int i = 0; i < BUS::GetCommandByteCount(static_cast(GetOpcode())); i++) { + s << setw(2) << GetCmdByte(i); + } + LogTrace(s.str()); + } // Initialization for data transfer ResetOffset(); @@ -245,15 +195,15 @@ void ScsiController::Execute() // Discard pending sense data from the previous command if the current command is not REQUEST SENSE if (GetOpcode() != scsi_command::eCmdRequestSense) { - SetStatus(status::GOOD); + SetStatus(status::good); } int lun = GetEffectiveLun(); if (!HasDeviceForLun(lun)) { if (GetOpcode() != scsi_command::eCmdInquiry && GetOpcode() != scsi_command::eCmdRequestSense) { - logger.Trace("Invalid LUN " + to_string(lun)); + LogTrace("Invalid LUN " + to_string(lun)); - Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_LUN); + Error(sense_key::illegal_request, asc::invalid_lun); return; } @@ -265,7 +215,7 @@ void ScsiController::Execute() // SCSI-2 4.4.3 Incorrect logical unit handling if (GetOpcode() == scsi_command::eCmdInquiry && !HasDeviceForLun(lun)) { - logger.Trace("Reporting LUN" + to_string(GetEffectiveLun()) + " as not supported"); + LogTrace("Reporting LUN" + to_string(GetEffectiveLun()) + " as not supported"); GetBuffer().data()[0] = 0x7f; @@ -279,7 +229,7 @@ void ScsiController::Execute() device->SetStatusCode(0); } - if (device->CheckReservation(initiator_id, GetOpcode(), GetCmd(4) & 0x01)) { + if (device->CheckReservation(initiator_id, GetOpcode(), GetCmdByte(4) & 0x01)) { try { device->Dispatch(GetOpcode()); } @@ -288,7 +238,7 @@ void ScsiController::Execute() } } else { - Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT); + Error(sense_key::aborted_command, asc::no_additional_sense_information, status::reservation_conflict); } } @@ -304,9 +254,8 @@ void ScsiController::Status() } stringstream s; - s << "Status Phase, status is $" << setfill('0') << setw(2) << hex << static_cast(GetStatus()); - logger.Trace(s.str()); - + s << "Status phase, status is $" << setfill('0') << setw(2) << hex << static_cast(GetStatus()); + LogTrace(s.str()); SetPhase(phase_t::status); // Signal line operated by the target @@ -329,8 +278,7 @@ void ScsiController::Status() void ScsiController::MsgIn() { if (!IsMsgIn()) { - logger.Trace("Message In phase"); - + LogTrace("Message In phase"); SetPhase(phase_t::msgin); GetBus().SetMSG(true); @@ -347,8 +295,6 @@ void ScsiController::MsgIn() void ScsiController::MsgOut() { if (!IsMsgOut()) { - logger.Trace("Message Out phase"); - // process the IDENTIFY message if (IsSelection()) { scsi.atnmsg = true; @@ -356,6 +302,7 @@ void ScsiController::MsgOut() scsi.msb = {}; } + LogTrace("Message Out phase"); SetPhase(phase_t::msgout); GetBus().SetMSG(true); @@ -387,8 +334,7 @@ void ScsiController::DataIn() return; } - logger.Trace("Entering Data In phase"); - + LogTrace("Data In phase"); SetPhase(phase_t::datain); GetBus().SetMSG(false); @@ -417,11 +363,9 @@ void ScsiController::DataOut() return; } - logger.Trace("Data Out phase"); - + LogTrace("Data Out phase"); SetPhase(phase_t::dataout); - // Signal line operated by the target GetBus().SetMSG(false); GetBus().SetCD(false); GetBus().SetIO(false); @@ -441,7 +385,6 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status) // Reset check if (GetBus().GetRST()) { Reset(); - GetBus().Reset(); return; } @@ -453,9 +396,9 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status) } int lun = GetEffectiveLun(); - if (!HasDeviceForLun(lun) || asc == asc::INVALID_LUN) { + if (!HasDeviceForLun(lun) || asc == asc::invalid_lun) { if (!HasDeviceForLun(0)) { - logger.Error("No LUN 0"); + LogError("No LUN 0"); SetStatus(status); SetMessage(0x00); @@ -468,11 +411,11 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status) lun = 0; } - if (sense_key != sense_key::NO_SENSE || asc != asc::NO_ADDITIONAL_SENSE_INFORMATION) { + if (sense_key != sense_key::no_sense || asc != asc::no_additional_sense_information) { stringstream s; - s << setfill('0') << setw(2) << hex << "Error status: Sense Key $" << static_cast(sense_key) - << ", ASC $" << static_cast(asc); - logger.Debug(s.str()); + s << setfill('0') << hex << "Error status: Sense Key $" << setw(2) << static_cast(sense_key) + << ", ASC $" << setw(2) << static_cast(asc); + LogDebug(s.str()); // Set Sense Key and ASC for a subsequent REQUEST SENSE GetDeviceForLun(lun)->SetStatusCode((static_cast(sense_key) << 16) | (static_cast(asc) << 8)); @@ -481,8 +424,6 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status) SetStatus(status); SetMessage(0x00); - logger.Trace("Error (to status phase)"); - Status(); } @@ -492,7 +433,7 @@ void ScsiController::Send() assert(GetBus().GetIO()); if (HasValidLength()) { - logger.Trace("Sending data, offset: " + to_string(GetOffset()) + ", length: " + to_string(GetLength())); + LogTrace("Sending data, offset: " + to_string(GetOffset()) + ", length: " + to_string(GetLength())); // The delay should be taken from the respective LUN, but as there are no Daynaport drivers for // LUNs other than 0 this work-around works. @@ -500,7 +441,7 @@ void ScsiController::Send() HasDeviceForLun(0) ? GetDeviceForLun(0)->GetSendDelay() : 0); len != static_cast(GetLength())) { // If you cannot send all, move to status phase - Error(sense_key::ABORTED_COMMAND); + Error(sense_key::aborted_command); return; } @@ -510,56 +451,46 @@ void ScsiController::Send() } DecrementBlocks(); - bool result = true; // Processing after data collection (read/data-in only) - if (IsDataIn() && GetBlocks() != 0) { + if (IsDataIn() && HasBlocks()) { // set next buffer (set offset, length) - result = XferIn(GetBuffer()); - logger.Trace("Processing after data collection. Blocks: " + to_string(GetBlocks())); + if (!XferIn(GetBuffer())) { + // If result FALSE, move to status phase + Error(sense_key::aborted_command); + return; + } + + LogTrace("Processing after data collection"); } - // If result FALSE, move to status phase - if (!result) { - Error(sense_key::ABORTED_COMMAND); - return; - } - - // Continue sending if block !=0 - if (GetBlocks() != 0){ - logger.Trace("Continuing to send. Blocks: " + to_string(GetBlocks())); + // Continue sending if blocks != 0 + if (HasBlocks()) { + LogTrace("Continuing to send"); assert(HasValidLength()); assert(GetOffset() == 0); return; } // Move to next phase - logger.Trace("Moving to next phase: " + string(BUS::GetPhaseStrRaw(GetPhase()))); + LogTrace("All data transferred, moving to next phase: " + string(BUS::GetPhaseStrRaw(GetPhase()))); switch (GetPhase()) { - // Message in phase case phase_t::msgin: // Completed sending response to extended message of IDENTIFY message if (scsi.atnmsg) { - // flag off scsi.atnmsg = false; - // command phase Command(); } else { - // Bus free phase BusFree(); } break; - // Data-in Phase case phase_t::datain: - // status phase Status(); break; - // status phase case phase_t::status: - // Message in phase SetLength(1); SetBlocks(1); GetBuffer()[0] = (uint8_t)GetMessage(); @@ -578,13 +509,13 @@ void ScsiController::Receive() assert(!GetBus().GetIO()); if (HasValidLength()) { - logger.Trace("Receiving data, transfer length: " + to_string(GetLength()) + " byte(s)"); + LogTrace("Receiving data, transfer length: " + to_string(GetLength()) + " byte(s)"); // If not able to receive all, move to status phase if (uint32_t len = GetBus().ReceiveHandShake(GetBuffer().data() + GetOffset(), GetLength()); len != GetLength()) { - logger.Error("Not able to receive " + to_string(GetLength()) + " byte(s) of data, only received " + LogError("Not able to receive " + to_string(GetLength()) + " byte(s) of data, only received " + to_string(len)); - Error(sense_key::ABORTED_COMMAND); + Error(sense_key::aborted_command); return; } } @@ -603,10 +534,10 @@ void ScsiController::Receive() bool result = true; // Processing after receiving data (by phase) - logger.Trace("Phase: " + string(BUS::GetPhaseStrRaw(GetPhase()))); + LogTrace("Phase: " + string(BUS::GetPhaseStrRaw(GetPhase()))); switch (GetPhase()) { case phase_t::dataout: - if (GetBlocks() == 0) { + if (!HasBlocks()) { // End with this buffer result = XferOut(false); } else { @@ -633,12 +564,12 @@ void ScsiController::Receive() // If result FALSE, move to status phase if (!result) { - Error(sense_key::ABORTED_COMMAND); + Error(sense_key::aborted_command); return; } - // Continue to receive if block != 0 - if (GetBlocks() != 0) { + // Continue to receive if blocks != 0 + if (HasBlocks()) { assert(HasValidLength()); assert(GetOffset() == 0); return; @@ -692,7 +623,7 @@ void ScsiController::ReceiveBytes() bool result = true; // Processing after receiving data (by phase) - logger.Trace("Phase: " + string(BUS::GetPhaseStrRaw(GetPhase()))); + LogTrace("Phase: " + string(BUS::GetPhaseStrRaw(GetPhase()))); switch (GetPhase()) { case phase_t::dataout: result = XferOut(false); @@ -716,7 +647,7 @@ void ScsiController::ReceiveBytes() // If result FALSE, move to status phase if (!result) { - Error(sense_key::ABORTED_COMMAND); + Error(sense_key::aborted_command); return; } @@ -752,15 +683,14 @@ bool ScsiController::XferOut(bool cont) SetByteTransfer(false); auto device = GetDeviceForLun(GetEffectiveLun()); - return device != nullptr ? device->WriteByteSequence(GetBuffer(), count) : false; + return device != nullptr ? device->WriteByteSequence(span(GetBuffer().data(), count)) : false; } -void ScsiController::DataOutNonBlockOriented() +void ScsiController::DataOutNonBlockOriented() const { assert(IsDataOut()); switch (GetOpcode()) { - // TODO Check why these cases are needed case scsi_command::eCmdWrite6: case scsi_command::eCmdWrite10: case scsi_command::eCmdWrite16: @@ -768,18 +698,8 @@ void ScsiController::DataOutNonBlockOriented() case scsi_command::eCmdWriteLong16: case scsi_command::eCmdVerify10: case scsi_command::eCmdVerify16: - break; - case scsi_command::eCmdModeSelect6: - case scsi_command::eCmdModeSelect10: { - if (auto device = dynamic_pointer_cast(GetDeviceForLun(GetEffectiveLun())); - device != nullptr) { - device->ModeSelect(GetOpcode(), GetCmd(), GetBuffer(), GetOffset()); - } - else { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE); - } - } + case scsi_command::eCmdModeSelect10: break; case scsi_command::eCmdSetMcastAddr: @@ -790,7 +710,7 @@ void ScsiController::DataOutNonBlockOriented() stringstream s; s << "Unexpected Data Out phase for command $" << setfill('0') << setw(2) << hex << static_cast(GetOpcode()); - logger.Warn(s.str()); + LogWarn(s.str()); break; } } @@ -807,9 +727,9 @@ bool ScsiController::XferIn(vector& buf) stringstream s; s << "Command: $" << setfill('0') << setw(2) << hex << static_cast(GetOpcode()); - logger.Trace(s.str()); + LogTrace(s.str()); - int lun = GetEffectiveLun(); + const int lun = GetEffectiveLun(); if (!HasDeviceForLun(lun)) { return false; } @@ -821,7 +741,7 @@ bool ScsiController::XferIn(vector& buf) case scsi_command::eCmdRead16: // Read from disk try { - SetLength(dynamic_pointer_cast(GetDeviceForLun(lun))->Read(GetCmd(), buf, GetNext())); + SetLength(dynamic_pointer_cast(GetDeviceForLun(lun))->Read(buf, GetNext())); } catch(const scsi_exception&) { // If there is an error, go to the status phase @@ -878,13 +798,20 @@ bool ScsiController::XferOutBlockOriented(bool cont) case scsi_command::eCmdWrite6: case scsi_command::eCmdWrite10: case scsi_command::eCmdWrite16: - // TODO Verify has to verify, not to write - case scsi_command::eCmdVerify10: - case scsi_command::eCmdVerify16: { - // Special case for SCBR and SCDP - if (auto byte_writer = dynamic_pointer_cast(device); byte_writer) { - if (!byte_writer->WriteBytes(GetCmd(), GetBuffer(), GetLength())) { + // TODO Get rid of this special case for SCBR + if (auto bridge = dynamic_pointer_cast(device); bridge) { + if (!bridge->ReadWrite(GetCmd(), GetBuffer())) { + return false; + } + + ResetOffset(); + break; + } + + // TODO Get rid of this special case for SCDP + if (auto daynaport = dynamic_pointer_cast(device); daynaport) { + if (!daynaport->Write(GetCmd(), GetBuffer())) { return false; } @@ -898,7 +825,7 @@ bool ScsiController::XferOutBlockOriented(bool cont) } try { - disk->Write(GetCmd(), GetBuffer(), GetNext() - 1); + disk->Write(GetBuffer(), GetNext() - 1); } catch(const scsi_exception& e) { Error(e.get_sense_key(), e.get_asc()); @@ -908,24 +835,41 @@ bool ScsiController::XferOutBlockOriented(bool cont) // If you do not need the next block, end here IncrementNext(); - if (!cont) { - break; + if (cont) { + SetLength(disk->GetSectorSizeInBytes()); + ResetOffset(); + } + + break; + } + + case scsi_command::eCmdVerify10: + case scsi_command::eCmdVerify16: + { + auto disk = dynamic_pointer_cast(device); + if (disk == nullptr) { + return false; + } + + // If you do not need the next block, end here + IncrementNext(); + if (cont) { + SetLength(disk->GetSectorSizeInBytes()); + ResetOffset(); } - SetLength(disk->GetSectorSizeInBytes()); - ResetOffset(); break; } case scsi_command::eCmdSetMcastAddr: - logger.Trace("Done with DaynaPort Set Multicast Address"); + LogTrace("Done with DaynaPort Set Multicast Address"); break; default: stringstream s; s << "Received an unexpected command ($" << setfill('0') << setw(2) << hex << static_cast(GetOpcode()) << ")"; - logger.Warn(s.str()); + LogWarn(s.str()); break; } @@ -934,15 +878,15 @@ bool ScsiController::XferOutBlockOriented(bool cont) void ScsiController::ProcessCommand() { - uint32_t len = GPIOBUS::GetCommandByteCount(GetBuffer()[0]); + const uint32_t len = GPIOBUS::GetCommandByteCount(GetBuffer()[0]); stringstream s; s << "CDB=$" << setfill('0') << setw(2) << hex; for (uint32_t i = 0; i < len; i++) { - GetCmd()[i] = GetBuffer()[i]; - s << GetCmd(i); + SetCmdByte(i, GetBuffer()[i]); + s << GetCmdByte(i); } - logger.Trace(s.str()); + LogTrace(s.str()); Execute(); } @@ -954,13 +898,13 @@ void ScsiController::ParseMessage() const uint8_t message_type = scsi.msb[i]; if (message_type == 0x06) { - logger.Trace("Received ABORT message"); + LogTrace("Received ABORT message"); BusFree(); return; } if (message_type == 0x0C) { - logger.Trace("Received BUS DEVICE RESET message"); + LogTrace("Received BUS DEVICE RESET message"); scsi.syncoffset = 0; if (auto device = GetDeviceForLun(identified_lun); device != nullptr) { device->DiscardReservation(); @@ -971,11 +915,11 @@ void ScsiController::ParseMessage() if (message_type >= 0x80) { identified_lun = static_cast(message_type) & 0x1F; - logger.Trace("Received IDENTIFY message for LUN " + to_string(identified_lun)); + LogTrace("Received IDENTIFY message for LUN " + to_string(identified_lun)); } if (message_type == 0x01) { - logger.Trace("Received EXTENDED MESSAGE"); + LogTrace("Received EXTENDED MESSAGE"); // Check only when synchronous transfer is possible if (!scsi.syncenable || scsi.msb[i + 2] != 0x01) { @@ -1046,4 +990,4 @@ void ScsiController::Sleep() SysTimer::SleepUsec(MIN_EXEC_TIME - time); } execstart = 0; -} \ No newline at end of file +} diff --git a/cpp/controllers/scsi_controller.h b/cpp/controllers/scsi_controller.h index fd5093bc..2fe3d525 100644 --- a/cpp/controllers/scsi_controller.h +++ b/cpp/controllers/scsi_controller.h @@ -15,8 +15,6 @@ #pragma once #include "shared/scsi.h" -#include "controller_manager.h" -#include "devices/device_logger.h" #include "abstract_controller.h" #include @@ -52,20 +50,17 @@ class ScsiController : public AbstractController public: - // Maximum number of logical units - static inline const int LUN_MAX = 32; - - explicit ScsiController(shared_ptr, int); + ScsiController(BUS&, int); ~ScsiController() override = default; void Reset() override; - phase_t Process(int) override; + bool Process(int) override; int GetEffectiveLun() const override; - void Error(scsi_defs::sense_key sense_key, scsi_defs::asc asc = scsi_defs::asc::NO_ADDITIONAL_SENSE_INFORMATION, - scsi_defs::status status = scsi_defs::status::CHECK_CONDITION) override; + void Error(scsi_defs::sense_key sense_key, scsi_defs::asc asc = scsi_defs::asc::no_additional_sense_information, + scsi_defs::status status = scsi_defs::status::check_condition) override; int GetInitiatorId() const override { return initiator_id; } @@ -79,16 +74,8 @@ public: void DataIn() override; void DataOut() override; - // TODO Make non-virtual private as soon as SysTimer calls do not segfault anymore on a regular PC, - // e.g. by using ifdef __arm__. Currently the unit tests require this method to be public. - virtual void Execute(); - - void ScheduleShutdown(piscsi_shutdown_mode mode) override { shutdown_mode = mode; } - private: - DeviceLogger logger; - // Execution start time uint32_t execstart = 0; @@ -106,9 +93,12 @@ private: bool XferOutBlockOriented(bool); void ReceiveBytes(); - void DataOutNonBlockOriented(); + void DataOutNonBlockOriented() const; void Receive(); + // TODO Make non-virtual as soon as SysTimer calls do not segfault anymore on a regular PC, e.g. by using ifdef __arm__. + virtual void Execute(); + void ProcessCommand(); void ParseMessage(); void ProcessMessage(); @@ -116,7 +106,5 @@ private: void Sleep(); scsi_t scsi = {}; - - AbstractController::piscsi_shutdown_mode shutdown_mode = AbstractController::piscsi_shutdown_mode::NONE; }; diff --git a/cpp/devices/cd_track.h b/cpp/devices/cd_track.h index cd712484..02302323 100644 --- a/cpp/devices/cd_track.h +++ b/cpp/devices/cd_track.h @@ -14,6 +14,7 @@ #pragma once +#include #include using namespace std; diff --git a/cpp/devices/cfilesystem.cpp b/cpp/devices/cfilesystem.cpp index 7531c70a..381ed933 100644 --- a/cpp/devices/cfilesystem.cpp +++ b/cpp/devices/cfilesystem.cpp @@ -17,8 +17,8 @@ // //--------------------------------------------------------------------------- -#include "shared/log.h" #include "cfilesystem.h" +#include #include #include #include @@ -3507,18 +3507,18 @@ int CFileSys::GetDPB(uint32_t nUnit, Human68k::dpb_t* pDpb) const // Acquire sector data if (!m_cEntry.GetCapacityCache(nUnit, &cap)) { // Carry out an extra media check here because it may be skipped when doing a manual eject - if (!m_cEntry.isEnable(nUnit)) - goto none; - // Media check - if (m_cEntry.isMediaOffline(nUnit)) - goto none; - - // Get drive status - m_cEntry.GetCapacity(nUnit, &cap); + if (!m_cEntry.isEnable(nUnit) || m_cEntry.isMediaOffline(nUnit)) { + cap.clusters = 4; // This is totally fine, right? + cap.sectors = 64; + cap.bytes = 512; + } + else { + // Get drive status + m_cEntry.GetCapacity(nUnit, &cap); + } } } else { - none: cap.clusters = 4; // This is totally fine, right? cap.sectors = 64; cap.bytes = 512; diff --git a/cpp/devices/cfilesystem.h b/cpp/devices/cfilesystem.h index 8c4c8703..e44339a2 100644 --- a/cpp/devices/cfilesystem.h +++ b/cpp/devices/cfilesystem.h @@ -13,6 +13,15 @@ #pragma once +#include +#include +#include +#include +#include +#include +#include +#include + using TCHAR = char; static const int FILEPATH_MAX = 260; diff --git a/cpp/devices/ctapdriver.cpp b/cpp/devices/ctapdriver.cpp index 2c868e51..85fc2adc 100644 --- a/cpp/devices/ctapdriver.cpp +++ b/cpp/devices/ctapdriver.cpp @@ -9,126 +9,71 @@ // //--------------------------------------------------------------------------- -#include "shared/log.h" #include "shared/piscsi_util.h" -#include "shared/piscsi_exceptions.h" +#include "shared/network_util.h" #include #include #include #include "ctapdriver.h" +#include #include #include #include #ifdef __linux__ -#include -#include #include #include #endif using namespace std; using namespace piscsi_util; +using namespace network_util; -//--------------------------------------------------------------------------- -// -// Initialization -// -//--------------------------------------------------------------------------- -static bool br_setif(int br_socket_fd, const char* bridgename, const char* ifname, bool add) { +const string CTapDriver::BRIDGE_NAME = "piscsi_bridge"; + +static string br_setif(int br_socket_fd, const string& bridgename, const string& ifname, bool add) { #ifndef __linux__ - return false; + return "if_nametoindex: Linux is required"; #else ifreq ifr; - ifr.ifr_ifindex = if_nametoindex(ifname); + ifr.ifr_ifindex = if_nametoindex(ifname.c_str()); if (ifr.ifr_ifindex == 0) { - LOGERROR("Can't if_nametoindex %s: %s", ifname, strerror(errno)) - return false; + return "Can't if_nametoindex " + ifname; } - strncpy(ifr.ifr_name, bridgename, IFNAMSIZ - 1); + strncpy(ifr.ifr_name, bridgename.c_str(), IFNAMSIZ - 1); //NOSONAR Using strncpy is safe if (ioctl(br_socket_fd, add ? SIOCBRADDIF : SIOCBRDELIF, &ifr) < 0) { - LOGERROR("Can't ioctl %s: %s", add ? "SIOCBRADDIF" : "SIOCBRDELIF", strerror(errno)) - return false; + return "Can't ioctl " + string(add ? "SIOCBRADDIF" : "SIOCBRDELIF"); } - return true; + return ""; #endif } -CTapDriver::~CTapDriver() -{ - if (m_hTAP != -1) { - if (int br_socket_fd; (br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) { - LOGERROR("Can't open bridge socket: %s", strerror(errno)) - } else { - LOGDEBUG("brctl delif %s piscsi0", BRIDGE_NAME) - if (!br_setif(br_socket_fd, BRIDGE_NAME, "piscsi0", false)) { //NOSONAR No exception is raised here - LOGWARN("Warning: Removing piscsi0 from the bridge failed.") - LOGWARN("You may need to manually remove the piscsi0 tap device from the bridge") - } - close(br_socket_fd); - } - - // Release TAP defice - close(m_hTAP); - } - - if (m_pcap_dumper != nullptr) { - pcap_dump_close(m_pcap_dumper); - } - - if (m_pcap != nullptr) { - pcap_close(m_pcap); - } -} - -static bool ip_link(int fd, const char* ifname, bool up) { +string ip_link(int fd, const char* ifname, bool up) { #ifndef __linux__ - return false; + return "Can't ip_link: Linux is required"; #else ifreq ifr; - strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); // Need to save room for null terminator - int err = ioctl(fd, SIOCGIFFLAGS, &ifr); - if (err) { - LOGERROR("Can't ioctl SIOCGIFFLAGS: %s", strerror(errno)) - return false; + strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); //NOSONAR Using strncpy is safe + if (ioctl(fd, SIOCGIFFLAGS, &ifr)) { + return "Can't ioctl SIOCGIFFLAGS"; } ifr.ifr_flags &= ~IFF_UP; if (up) { ifr.ifr_flags |= IFF_UP; } - err = ioctl(fd, SIOCSIFFLAGS, &ifr); - if (err) { - LOGERROR("Can't ioctl SIOCSIFFLAGS: %s", strerror(errno)) - return false; + if (ioctl(fd, SIOCSIFFLAGS, &ifr)) { + return "Can't ioctl SIOCSIFFLAGS"; } - return true; + return ""; #endif } -static bool is_interface_up(string_view interface) { - string file = "/sys/class/net/"; - file += interface; - file += "/carrier"; - - bool status = true; - FILE *fp = fopen(file.c_str(), "r"); - if (!fp || fgetc(fp) != '1') { - status = false; - } - - if (fp) { - fclose(fp); - } - - return status; -} - -bool CTapDriver::Init(const unordered_map& const_params) +bool CTapDriver::Init(const param_map& const_params) { #ifndef __linux__ return false; #else - unordered_map params = const_params; + param_map params = const_params; stringstream s(params["interface"]); string interface; while (getline(s, interface, ',')) { @@ -136,36 +81,33 @@ bool CTapDriver::Init(const unordered_map& const_params) } inet = params["inet"]; - LOGTRACE("Opening tap device") + spdlog::trace("Opening tap device"); // TAP device initilization if ((m_hTAP = open("/dev/net/tun", O_RDWR)) < 0) { - LOGERROR("Can't open tun: %s", strerror(errno)) + LogErrno("Can't open tun"); return false; } - LOGTRACE("Opened tap device %d", m_hTAP) - // IFF_NO_PI for no extra packet information ifreq ifr = {}; ifr.ifr_flags = IFF_TAP | IFF_NO_PI; - string dev = "piscsi0"; - strncpy(ifr.ifr_name, dev.c_str(), IFNAMSIZ - 1); + strncpy(ifr.ifr_name, "piscsi0", IFNAMSIZ - 1); //NOSONAR Using strncpy is safe - LOGTRACE("Going to open %s", ifr.ifr_name) + spdlog::trace("Going to open " + string(ifr.ifr_name)); - int ret = ioctl(m_hTAP, TUNSETIFF, (void *)&ifr); + const int ret = ioctl(m_hTAP, TUNSETIFF, (void *)&ifr); if (ret < 0) { - LOGERROR("Can't ioctl TUNSETIFF: %s", strerror(errno)) + LogErrno("Can't ioctl TUNSETIFF"); close(m_hTAP); return false; } - LOGTRACE("Return code from ioctl was %d", ret) + spdlog::trace("Return code from ioctl was " + to_string(ret)); const int ip_fd = socket(PF_INET, SOCK_DGRAM, 0); if (ip_fd < 0) { - LOGERROR("Can't open ip socket: %s", strerror(errno)) + LogErrno("Can't open ip socket"); close(m_hTAP); return false; @@ -173,179 +115,72 @@ bool CTapDriver::Init(const unordered_map& const_params) const int br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0); if (br_socket_fd < 0) { - LOGERROR("Can't open bridge socket: %s", strerror(errno)) + LogErrno("Can't open bridge socket"); close(m_hTAP); close(ip_fd); return false; } + auto cleanUp = [&] (const string& error) { + LogErrno(error); + close(m_hTAP); + close(ip_fd); + close(br_socket_fd); + return false; + }; + // Check if the bridge has already been created - string sys_file = "/sys/class/net/"; - sys_file += BRIDGE_NAME; - if (access(sys_file.c_str(), F_OK)) { - LOGINFO("%s is not yet available", BRIDGE_NAME) + // TODO Find an alternative to accessing a file, there is most likely a system call/ioctl + if (access(string("/sys/class/net/" + BRIDGE_NAME).c_str(), F_OK)) { + spdlog::trace("Checking which interface is available for creating the bridge " + BRIDGE_NAME); - LOGTRACE("Checking which interface is available for creating the bridge") - - string bridge_interface; - for (const string& iface : interfaces) { - if (is_interface_up(iface)) { - LOGTRACE("%s", string("Interface " + iface + " is up").c_str()) - - bridge_interface = iface; - break; - } - else { - LOGTRACE("%s", string("Interface " + iface + " is not available or is not up").c_str()) - } + const auto& it = ranges::find_if(interfaces, [] (const string& iface) { return IsInterfaceUp(iface); } ); + if (it == interfaces.end()) { + return cleanUp("No interface is up, not creating bridge " + BRIDGE_NAME); } - if (bridge_interface.empty()) { - LOGERROR("No interface is up, not creating bridge") - return false; - } + const string bridge_interface = *it; - LOGINFO("Creating %s for interface %s", BRIDGE_NAME, bridge_interface.c_str()) + spdlog::info("Creating " + BRIDGE_NAME + " for interface " + bridge_interface); if (bridge_interface == "eth0") { - LOGTRACE("brctl addbr %s", BRIDGE_NAME) - - if (ioctl(br_socket_fd, SIOCBRADDBR, BRIDGE_NAME) < 0) { - LOGERROR("Can't ioctl SIOCBRADDBR: %s", strerror(errno)) - - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; - } - - LOGTRACE("brctl addif %s %s", BRIDGE_NAME, bridge_interface.c_str()) - - if (!br_setif(br_socket_fd, BRIDGE_NAME, bridge_interface.c_str(), true)) { - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; + if (const string error = SetUpEth0(br_socket_fd, bridge_interface); !error.empty()) { + return cleanUp(error); } } - else { - string address = inet; - string netmask = "255.255.255.0"; //NOSONAR This hardcoded IP address is safe - if (size_t separatorPos = inet.find('/'); separatorPos != string::npos) { - address = inet.substr(0, separatorPos); - - int m; - if (!GetAsUnsignedInt(inet.substr(separatorPos + 1), m) || m < 8 || m > 32) { - LOGERROR("Invalid CIDR netmask notation '%s'", inet.substr(separatorPos + 1).c_str()) - - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; - } - - // long long is required for compatibility with 32 bit platforms - const auto mask = (long long)(pow(2, 32) - (1 << (32 - m))); - netmask = to_string((mask >> 24) & 0xff) + '.' + to_string((mask >> 16) & 0xff) + '.' + - to_string((mask >> 8) & 0xff) + '.' + to_string(mask & 0xff); - - } - - LOGTRACE("brctl addbr %s", BRIDGE_NAME) - - if (ioctl(br_socket_fd, SIOCBRADDBR, BRIDGE_NAME) < 0) { - LOGERROR("Can't ioctl SIOCBRADDBR: %s", strerror(errno)) - - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; - } - - ifreq ifr_a; - ifr_a.ifr_addr.sa_family = AF_INET; - strncpy(ifr_a.ifr_name, BRIDGE_NAME, IFNAMSIZ); - if (auto addr = (sockaddr_in*)&ifr_a.ifr_addr; - inet_pton(AF_INET, address.c_str(), &addr->sin_addr) != 1) { - LOGERROR("Can't convert '%s' into a network address: %s", address.c_str(), strerror(errno)) - - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; - } - - ifreq ifr_n; - ifr_n.ifr_addr.sa_family = AF_INET; - strncpy(ifr_n.ifr_name, BRIDGE_NAME, IFNAMSIZ); - if (auto mask = (sockaddr_in*)&ifr_n.ifr_addr; - inet_pton(AF_INET, netmask.c_str(), &mask->sin_addr) != 1) { - LOGERROR("Can't convert '%s' into a netmask: %s", netmask.c_str(), strerror(errno)) - - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; - } - - LOGTRACE("ip address add %s dev %s", inet.c_str(), BRIDGE_NAME) - - if (ioctl(ip_fd, SIOCSIFADDR, &ifr_a) < 0 || ioctl(ip_fd, SIOCSIFNETMASK, &ifr_n) < 0) { - LOGERROR("Can't ioctl SIOCSIFADDR or SIOCSIFNETMASK: %s", strerror(errno)) - - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; - } + else if (const string error = SetUpNonEth0(br_socket_fd, ip_fd, inet); !error.empty()) { + return cleanUp(error); } - LOGTRACE("ip link set dev %s up", BRIDGE_NAME) + spdlog::trace(">ip link set dev " + BRIDGE_NAME + " up"); - if (!ip_link(ip_fd, BRIDGE_NAME, true)) { - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; + if (const string error = ip_link(ip_fd, BRIDGE_NAME.c_str(), true); !error.empty()) { + return cleanUp(error); } } - else - { - LOGINFO("%s is already available", BRIDGE_NAME) + else { + spdlog::info(BRIDGE_NAME + " is already available"); } - LOGTRACE("ip link set piscsi0 up") + spdlog::trace(">ip link set piscsi0 up"); - if (!ip_link(ip_fd, "piscsi0", true)) { - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; + if (const string error = ip_link(ip_fd, "piscsi0", true); !error.empty()) { + return cleanUp(error); } - LOGTRACE("brctl addif %s piscsi0", BRIDGE_NAME) + spdlog::trace(">brctl addif " + BRIDGE_NAME + " piscsi0"); - if (!br_setif(br_socket_fd, BRIDGE_NAME, "piscsi0", true)) { - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; + if (const string error = br_setif(br_socket_fd, BRIDGE_NAME, "piscsi0", true); !error.empty()) { + return cleanUp(error); } - // Get MAC address - LOGTRACE("Getting the MAC address") + spdlog::trace("Getting the MAC address"); ifr.ifr_addr.sa_family = AF_INET; if (ioctl(m_hTAP, SIOCGIFHWADDR, &ifr) < 0) { - LOGERROR("Can't ioctl SIOCGIFHWADDR: %s", strerror(errno)) - - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; + return cleanUp("Can't ioctl SIOCGIFHWADDR"); } - LOGTRACE("Got the MAC") // Save MAC address memcpy(m_MacAddr.data(), ifr.ifr_hwaddr.sa_data, m_MacAddr.size()); @@ -353,50 +188,132 @@ bool CTapDriver::Init(const unordered_map& const_params) close(ip_fd); close(br_socket_fd); - LOGINFO("Tap device %s created", ifr.ifr_name) + spdlog::info("Tap device " + string(ifr.ifr_name) + " created"); return true; #endif } -void CTapDriver::OpenDump(const string& path) { - if (m_pcap == nullptr) { - m_pcap = pcap_open_dead(DLT_EN10MB, 65535); - } - if (m_pcap_dumper != nullptr) { - pcap_dump_close(m_pcap_dumper); - } - m_pcap_dumper = pcap_dump_open(m_pcap, path.c_str()); - if (m_pcap_dumper == nullptr) { - LOGERROR("Can't open pcap file: %s", pcap_geterr(m_pcap)) - throw io_exception("Can't open pcap file"); - } +void CTapDriver::CleanUp() const +{ + if (m_hTAP != -1) { + if (const int br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0); br_socket_fd < 0) { + LogErrno("Can't open bridge socket"); + } else { + spdlog::trace(">brctl delif " + BRIDGE_NAME + " piscsi0"); + if (const string error = br_setif(br_socket_fd, BRIDGE_NAME, "piscsi0", false); !error.empty()) { + spdlog::warn("Warning: Removing piscsi0 from the bridge failed: " + error); + spdlog::warn("You may need to manually remove the piscsi0 tap device from the bridge"); + } + close(br_socket_fd); + } - LOGTRACE("%s Opened %s for dumping", __PRETTY_FUNCTION__, path.c_str()) + // Release TAP device + close(m_hTAP); + } } -bool CTapDriver::Enable() const +param_map CTapDriver::GetDefaultParams() const +{ + return { + { "interface", Join(GetNetworkInterfaces(), ",") }, + { "inet", DEFAULT_IP } + }; +} + +pair CTapDriver::ExtractAddressAndMask(const string& s) +{ + string address = s; + string netmask = "255.255.255.0"; //NOSONAR This hardcoded IP address is safe + if (const auto& components = Split(s, '/', 2); components.size() == 2) { + address = components[0]; + + int m; + if (!GetAsUnsignedInt(components[1], m) || m < 8 || m > 32) { + spdlog::error("Invalid CIDR netmask notation '" + components[1] + "'"); + return { "", "" }; + } + + // long long is required for compatibility with 32 bit platforms + const auto mask = (long long)(pow(2, 32) - (1 << (32 - m))); + netmask = to_string((mask >> 24) & 0xff) + '.' + to_string((mask >> 16) & 0xff) + '.' + + to_string((mask >> 8) & 0xff) + '.' + to_string(mask & 0xff); + } + + return { address, netmask }; +} + +string CTapDriver::SetUpEth0(int socket_fd, const string& bridge_interface) +{ +#ifdef __linux__ + spdlog::trace(">brctl addbr " + BRIDGE_NAME); + + if (ioctl(socket_fd, SIOCBRADDBR, BRIDGE_NAME.c_str()) < 0) { + return "Can't ioctl SIOCBRADDBR"; + } + + spdlog::trace(">brctl addif " + BRIDGE_NAME + " " + bridge_interface); + + if (const string error = br_setif(socket_fd, BRIDGE_NAME, bridge_interface, true); !error.empty()) { + return error; + } +#endif + + return ""; +} + +string CTapDriver::SetUpNonEth0(int socket_fd, int ip_fd, const string& s) +{ +#ifdef __linux__ + const auto [address, netmask] = ExtractAddressAndMask(s); + if (address.empty() || netmask.empty()) { + return "Error extracting inet address and netmask"; + } + + spdlog::trace(">brctl addbr " + BRIDGE_NAME); + + if (ioctl(socket_fd, SIOCBRADDBR, BRIDGE_NAME.c_str()) < 0) { + return "Can't ioctl SIOCBRADDBR"; + } + + ifreq ifr_a; + ifr_a.ifr_addr.sa_family = AF_INET; + strncpy(ifr_a.ifr_name, BRIDGE_NAME.c_str(), IFNAMSIZ - 1); //NOSONAR Using strncpy is safe + if (auto addr = (sockaddr_in*)&ifr_a.ifr_addr; + inet_pton(AF_INET, address.c_str(), &addr->sin_addr) != 1) { + return "Can't convert '" + address + "' into a network address"; + } + + ifreq ifr_n; + ifr_n.ifr_addr.sa_family = AF_INET; + strncpy(ifr_n.ifr_name, BRIDGE_NAME.c_str(), IFNAMSIZ - 1); //NOSONAR Using strncpy is safe + if (auto mask = (sockaddr_in*)&ifr_n.ifr_addr; + inet_pton(AF_INET, netmask.c_str(), &mask->sin_addr) != 1) { + return "Can't convert '" + netmask + "' into a netmask"; + } + + spdlog::trace(">ip address add " + s + " dev " + BRIDGE_NAME); + + if (ioctl(ip_fd, SIOCSIFADDR, &ifr_a) < 0 || ioctl(ip_fd, SIOCSIFNETMASK, &ifr_n) < 0) { + return "Can't ioctl SIOCSIFADDR or SIOCSIFNETMASK"; + } +#endif + + return ""; +} + +string CTapDriver::IpLink(bool enable) const { const int fd = socket(PF_INET, SOCK_DGRAM, 0); - LOGDEBUG("%s: ip link set piscsi0 up", __PRETTY_FUNCTION__) - const bool result = ip_link(fd, "piscsi0", true); + spdlog::trace(string(">ip link set piscsi0 ") + (enable ? "up" : "down")); + const string result = ip_link(fd, "piscsi0", enable); close(fd); return result; } -bool CTapDriver::Disable() const +void CTapDriver::Flush() const { - const int fd = socket(PF_INET, SOCK_DGRAM, 0); - LOGDEBUG("%s: ip link set piscsi0 down", __PRETTY_FUNCTION__) - const bool result = ip_link(fd, "piscsi0", false); - close(fd); - return result; -} - -void CTapDriver::Flush() -{ - LOGTRACE("%s", __PRETTY_FUNCTION__) - while (PendingPackets()) { + while (HasPendingPackets()) { array m_garbage_buffer; (void)Receive(m_garbage_buffer.data()); } @@ -409,12 +326,7 @@ void CTapDriver::GetMacAddr(uint8_t *mac) const memcpy(mac, m_MacAddr.data(), m_MacAddr.size()); } -//--------------------------------------------------------------------------- -// -// Receive -// -//--------------------------------------------------------------------------- -bool CTapDriver::PendingPackets() const +bool CTapDriver::HasPendingPackets() const { assert(m_hTAP != -1); @@ -424,20 +336,16 @@ bool CTapDriver::PendingPackets() const fds.events = POLLIN | POLLERR; fds.revents = 0; poll(&fds, 1, 0); - LOGTRACE("%s %u revents", __PRETTY_FUNCTION__, fds.revents) - if (!(fds.revents & POLLIN)) { - return false; - } else { - return true; - } + spdlog::trace(to_string(fds.revents) + " revents"); + return fds.revents & POLLIN; } // See https://stackoverflow.com/questions/21001659/crc32-algorithm-implementation-in-c-without-a-look-up-table-and-with-a-public-li -uint32_t CTapDriver::Crc32(const uint8_t *buf, int length) { +uint32_t CTapDriver::Crc32(span data) { uint32_t crc = 0xffffffff; - for (int i = 0; i < length; i++) { - crc ^= buf[i]; - for (int j = 0; j < 8; j++) { + for (const auto d: data) { + crc ^= d; + for (int i = 0; i < 8; i++) { const uint32_t mask = -(static_cast(crc) & 1); crc = (crc >> 1) ^ (0xEDB88320 & mask); } @@ -445,19 +353,19 @@ uint32_t CTapDriver::Crc32(const uint8_t *buf, int length) { return ~crc; } -int CTapDriver::Receive(uint8_t *buf) +int CTapDriver::Receive(uint8_t *buf) const { assert(m_hTAP != -1); // Check if there is data that can be received - if (!PendingPackets()) { + if (!HasPendingPackets()) { return 0; } // Receive auto dwReceived = static_cast(read(m_hTAP, buf, ETH_FRAME_LEN)); if (dwReceived == static_cast(-1)) { - LOGWARN("%s Error occured while receiving a packet", __PRETTY_FUNCTION__) + spdlog::warn("Error occured while receiving a packet"); return 0; } @@ -466,49 +374,28 @@ int CTapDriver::Receive(uint8_t *buf) // We need to add the Frame Check Status (FCS) CRC back onto the end of the packet. // The Linux network subsystem removes it, since most software apps shouldn't ever // need it. - const int crc = Crc32(buf, dwReceived); + const int crc = Crc32(span(buf, dwReceived)); buf[dwReceived + 0] = (uint8_t)((crc >> 0) & 0xFF); buf[dwReceived + 1] = (uint8_t)((crc >> 8) & 0xFF); buf[dwReceived + 2] = (uint8_t)((crc >> 16) & 0xFF); buf[dwReceived + 3] = (uint8_t)((crc >> 24) & 0xFF); - LOGDEBUG("%s CRC is %08X - %02X %02X %02X %02X\n", __PRETTY_FUNCTION__, crc, buf[dwReceived+0], buf[dwReceived+1], buf[dwReceived+2], buf[dwReceived+3]) + spdlog::trace("CRC is " + to_string(crc) + " - " + to_string(buf[dwReceived+0]) + " " + to_string(buf[dwReceived+1]) + + " " + to_string(buf[dwReceived+2]) + " " + to_string(buf[dwReceived+3])); // Add FCS size to the received message size dwReceived += 4; } - if (m_pcap_dumper != nullptr) { - pcap_pkthdr h = { - .ts = {}, - .caplen = dwReceived, - .len = dwReceived - }; - gettimeofday(&h.ts, nullptr); - pcap_dump((u_char*)m_pcap_dumper, &h, buf); - LOGTRACE("%s Dumped %d byte packet (first byte: %02x last byte: %02x)", __PRETTY_FUNCTION__, (unsigned int)dwReceived, buf[0], buf[dwReceived-1]) - } - // Return the number of bytes return dwReceived; } -int CTapDriver::Send(const uint8_t *buf, int len) +int CTapDriver::Send(const uint8_t *buf, int len) const { assert(m_hTAP != -1); - if (m_pcap_dumper != nullptr) { - pcap_pkthdr h = { - .ts = {}, - .caplen = (bpf_u_int32)len, - .len = (bpf_u_int32)len, - }; - gettimeofday(&h.ts, nullptr); - pcap_dump((u_char*)m_pcap_dumper, &h, buf); - LOGTRACE("%s Dumped %d byte packet (first byte: %02x last byte: %02x)", __PRETTY_FUNCTION__, (unsigned int)h.len, buf[0], buf[h.len-1]) - } - // Start sending return static_cast(write(m_hTAP, buf, len)); } diff --git a/cpp/devices/ctapdriver.h b/cpp/devices/ctapdriver.h index bb3bc371..1e0eff68 100644 --- a/cpp/devices/ctapdriver.h +++ b/cpp/devices/ctapdriver.h @@ -11,46 +11,59 @@ #pragma once -#include -#include +#include "devices/device.h" #include #include #include #include +#include + +#ifndef ETH_FRAME_LEN +static const int ETH_FRAME_LEN = 1514; +#endif +#ifndef ETH_FCS_LEN +static const int ETH_FCS_LEN = 4; +#endif using namespace std; class CTapDriver { - static constexpr const char *BRIDGE_NAME = "piscsi_bridge"; + static const string BRIDGE_NAME; + + const inline static string DEFAULT_IP = "10.10.20.1/24"; //NOSONAR This hardcoded IP address is safe public: CTapDriver() = default; - ~CTapDriver(); + ~CTapDriver() = default; CTapDriver(CTapDriver&) = default; CTapDriver& operator=(const CTapDriver&) = default; - bool Init(const unordered_map&); - void OpenDump(const string& path); // Capture packets - void GetMacAddr(uint8_t *mac) const; - int Receive(uint8_t *buf); - int Send(const uint8_t *buf, int len); - bool PendingPackets() const; // Check if there are IP packets available - bool Enable() const; // Enable the piscsi0 interface - bool Disable() const; // Disable the piscsi0 interface - void Flush(); // Purge all of the packets that are waiting to be processed + bool Init(const param_map&); + void CleanUp() const; - static uint32_t Crc32(const uint8_t *, int); + param_map GetDefaultParams() const; + + void GetMacAddr(uint8_t *) const; + int Receive(uint8_t *) const; + int Send(const uint8_t *, int) const; + bool HasPendingPackets() const; // Check if there are IP packets available + string IpLink(bool) const; // Enable/Disable the piscsi0 interface + void Flush() const; // Purge all of the packets that are waiting to be processed + + static uint32_t Crc32(span); private: + + static string SetUpEth0(int, const string&); + static string SetUpNonEth0(int, int, const string&); + static pair ExtractAddressAndMask(const string&); + array m_MacAddr; // MAC Address int m_hTAP = -1; // File handle - pcap_t *m_pcap = nullptr; - pcap_dumper_t *m_pcap_dumper = nullptr; - // Prioritized comma-separated list of interfaces to create the bridge for vector interfaces; diff --git a/cpp/devices/device.cpp b/cpp/devices/device.cpp index 2aedf5d9..6c764ec3 100644 --- a/cpp/devices/device.cpp +++ b/cpp/devices/device.cpp @@ -3,22 +3,24 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "shared/piscsi_version.h" #include "device.h" +#include #include #include #include +#include using namespace std; Device::Device(PbDeviceType type, int lun) : type(type), lun(lun) { ostringstream os; - os << setw(2) << setfill('0') << piscsi_major_version << setw(2) << setfill('0') << piscsi_minor_version; + os << setfill('0') << setw(2) << piscsi_major_version << setw(2) << piscsi_minor_version; revision = os.str(); } @@ -39,7 +41,7 @@ void Device::SetProtected(bool b) void Device::SetVendor(const string& v) { if (v.empty() || v.length() > 8) { - throw invalid_argument("Vendor '" + v + "' must be between 1 and 8 characters"); + throw invalid_argument("Vendor '" + v + "' must have between 1 and 8 characters"); } vendor = v; @@ -48,7 +50,7 @@ void Device::SetVendor(const string& v) void Device::SetProduct(const string& p, bool force) { if (p.empty() || p.length() > 16) { - throw invalid_argument("Product '" + p + "' must be between 1 and 16 characters"); + throw invalid_argument("Product '" + p + "' must have between 1 and 16 characters"); } // Changing vital product data is not SCSI compliant @@ -62,7 +64,7 @@ void Device::SetProduct(const string& p, bool force) void Device::SetRevision(const string& r) { if (r.empty() || r.length() > 4) { - throw invalid_argument("Revision '" + r + "' must be between 1 and 4 characters"); + throw invalid_argument("Revision '" + r + "' must have between 1 and 4 characters"); } revision = r; @@ -85,9 +87,9 @@ string Device::GetParam(const string& key) const return it == params.end() ? "" : it->second; } -void Device::SetParams(const unordered_map& set_params) +void Device::SetParams(const param_map& set_params) { - params = default_params; + params = GetDefaultParams(); // Devices with image file support implicitly support the "file" parameter if (SupportsFile()) { @@ -96,11 +98,11 @@ void Device::SetParams(const unordered_map& set_params) for (const auto& [key, value] : set_params) { // It is assumed that there are default parameters for all supported parameters - if (params.find(key) != params.end()) { + if (params.contains(key)) { params[key] = value; } else { - LOGWARN("%s", string("Ignored unknown parameter '" + key + "'").c_str()) + spdlog::warn("Ignored unknown parameter '" + key + "'"); } } } diff --git a/cpp/devices/device.h b/cpp/devices/device.h index f971bc9d..edff1d13 100644 --- a/cpp/devices/device.h +++ b/cpp/devices/device.h @@ -3,20 +3,26 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once -#include "shared/log.h" #include "generated/piscsi_interface.pb.h" +#include "shared/piscsi_util.h" #include #include using namespace std; using namespace piscsi_interface; +// A combination of device ID and LUN +using id_set = pair; + +// The map used for storing/passing device parameters +using param_map = unordered_map>; + class Device //NOSONAR The number of fields and methods is justified, the complexity is low { const string DEFAULT_VENDOR = "PiSCSI"; @@ -60,10 +66,7 @@ class Device //NOSONAR The number of fields and methods is justified, the comple string revision; // The parameters the device was created with - unordered_map params; - - // The default parameters - unordered_map default_params; + param_map params; // Sense Key and ASC // MSB Reserved (0x00) @@ -90,14 +93,15 @@ protected: int GetStatusCode() const { return status_code; } string GetParam(const string&) const; - void SetParams(const unordered_map&); + void SetParams(const param_map&); public: virtual ~Device() = default; PbDeviceType GetType() const { return type; } - const char *GetTypeString() const { return PbDeviceType_Name(type).c_str(); } + string GetTypeString() const { return PbDeviceType_Name(type); } + string GetIdentifier() const { return GetTypeString() + " " + to_string(GetId()) + ":" + to_string(lun); } bool IsReady() const { return ready; } virtual void Reset(); @@ -131,8 +135,8 @@ public: bool SupportsFile() const { return supports_file; } void SupportsParams(bool b) { supports_params = b; } void SupportsFile(bool b) { supports_file = b; } - unordered_map GetParams() const { return params; } - void SetDefaultParams(const unordered_map& p) { default_params = p; } + auto GetParams() const { return params; } + virtual param_map GetDefaultParams() const { return {}; } void SetStatusCode(int s) { status_code = s; } diff --git a/cpp/devices/device_factory.cpp b/cpp/devices/device_factory.cpp index 321162f7..f951d645 100644 --- a/cpp/devices/device_factory.cpp +++ b/cpp/devices/device_factory.cpp @@ -3,11 +3,11 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "shared/piscsi_util.h" +#include "shared/network_util.h" #include "scsihd.h" #include "scsihd_nec.h" #include "scsimo.h" @@ -17,15 +17,10 @@ #include "scsi_daynaport.h" #include "host_services.h" #include "device_factory.h" -#include -#include -#include -#include -#include using namespace std; -using namespace piscsi_interface; using namespace piscsi_util; +using namespace network_util; DeviceFactory::DeviceFactory() { @@ -34,22 +29,6 @@ DeviceFactory::DeviceFactory() sector_sizes[SCMO] = { 512, 1024, 2048, 4096 }; sector_sizes[SCCD] = { 512, 2048}; - string network_interfaces; - for (const auto& network_interface : GetNetworkInterfaces()) { - if (network_interface.rfind("dummy", 0) == string::npos) { - if (!network_interfaces.empty()) { - network_interfaces += ","; - } - network_interfaces += network_interface; - } - } - - default_params[SCBR]["interface"] = network_interfaces; - default_params[SCBR]["inet"] = DEFAULT_IP; - default_params[SCDP]["interface"] = network_interfaces; - default_params[SCDP]["inet"] = DEFAULT_IP; - default_params[SCLP]["cmd"] = "lp -oraw %f"; - extension_mapping["hd1"] = SCHD; extension_mapping["hds"] = SCHD; extension_mapping["hda"] = SCHD; @@ -96,8 +75,8 @@ shared_ptr DeviceFactory::CreateDevice(PbDeviceType type, int lun if (const string ext = GetExtensionLowerCase(filename); ext == "hdn" || ext == "hdi" || ext == "nhd") { device = make_shared(lun); } else { - device = make_shared(lun, sector_sizes.find(SCHD)->second, false, - ext == "hd1" ? scsi_level::SCSI_1_CCS : scsi_level::SCSI_2); + device = make_shared(lun, sector_sizes.find(type)->second, false, + ext == "hd1" ? scsi_level::scsi_1_ccs : scsi_level::scsi_2); // Some Apple tools require a particular drive identification if (ext == "hda") { @@ -109,18 +88,18 @@ shared_ptr DeviceFactory::CreateDevice(PbDeviceType type, int lun } case SCRM: - device = make_shared(lun, sector_sizes.find(SCRM)->second, true); + device = make_shared(lun, sector_sizes.find(type)->second, true); device->SetProduct("SCSI HD (REM.)"); break; case SCMO: - device = make_shared(lun, sector_sizes.find(SCMO)->second); + device = make_shared(lun, sector_sizes.find(type)->second); device->SetProduct("SCSI MO"); break; case SCCD: - device = make_shared(lun, sector_sizes.find(SCCD)->second, - GetExtensionLowerCase(filename) == "is1" ? scsi_level::SCSI_1_CCS : scsi_level::SCSI_2); + device = make_shared(lun, sector_sizes.find(type)->second, + GetExtensionLowerCase(filename) == "is1" ? scsi_level::scsi_1_ccs : scsi_level::scsi_2); device->SetProduct("SCSI CD-ROM"); break; @@ -128,7 +107,6 @@ shared_ptr DeviceFactory::CreateDevice(PbDeviceType type, int lun device = make_shared(lun); // Since this is an emulation for a specific driver the product name has to be set accordingly device->SetProduct("RASCSI BRIDGE"); - device->SetDefaultParams(default_params.find(SCBR)->second); break; case SCDP: @@ -137,7 +115,6 @@ shared_ptr DeviceFactory::CreateDevice(PbDeviceType type, int lun device->SetVendor("Dayna"); device->SetProduct("SCSI/Link"); device->SetRevision("1.4a"); - device->SetDefaultParams(default_params.find(SCDP)->second); break; case SCHS: @@ -150,7 +127,6 @@ shared_ptr DeviceFactory::CreateDevice(PbDeviceType type, int lun case SCLP: device = make_shared(lun); device->SetProduct("SCSI PRINTER"); - device->SetDefaultParams(default_params.find(SCLP)->second); break; default: @@ -160,47 +136,14 @@ shared_ptr DeviceFactory::CreateDevice(PbDeviceType type, int lun return device; } -const unordered_set& DeviceFactory::GetSectorSizes(PbDeviceType type) const +// TODO Move to respective device, which may require changes in the SCSI_HD/SCSIHD_NEC inheritance hierarchy +unordered_set DeviceFactory::GetSectorSizes(PbDeviceType type) const { const auto& it = sector_sizes.find(type); - return it != sector_sizes.end() ? it->second : empty_set; -} - -const unordered_map& DeviceFactory::GetDefaultParams(PbDeviceType type) const -{ - const auto& it = default_params.find(type); - return it != default_params.end() ? it->second : empty_map; -} - -vector DeviceFactory::GetNetworkInterfaces() const -{ - vector network_interfaces; - -#ifdef __linux__ - ifaddrs *addrs; - getifaddrs(&addrs); - ifaddrs *tmp = addrs; - - while (tmp) { - if (tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_PACKET && - strcmp(tmp->ifa_name, "lo") && strcmp(tmp->ifa_name, "piscsi_bridge")) { - const int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); - - ifreq ifr = {}; - strcpy(ifr.ifr_name, tmp->ifa_name); //NOSONAR Using strcpy is safe here - // Only list interfaces that are up - if (!ioctl(fd, SIOCGIFFLAGS, &ifr) && (ifr.ifr_flags & IFF_UP)) { - network_interfaces.emplace_back(tmp->ifa_name); - } - - close(fd); - } - - tmp = tmp->ifa_next; + if (it != sector_sizes.end()) { + return it->second; + } + else { + return {}; } - - freeifaddrs(addrs); -#endif - - return network_interfaces; } diff --git a/cpp/devices/device_factory.h b/cpp/devices/device_factory.h index a2865d88..fef1c17d 100644 --- a/cpp/devices/device_factory.h +++ b/cpp/devices/device_factory.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // // The DeviceFactory creates devices based on their type and the image file extension // @@ -11,8 +11,8 @@ #pragma once +#include "shared/piscsi_util.h" #include -#include #include #include #include "generated/piscsi_interface.pb.h" @@ -24,7 +24,6 @@ class PrimaryDevice; class DeviceFactory { - const inline static string DEFAULT_IP = "10.10.20.1/24"; //NOSONAR This hardcoded IP address is safe public: @@ -33,21 +32,14 @@ public: shared_ptr CreateDevice(PbDeviceType, int, const string&) const; PbDeviceType GetTypeForFile(const string&) const; - const unordered_set& GetSectorSizes(PbDeviceType type) const; - const unordered_map& GetDefaultParams(PbDeviceType type) const; - vector GetNetworkInterfaces() const; - const unordered_map& GetExtensionMapping() const { return extension_mapping; } + unordered_set GetSectorSizes(PbDeviceType type) const; + const auto& GetExtensionMapping() const { return extension_mapping; } private: unordered_map> sector_sizes; - unordered_map> default_params; + unordered_map> extension_mapping; - unordered_map extension_mapping; - - unordered_map device_mapping; - - unordered_set empty_set; - unordered_map empty_map; + unordered_map> device_mapping; }; diff --git a/cpp/devices/device_logger.cpp b/cpp/devices/device_logger.cpp index 98c6d8d3..65afb129 100644 --- a/cpp/devices/device_logger.cpp +++ b/cpp/devices/device_logger.cpp @@ -3,63 +3,52 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "shared/log.h" #include "device_logger.h" using namespace std; +using namespace spdlog; void DeviceLogger::Trace(const string& message) const { - if (const string m = GetLogMessage(message); !m.empty()) { - LOGTRACE("%s", m.c_str()) - } + Log(level::trace, message); } void DeviceLogger::Debug(const string& message) const { - if (const string m = GetLogMessage(message); !m.empty()) { - LOGDEBUG("%s", m.c_str()) - } + Log(level::debug, message); } void DeviceLogger::Info(const string& message) const { - if (const string m = GetLogMessage(message); !m.empty()) { - LOGINFO("%s", m.c_str()) - } + Log(level::info, message); } void DeviceLogger::Warn(const string& message) const { - if (const string m = GetLogMessage(message); !m.empty()) { - LOGWARN("%s", m.c_str()) - } + Log(level::warn, message); } void DeviceLogger::Error(const string& message) const { - if (const string m = GetLogMessage(message); !m.empty()) { - LOGERROR("%s", m.c_str()) - } + Log(level::err, message); } -string DeviceLogger::GetLogMessage(const string& message) const +void DeviceLogger::Log(level::level_enum level, const string& message) const { - if (log_device_id == -1 || (log_device_id == id && (log_device_lun == -1 || log_device_lun == lun))) - { + if (!message.empty() && + (log_device_id == -1 || + (log_device_id == id && (log_device_lun == -1 || log_device_lun == lun)))) { if (lun == -1) { - return "(ID " + to_string(id) + ") - " + message; + log(level, "(ID " + to_string(id) + ") - " + message); } else { - return "(ID:LUN " + to_string(id) + ":" + to_string(lun) + ") - " + message; + log(level, "(ID:LUN " + to_string(id) + ":" + to_string(lun) + ") - " + message); } } - - return ""; } void DeviceLogger::SetIdAndLun(int i, int l) diff --git a/cpp/devices/device_logger.h b/cpp/devices/device_logger.h index fb5b8f7a..dc662718 100644 --- a/cpp/devices/device_logger.h +++ b/cpp/devices/device_logger.h @@ -3,12 +3,13 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once +#include "spdlog/spdlog.h" #include using namespace std; @@ -27,14 +28,13 @@ public: void Warn(const string&) const; void Error(const string&) const; - string GetLogMessage(const string&) const; - void SetIdAndLun(int, int); - static void SetLogIdAndLun(int, int); private: + void Log(spdlog::level::level_enum, const string&) const; + int id = -1; int lun = -1; diff --git a/cpp/devices/disk.cpp b/cpp/devices/disk.cpp index 8649103f..bc8b53d0 100644 --- a/cpp/devices/disk.cpp +++ b/cpp/devices/disk.cpp @@ -23,15 +23,7 @@ using namespace scsi_defs; using namespace scsi_command_util; -Disk::~Disk() -{ - // Save disk cache, only if ready - if (IsReady() && cache != nullptr) { - cache->Save(); - } -} - -bool Disk::Init(const unordered_map& params) +bool Disk::Init(const param_map& params) { StorageDevice::Init(params); @@ -64,6 +56,13 @@ bool Disk::Init(const unordered_map& params) return true; } +void Disk::CleanUp() +{ + FlushCache(); + + StorageDevice::CleanUp(); +} + void Disk::Dispatch(scsi_command cmd) { // Media changes must be reported on the next access, i.e. not only for TEST UNIT READY @@ -72,7 +71,7 @@ void Disk::Dispatch(scsi_command cmd) SetMediumChanged(false); - GetController()->Error(sense_key::UNIT_ATTENTION, asc::NOT_READY_TO_READY_CHANGE); + GetController()->Error(sense_key::unit_attention, asc::not_ready_to_ready_change); } else { PrimaryDevice::Dispatch(cmd); @@ -93,7 +92,7 @@ void Disk::ResizeCache(const string& path, bool raw) void Disk::FlushCache() { - if (cache != nullptr) { + if (cache != nullptr && IsReady()) { cache->Save(); } } @@ -103,8 +102,8 @@ void Disk::FormatUnit() CheckReady(); // FMTDATA=1 is not supported (but OK if there is no DEFECT LIST) - if ((GetController()->GetCmd(1) & 0x10) != 0 && GetController()->GetCmd(4) != 0) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + if ((GetController()->GetCmdByte(1) & 0x10) != 0 && GetController()->GetCmdByte(4) != 0) { + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } EnterStatusPhase(); @@ -115,9 +114,9 @@ void Disk::Read(access_mode mode) const auto& [valid, start, blocks] = CheckAndGetStartAndCount(mode); if (valid) { GetController()->SetBlocks(blocks); - GetController()->SetLength(Read(GetController()->GetCmd(), GetController()->GetBuffer(), start)); + GetController()->SetLength(Read(GetController()->GetBuffer(), start)); - GetLogger().Trace("Length is " + to_string(GetController()->GetLength())); + LogTrace("Length is " + to_string(GetController()->GetLength())); // Set next block GetController()->SetNext(start + 1); @@ -135,7 +134,7 @@ void Disk::ReadWriteLong10() const // Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard if (GetInt16(GetController()->GetCmd(), 7) != 0) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } EnterStatusPhase(); @@ -147,7 +146,7 @@ void Disk::ReadWriteLong16() const // Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard if (GetInt16(GetController()->GetCmd(), 12) != 0) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } EnterStatusPhase(); @@ -156,7 +155,7 @@ void Disk::ReadWriteLong16() const void Disk::Write(access_mode mode) const { if (IsProtected()) { - throw scsi_exception(sense_key::DATA_PROTECT, asc::WRITE_PROTECTED); + throw scsi_exception(sense_key::data_protect, asc::write_protected); } const auto& [valid, start, blocks] = CheckAndGetStartAndCount(mode); @@ -179,14 +178,14 @@ void Disk::Verify(access_mode mode) const auto& [valid, start, blocks] = CheckAndGetStartAndCount(mode); if (valid) { // if BytChk=0 - if ((GetController()->GetCmd(1) & 0x02) == 0) { + if ((GetController()->GetCmdByte(1) & 0x02) == 0) { Seek(); return; } // Test reading GetController()->SetBlocks(blocks); - GetController()->SetLength(Read(GetController()->GetCmd(), GetController()->GetBuffer(), start)); + GetController()->SetLength(Read(GetController()->GetBuffer(), start)); // Set next block GetController()->SetNext(start + 1); @@ -200,14 +199,14 @@ void Disk::Verify(access_mode mode) void Disk::StartStopUnit() { - const bool start = GetController()->GetCmd(4) & 0x01; - const bool load = GetController()->GetCmd(4) & 0x02; + const bool start = GetController()->GetCmdByte(4) & 0x01; + const bool load = GetController()->GetCmdByte(4) & 0x02; if (load) { - GetLogger().Trace(start ? "Loading medium" : "Ejecting medium"); + LogTrace(start ? "Loading medium" : "Ejecting medium"); } else { - GetLogger().Trace(start ? "Starting unit" : "Stopping unit"); + LogTrace(start ? "Starting unit" : "Stopping unit"); SetStopped(!start); } @@ -217,12 +216,12 @@ void Disk::StartStopUnit() if (load) { if (IsLocked()) { // Cannot be ejected because it is locked - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LOAD_OR_EJECT_FAILED); + throw scsi_exception(sense_key::illegal_request, asc::load_or_eject_failed); } // Eject if (!Eject(false)) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LOAD_OR_EJECT_FAILED); + throw scsi_exception(sense_key::illegal_request, asc::load_or_eject_failed); } } else { @@ -237,9 +236,9 @@ void Disk::PreventAllowMediumRemoval() { CheckReady(); - const bool lock = GetController()->GetCmd(4) & 0x01; + const bool lock = GetController()->GetCmdByte(4) & 0x01; - GetLogger().Trace(lock ? "Locking medium" : "Unlocking medium"); + LogTrace(lock ? "Locking medium" : "Unlocking medium"); SetLocked(lock); @@ -274,12 +273,15 @@ bool Disk::Eject(bool force) // The image file for this drive is not in use anymore UnreserveFile(); + + sector_read_count = 0; + sector_write_count = 0; } return status; } -int Disk::ModeSense6(const vector& cdb, vector& buf) const +int Disk::ModeSense6(cdb_t cdb, vector& buf) const { // Get length, clear buffer const auto length = static_cast(min(buf.size(), static_cast(cdb[4]))); @@ -315,7 +317,7 @@ int Disk::ModeSense6(const vector& cdb, vector& buf) const return size; } -int Disk::ModeSense10(const vector& cdb, vector& buf) const +int Disk::ModeSense10(cdb_t cdb, vector& buf) const { // Get length, clear buffer const auto length = static_cast(min(buf.size(), static_cast(GetInt16(cdb, 7)))); @@ -497,28 +499,32 @@ void Disk::AddCachePage(map>& pages, bool changeable) const pages[8] = buf; } -int Disk::Read(const vector&, vector& buf, uint64_t block) +int Disk::Read(span buf, uint64_t block) { assert(block < GetBlockCount()); CheckReady(); if (!cache->ReadSector(buf, static_cast(block))) { - throw scsi_exception(sense_key::MEDIUM_ERROR, asc::READ_FAULT); + throw scsi_exception(sense_key::medium_error, asc::read_fault); } + ++sector_read_count; + return GetSectorSizeInBytes(); } -void Disk::Write(const vector&, const vector& buf, uint64_t block) +void Disk::Write(span buf, uint64_t block) { assert(block < GetBlockCount()); CheckReady(); if (!cache->WriteSector(buf, static_cast(block))) { - throw scsi_exception(sense_key::MEDIUM_ERROR, asc::WRITE_FAULT); + throw scsi_exception(sense_key::medium_error, asc::write_fault); } + + ++sector_write_count; } void Disk::Seek() @@ -553,7 +559,7 @@ void Disk::ReadCapacity10() CheckReady(); if (GetBlockCount() == 0) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::MEDIUM_NOT_PRESENT); + throw scsi_exception(sense_key::illegal_request, asc::medium_not_present); } vector& buf = GetController()->GetBuffer(); @@ -580,7 +586,7 @@ void Disk::ReadCapacity16() CheckReady(); if (GetBlockCount() == 0) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::MEDIUM_NOT_PRESENT); + throw scsi_exception(sense_key::illegal_request, asc::medium_not_present); } vector& buf = GetController()->GetBuffer(); @@ -604,7 +610,7 @@ void Disk::ReadCapacity16() void Disk::ReadCapacity16_ReadLong16() { // The service action determines the actual command - switch (GetController()->GetCmd(1) & 0x1f) { + switch (GetController()->GetCmdByte(1) & 0x1f) { case 0x10: ReadCapacity16(); break; @@ -614,7 +620,7 @@ void Disk::ReadCapacity16_ReadLong16() break; default: - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); break; } } @@ -624,9 +630,9 @@ void Disk::ValidateBlockAddress(access_mode mode) const const uint64_t block = mode == RW16 ? GetInt64(GetController()->GetCmd(), 2) : GetInt32(GetController()->GetCmd(), 2); if (block > GetBlockCount()) { - GetLogger().Trace("Capacity of " + to_string(GetBlockCount()) + " block(s) exceeded: Trying to access block " + LogTrace("Capacity of " + to_string(GetBlockCount()) + " block(s) exceeded: Trying to access block " + to_string(block)); - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE); + throw scsi_exception(sense_key::illegal_request, asc::lba_out_of_range); } } @@ -638,7 +644,7 @@ tuple Disk::CheckAndGetStartAndCount(access_mode mode) if (mode == RW6 || mode == SEEK6) { start = GetInt24(GetController()->GetCmd(), 1); - count = GetController()->GetCmd(4); + count = GetController()->GetCmdByte(4); if (!count) { count= 0x100; } @@ -659,13 +665,13 @@ tuple Disk::CheckAndGetStartAndCount(access_mode mode) stringstream s; s << "READ/WRITE/VERIFY/SEEK, start block: $" << setfill('0') << setw(8) << hex << start; - GetLogger().Trace(s.str() + ", blocks: " + to_string(count)); + LogTrace(s.str() + ", blocks: " + to_string(count)); // Check capacity if (uint64_t capacity = GetBlockCount(); !capacity || start > capacity || start + count > capacity) { - GetLogger().Trace("Capacity of " + to_string(capacity) + " block(s) exceeded: Trying to access block " + LogTrace("Capacity of " + to_string(capacity) + " block(s) exceeded: Trying to access block " + to_string(start) + ", block count " + to_string(count)); - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE); + throw scsi_exception(sense_key::illegal_request, asc::lba_out_of_range); } // Do not process 0 blocks @@ -689,10 +695,8 @@ uint32_t Disk::GetSectorSizeInBytes() const void Disk::SetSectorSizeInBytes(uint32_t size_in_bytes) { - DeviceFactory device_factory; - if (const auto& sizes = device_factory.GetSectorSizes(GetType()); - !sizes.empty() && sizes.find(size_in_bytes) == sizes.end()) { - throw io_exception("Invalid sector size of " + to_string(size_in_bytes) + " byte(s)"); + if (DeviceFactory device_factory; !device_factory.GetSectorSizes(GetType()).contains(size_in_bytes)) { + throw io_exception("Invalid sector size of " + to_string(size_in_bytes) + " byte(s)"); } size_shift_count = CalculateShiftCount(size_in_bytes); @@ -706,12 +710,43 @@ uint32_t Disk::GetConfiguredSectorSize() const bool Disk::SetConfiguredSectorSize(const DeviceFactory& device_factory, uint32_t configured_size) { - if (unordered_set sizes = device_factory.GetSectorSizes(GetType()); - sizes.find(configured_size) == sizes.end()) { + if (!device_factory.GetSectorSizes(GetType()).contains(configured_size)) { return false; } - configured_sector_size = configured_size; + configured_sector_size = configured_size; return true; } + +vector Disk::GetStatistics() const +{ + vector statistics = PrimaryDevice::GetStatistics(); + + // Enrich cache statistics with device information before adding them to device statistics + if (cache) { + for (auto& s : cache->GetStatistics(IsReadOnly())) { + s.set_id(GetId()); + s.set_unit(GetLun()); + statistics.push_back(s); + } + } + + PbStatistics s; + s.set_id(GetId()); + s.set_unit(GetLun()); + + s.set_category(PbStatisticsCategory::CATEGORY_INFO); + + s.set_key(SECTOR_READ_COUNT); + s.set_value(sector_read_count); + statistics.push_back(s); + + if (!IsReadOnly()) { + s.set_key(SECTOR_WRITE_COUNT); + s.set_value(sector_write_count); + statistics.push_back(s); + } + + return statistics; +} diff --git a/cpp/devices/disk.h b/cpp/devices/disk.h index 75af2697..7364ad08 100644 --- a/cpp/devices/disk.h +++ b/cpp/devices/disk.h @@ -8,7 +8,6 @@ // XM6i // Copyright (C) 2010-2015 isaki@NetBSD.org // -// Imported sava's Anex86/T98Next image and MO format support patch. // Comments translated to english by akuker. // //--------------------------------------------------------------------------- @@ -16,12 +15,14 @@ #pragma once #include "shared/scsi.h" +#include "shared/piscsi_util.h" #include "device_factory.h" #include "disk_track.h" #include "disk_cache.h" #include "interfaces/scsi_block_commands.h" #include "storage_device.h" #include +#include #include #include #include @@ -41,26 +42,34 @@ class Disk : public StorageDevice, private ScsiBlockCommands // Sector size shift count (9=512, 10=1024, 11=2048, 12=4096) uint32_t size_shift_count = 0; + uint64_t sector_read_count = 0; + uint64_t sector_write_count = 0; + + inline static const string SECTOR_READ_COUNT = "sector_read_count"; + inline static const string SECTOR_WRITE_COUNT = "sector_write_count"; + public: - Disk(PbDeviceType type, int lun) : StorageDevice(type, lun) {} - ~Disk() override; + using StorageDevice::StorageDevice; - bool Init(const unordered_map&) override; + bool Init(const param_map&) override; + void CleanUp() override; void Dispatch(scsi_command) override; bool Eject(bool) override; - virtual void Write(const vector&, const vector&, uint64_t); + virtual void Write(span, uint64_t); - virtual int Read(const vector&, vector& , uint64_t); + virtual int Read(span , uint64_t); uint32_t GetSectorSizeInBytes() const; bool IsSectorSizeConfigurable() const { return !sector_sizes.empty(); } bool SetConfiguredSectorSize(const DeviceFactory&, uint32_t); void FlushCache() override; + vector GetStatistics() const override; + private: // Commands covered by the SCSI specifications (see https://www.t10.org/drafts.htm) @@ -92,8 +101,8 @@ private: void ValidateBlockAddress(access_mode) const; tuple CheckAndGetStartAndCount(access_mode) const; - int ModeSense6(const vector&, vector&) const override; - int ModeSense10(const vector&, vector&) const override; + int ModeSense6(cdb_t, vector&) const override; + int ModeSense10(cdb_t, vector&) const override; static inline const unordered_map shift_counts = { { 512, 9 }, { 1024, 10 }, { 2048, 11 }, { 4096, 12 } }; diff --git a/cpp/devices/disk_cache.cpp b/cpp/devices/disk_cache.cpp index 8fc126db..7ab69c19 100644 --- a/cpp/devices/disk_cache.cpp +++ b/cpp/devices/disk_cache.cpp @@ -27,11 +27,11 @@ DiskCache::DiskCache(const string& path, int size, uint32_t blocks, off_t imgoff assert(imgoff >= 0); } -bool DiskCache::Save() const +bool DiskCache::Save() { // Save valid tracks - return none_of(cache.begin(), cache.end(), [this](const cache_t& c) - { return c.disktrk != nullptr && !c.disktrk->Save(sec_path); }); + return ranges::none_of(cache.begin(), cache.end(), [this](const cache_t& c) + { return c.disktrk != nullptr && !c.disktrk->Save(sec_path, cache_miss_write_count); }); } shared_ptr DiskCache::GetTrack(uint32_t block) @@ -46,7 +46,7 @@ shared_ptr DiskCache::GetTrack(uint32_t block) return Assign(track); } -bool DiskCache::ReadSector(vector& buf, uint32_t block) +bool DiskCache::ReadSector(span buf, uint32_t block) { shared_ptr disktrk = GetTrack(block); if (disktrk == nullptr) { @@ -57,7 +57,7 @@ bool DiskCache::ReadSector(vector& buf, uint32_t block) return disktrk->ReadSector(buf, block & 0xff); } -bool DiskCache::WriteSector(const vector& buf, uint32_t block) +bool DiskCache::WriteSector(span buf, uint32_t block) { shared_ptr disktrk = GetTrack(block); if (disktrk == nullptr) { @@ -120,7 +120,7 @@ shared_ptr DiskCache::Assign(int track) } // Save this track - if (!cache[c].disktrk->Save(sec_path)) { + if (!cache[c].disktrk->Save(sec_path, cache_miss_write_count)) { return nullptr; } @@ -156,17 +156,16 @@ bool DiskCache::Load(int index, int track, shared_ptr disktrk) sectors = 0x100; } - // Create a disk track if (disktrk == nullptr) { disktrk = make_shared(); } - // Initialize disk track disktrk->Init(track, sec_size, sectors, cd_raw, imgoffset); // Try loading - if (!disktrk->Load(sec_path)) { - // Failure + if (!disktrk->Load(sec_path, cache_miss_read_count)) { + ++read_error_count; + return false; } @@ -190,3 +189,35 @@ void DiskCache::UpdateSerialNumber() } } +vector DiskCache::GetStatistics(bool is_read_only) const +{ + vector statistics; + + PbStatistics s; + + s.set_category(PbStatisticsCategory::CATEGORY_INFO); + + s.set_key(CACHE_MISS_READ_COUNT); + s.set_value(cache_miss_read_count); + statistics.push_back(s); + + if (!is_read_only) { + s.set_key(CACHE_MISS_WRITE_COUNT); + s.set_value(cache_miss_write_count); + statistics.push_back(s); + } + + s.set_category(PbStatisticsCategory::CATEGORY_ERROR); + + s.set_key(READ_ERROR_COUNT); + s.set_value(read_error_count); + statistics.push_back(s); + + if (!is_read_only) { + s.set_key(WRITE_ERROR_COUNT); + s.set_value(write_error_count); + statistics.push_back(s); + } + + return statistics; +} diff --git a/cpp/devices/disk_cache.h b/cpp/devices/disk_cache.h index d9199b5c..ec486edd 100644 --- a/cpp/devices/disk_cache.h +++ b/cpp/devices/disk_cache.h @@ -15,17 +15,30 @@ #pragma once +#include "generated/piscsi_interface.pb.h" +#include #include #include #include using namespace std; +using namespace piscsi_interface; class DiskCache { // Number of tracks to cache static const int CACHE_MAX = 16; + uint64_t read_error_count = 0; + uint64_t write_error_count = 0; + uint64_t cache_miss_read_count = 0; + uint64_t cache_miss_write_count = 0; + + inline static const string READ_ERROR_COUNT = "read_error_count"; + inline static const string WRITE_ERROR_COUNT = "write_error_count"; + inline static const string CACHE_MISS_READ_COUNT = "cache_miss_read_count"; + inline static const string CACHE_MISS_WRITE_COUNT = "cache_miss_write_count"; + public: // Internal data definition @@ -39,10 +52,11 @@ public: void SetRawMode(bool b) { cd_raw = b; } // CD-ROM raw mode setting - // Access - bool Save() const; // Save and release all - bool ReadSector(vector&, uint32_t); // Sector Read - bool WriteSector(const vector&, uint32_t); // Sector Write + bool Save(); // Save and release all + bool ReadSector(span, uint32_t); // Sector Read + bool WriteSector(span, uint32_t); // Sector Write + + vector GetStatistics(bool) const; private: diff --git a/cpp/devices/disk_track.cpp b/cpp/devices/disk_track.cpp index 2b4f4008..e2e2e01d 100644 --- a/cpp/devices/disk_track.cpp +++ b/cpp/devices/disk_track.cpp @@ -14,8 +14,10 @@ // //--------------------------------------------------------------------------- -#include "shared/log.h" #include "disk_track.h" +#include +#include +#include #include DiskTrack::~DiskTrack() @@ -46,7 +48,7 @@ void DiskTrack::Init(int track, int size, int sectors, bool raw, off_t imgoff) dt.imgoffset = imgoff; } -bool DiskTrack::Load(const string& path) +bool DiskTrack::Load(const string& path, uint64_t& cache_miss_read_count) { // Not needed if already loaded if (dt.init) { @@ -54,6 +56,8 @@ bool DiskTrack::Load(const string& path) return true; } + ++cache_miss_read_count; + // Calculate offset (previous tracks are considered to hold 256 sectors) off_t offset = ((off_t)dt.track << 8); if (dt.raw) { @@ -75,7 +79,7 @@ bool DiskTrack::Load(const string& path) if (dt.buffer == nullptr) { if (posix_memalign((void **)&dt.buffer, 512, ((length + 511) / 512) * 512)) { - LOGWARN("posix_memalign failed") + spdlog::warn("posix_memalign failed"); } dt.length = length; } @@ -88,14 +92,14 @@ bool DiskTrack::Load(const string& path) if (dt.length != static_cast(length)) { free(dt.buffer); if (posix_memalign((void **)&dt.buffer, 512, ((length + 511) / 512) * 512)) { - LOGWARN("posix_memalign failed") + spdlog::warn("posix_memalign failed"); } dt.length = length; } // Resize and clear changemap dt.changemap.resize(dt.sectors); - fill(dt.changemap.begin(), dt.changemap.end(), false); + fill(dt.changemap.begin(), dt.changemap.end(), false); //NOSONAR ranges::fill() cannot be applied to vector ifstream in(path, ios::binary); if (in.fail()) { @@ -136,7 +140,7 @@ bool DiskTrack::Load(const string& path) return true; } -bool DiskTrack::Save(const string& path) +bool DiskTrack::Save(const string& path, uint64_t& cache_miss_write_count) { // Not needed if not initialized if (!dt.init) { @@ -148,6 +152,8 @@ bool DiskTrack::Save(const string& path) return true; } + ++cache_miss_write_count; + // Need to write assert(dt.buffer); assert((dt.sectors > 0) && (dt.sectors <= 0x100)); @@ -209,13 +215,13 @@ bool DiskTrack::Save(const string& path) } // Drop the change flag and exit - fill(dt.changemap.begin(), dt.changemap.end(), false); + fill(dt.changemap.begin(), dt.changemap.end(), false); //NOSONAR ranges::fill() cannot be applied to vector dt.changed = false; return true; } -bool DiskTrack::ReadSector(vector& buf, int sec) const +bool DiskTrack::ReadSector(span buf, int sec) const { assert(sec >= 0 && sec < 0x100); @@ -238,7 +244,7 @@ bool DiskTrack::ReadSector(vector& buf, int sec) const return true; } -bool DiskTrack::WriteSector(const vector& buf, int sec) +bool DiskTrack::WriteSector(span buf, int sec) { assert((sec >= 0) && (sec < 0x100)); assert(!dt.raw); diff --git a/cpp/devices/disk_track.h b/cpp/devices/disk_track.h index 786ab6e5..c61c2f29 100644 --- a/cpp/devices/disk_track.h +++ b/cpp/devices/disk_track.h @@ -16,6 +16,8 @@ #pragma once #include +#include +#include #include #include @@ -48,12 +50,11 @@ private: friend class DiskCache; void Init(int track, int size, int sectors, bool raw = false, off_t imgoff = 0); - bool Load(const string& path); - bool Save(const string& path); + bool Load(const string& path, uint64_t&); + bool Save(const string& path, uint64_t&); - // Read / Write - bool ReadSector(vector&, int) const; // Sector Read - bool WriteSector(const vector& buf, int); // Sector Write + bool ReadSector(span, int) const; // Sector Read + bool WriteSector(span buf, int); // Sector Write int GetTrack() const { return dt.track; } // Get track }; diff --git a/cpp/devices/host_services.cpp b/cpp/devices/host_services.cpp index 5cb74b9a..3d02bbd0 100644 --- a/cpp/devices/host_services.cpp +++ b/cpp/devices/host_services.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // Host Services with realtime clock and shutdown support // @@ -25,11 +25,13 @@ #include "scsi_command_util.h" #include "host_services.h" #include +#include +using namespace std::chrono; using namespace scsi_defs; using namespace scsi_command_util; -bool HostServices::Init(const unordered_map& params) +bool HostServices::Init(const param_map& params) { ModePageDevice::Init(params); @@ -49,13 +51,13 @@ void HostServices::TestUnitReady() vector HostServices::InquiryInternal() const { - return HandleInquiry(device_type::PROCESSOR, scsi_level::SPC_3, false); + return HandleInquiry(device_type::processor, scsi_level::spc_3, false); } void HostServices::StartStopUnit() const { - const bool start = GetController()->GetCmd(4) & 0x01; - const bool load = GetController()->GetCmd(4) & 0x02; + const bool start = GetController()->GetCmdByte(4) & 0x01; + const bool load = GetController()->GetCmdByte(4) & 0x02; if (!start) { if (load) { @@ -69,17 +71,17 @@ void HostServices::StartStopUnit() const GetController()->ScheduleShutdown(AbstractController::piscsi_shutdown_mode::RESTART_PI); } else { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } EnterStatusPhase(); } -int HostServices::ModeSense6(const vector& cdb, vector& buf) const +int HostServices::ModeSense6(cdb_t cdb, vector& buf) const { // Block descriptors cannot be returned if (!(cdb[1] & 0x08)) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } const auto length = static_cast(min(buf.size(), static_cast(cdb[4]))); @@ -93,11 +95,11 @@ int HostServices::ModeSense6(const vector& cdb, vector& buf) const return size; } -int HostServices::ModeSense10(const vector& cdb, vector& buf) const +int HostServices::ModeSense10(cdb_t cdb, vector& buf) const { // Block descriptors cannot be returned if (!(cdb[1] & 0x08)) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } const auto length = static_cast(min(buf.size(), static_cast(GetInt16(cdb, 7)))); @@ -123,7 +125,8 @@ void HostServices::AddRealtimeClockPage(map>& pages, bool chan pages[32] = vector(10); if (!changeable) { - time_t t = time(nullptr); + const auto now = system_clock::now(); + const time_t t = system_clock::to_time_t(now); tm localtime; localtime_r(&t, &localtime); diff --git a/cpp/devices/host_services.h b/cpp/devices/host_services.h index a269b2ac..76a41f8c 100644 --- a/cpp/devices/host_services.h +++ b/cpp/devices/host_services.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // Host Services with realtime clock and shutdown support // @@ -12,6 +12,7 @@ #pragma once #include "mode_page_device.h" +#include #include #include @@ -23,7 +24,7 @@ public: explicit HostServices(int lun) : ModePageDevice(SCHS, lun) {} ~HostServices() override = default; - bool Init(const unordered_map&) override; + bool Init(const param_map&) override; vector InquiryInternal() const override; void TestUnitReady() override; @@ -48,8 +49,8 @@ private: }; void StartStopUnit() const; - int ModeSense6(const vector&, vector&) const override; - int ModeSense10(const vector&, vector&) const override; + int ModeSense6(cdb_t, vector&) const override; + int ModeSense10(cdb_t, vector&) const override; void AddRealtimeClockPage(map>&, bool) const; }; diff --git a/cpp/devices/interfaces/byte_writer.h b/cpp/devices/interfaces/byte_writer.h deleted file mode 100644 index bfbfa5ee..00000000 --- a/cpp/devices/interfaces/byte_writer.h +++ /dev/null @@ -1,27 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Copyright (C) 2022 Uwe Seimet -// -// Abstraction for the DaynaPort and the host bridge, which both have methods for writing byte sequences -// -//--------------------------------------------------------------------------- - -#pragma once - -#include - -using namespace std; - -class ByteWriter -{ - -public: - - ByteWriter() = default; - virtual ~ByteWriter() = default; - - virtual bool WriteBytes(const vector&, vector&, uint32_t) = 0; -}; diff --git a/cpp/devices/mode_page_device.cpp b/cpp/devices/mode_page_device.cpp index bb292097..7335d5a3 100644 --- a/cpp/devices/mode_page_device.cpp +++ b/cpp/devices/mode_page_device.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // A basic device with mode page support, to be used for subclassing // @@ -20,7 +20,7 @@ using namespace std; using namespace scsi_defs; using namespace scsi_command_util; -bool ModePageDevice::Init(const unordered_map& params) +bool ModePageDevice::Init(const param_map& params) { PrimaryDevice::Init(params); @@ -32,7 +32,7 @@ bool ModePageDevice::Init(const unordered_map& params) return true; } -int ModePageDevice::AddModePages(const vector& cdb, vector& buf, int offset, int length, int max_size) const +int ModePageDevice::AddModePages(cdb_t cdb, vector& buf, int offset, int length, int max_size) const { const int max_length = length - offset; if (max_length < 0) { @@ -46,7 +46,7 @@ int ModePageDevice::AddModePages(const vector& cdb, vector& buf, i stringstream s; s << "Requesting mode page $" << setfill('0') << setw(2) << hex << page; - GetLogger().Trace(s.str()); + LogTrace(s.str()); // Mode page data mapped to the respective page numbers, C++ maps are ordered by key map> pages; @@ -54,8 +54,8 @@ int ModePageDevice::AddModePages(const vector& cdb, vector& buf, i if (pages.empty()) { s << "Unsupported mode page $" << page; - GetLogger().Trace(s.str()); - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + LogTrace(s.str()); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } // Holds all mode page data @@ -90,7 +90,7 @@ int ModePageDevice::AddModePages(const vector& cdb, vector& buf, i } if (static_cast(result.size()) > max_size) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } const auto size = static_cast(min(static_cast(max_length), result.size())); @@ -114,15 +114,15 @@ void ModePageDevice::ModeSense10() const EnterDataInPhase(); } -void ModePageDevice::ModeSelect(scsi_command, const vector&, const vector&, int) const +void ModePageDevice::ModeSelect(scsi_command, cdb_t, span, int) const { - // There is no default implementation of MDOE SELECT - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE); + // There is no default implementation of MODE SELECT + throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code); } void ModePageDevice::ModeSelect6() const { - SaveParametersCheck(GetController()->GetCmd(4)); + SaveParametersCheck(GetController()->GetCmdByte(4)); } void ModePageDevice::ModeSelect10() const @@ -134,8 +134,8 @@ void ModePageDevice::ModeSelect10() const void ModePageDevice::SaveParametersCheck(int length) const { - if (!SupportsSaveParameters() && (GetController()->GetCmd(1) & 0x01)) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + if (!SupportsSaveParameters() && (GetController()->GetCmdByte(1) & 0x01)) { + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } GetController()->SetLength(length); diff --git a/cpp/devices/mode_page_device.h b/cpp/devices/mode_page_device.h index e8fc06d7..4a487fb2 100644 --- a/cpp/devices/mode_page_device.h +++ b/cpp/devices/mode_page_device.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -11,25 +11,25 @@ #include "primary_device.h" #include +#include #include #include -// TODO Maybe this should better be a mixin class because not all storage devicess have mode pages class ModePageDevice : public PrimaryDevice { public: using PrimaryDevice::PrimaryDevice; - bool Init(const unordered_map&) override; + bool Init(const param_map&) override; - virtual void ModeSelect(scsi_defs::scsi_command, const vector&, const vector&, int) const; + virtual void ModeSelect(scsi_defs::scsi_command, cdb_t, span, int) const; protected: bool SupportsSaveParameters() const { return supports_save_parameters; } void SupportsSaveParameters(bool b) { supports_save_parameters = b; } - int AddModePages(const vector&, vector&, int, int, int) const; + int AddModePages(cdb_t, vector&, int, int, int) const; virtual void SetUpModePages(map>&, int, bool) const = 0; virtual void AddVendorPage(map>&, int, bool) const { // Nothing to add by default @@ -39,8 +39,8 @@ private: bool supports_save_parameters = false; - virtual int ModeSense6(const vector&, vector&) const = 0; - virtual int ModeSense10(const vector&, vector&) const = 0; + virtual int ModeSense6(cdb_t, vector&) const = 0; + virtual int ModeSense10(cdb_t, vector&) const = 0; void ModeSense6() const; void ModeSense10() const; diff --git a/cpp/devices/primary_device.cpp b/cpp/devices/primary_device.cpp index 10989167..4249ed4e 100644 --- a/cpp/devices/primary_device.cpp +++ b/cpp/devices/primary_device.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -17,7 +17,7 @@ using namespace std; using namespace scsi_defs; using namespace scsi_command_util; -bool PrimaryDevice::Init(const unordered_map& params) +bool PrimaryDevice::Init(const param_map& params) { // Mandatory SCSI primary commands AddCommand(scsi_command::eCmdTestUnitReady, [this] { TestUnitReady(); }); @@ -35,9 +35,9 @@ bool PrimaryDevice::Init(const unordered_map& params) return true; } -void PrimaryDevice::AddCommand(scsi_command opcode, const operation& execute) +void PrimaryDevice::AddCommand(scsi_command cmd, const operation& execute) { - commands[opcode] = execute; + commands[cmd] = execute; } void PrimaryDevice::Dispatch(scsi_command cmd) @@ -46,15 +46,14 @@ void PrimaryDevice::Dispatch(scsi_command cmd) s << "$" << setfill('0') << setw(2) << hex << static_cast(cmd); if (const auto& it = commands.find(cmd); it != commands.end()) { - GetLogger().Debug("Device is executing " + string(command_mapping.find(cmd)->second.second) + - " (" + s.str() + ")"); + LogDebug("Device is executing " + command_mapping.find(cmd)->second.second + " (" + s.str() + ")"); it->second(); } else { - GetLogger().Trace("Received unsupported command: " + s.str()); + LogTrace("Received unsupported command: " + s.str()); - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE); + throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code); } } @@ -67,18 +66,14 @@ void PrimaryDevice::Reset() int PrimaryDevice::GetId() const { - if (GetController() == nullptr) { - GetLogger().Error("Device is missing its controller"); - } - return GetController() != nullptr ? GetController()->GetTargetId() : -1; } -void PrimaryDevice::SetController(shared_ptr c) +void PrimaryDevice::SetController(AbstractController *c) { controller = c; - logger.SetIdAndLun(c != nullptr ? c->GetTargetId() : -1, GetLun()); + device_logger.SetIdAndLun(GetId(), GetLun()); } void PrimaryDevice::TestUnitReady() @@ -91,11 +86,11 @@ void PrimaryDevice::TestUnitReady() void PrimaryDevice::Inquiry() { // EVPD and page code check - if ((GetController()->GetCmd(1) & 0x01) || GetController()->GetCmd(2)) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + if ((GetController()->GetCmdByte(1) & 0x01) || GetController()->GetCmdByte(2)) { + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } - vector buf = InquiryInternal(); + const vector buf = InquiryInternal(); const size_t allocation_length = min(buf.size(), static_cast(GetInt16(GetController()->GetCmd(), 3))); @@ -103,8 +98,8 @@ void PrimaryDevice::Inquiry() GetController()->SetLength(static_cast(allocation_length)); // Report if the device does not support the requested LUN - if (int lun = GetController()->GetEffectiveLun(); !GetController()->HasDeviceForLun(lun)) { - GetLogger().Trace("LUN is not available"); + if (const int lun = GetController()->GetEffectiveLun(); !GetController()->HasDeviceForLun(lun)) { + LogTrace("LUN is not available"); // Signal that the requested LUN does not exist GetController()->GetBuffer().data()[0] = 0x7f; @@ -116,8 +111,8 @@ void PrimaryDevice::Inquiry() void PrimaryDevice::ReportLuns() { // Only SELECT REPORT mode 0 is supported - if (GetController()->GetCmd(2)) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + if (GetController()->GetCmdByte(2)) { + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } const uint32_t allocation_length = GetInt32(GetController()->GetCmd(), 6); @@ -155,14 +150,14 @@ void PrimaryDevice::RequestSense() lun = 0; // Do not raise an exception here because the rest of the code must be executed - GetController()->Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_LUN); + GetController()->Error(sense_key::illegal_request, asc::invalid_lun); - GetController()->SetStatus(status::GOOD); + GetController()->SetStatus(status::good); } vector buf = GetController()->GetDeviceForLun(lun)->HandleRequestSense(); - const size_t allocation_length = min(buf.size(), static_cast(GetController()->GetCmd(4))); + const size_t allocation_length = min(buf.size(), static_cast(GetController()->GetCmdByte(4))); memcpy(GetController()->GetBuffer().data(), buf.data(), allocation_length); GetController()->SetLength(static_cast(allocation_length)); @@ -173,13 +168,13 @@ void PrimaryDevice::RequestSense() void PrimaryDevice::SendDiagnostic() { // Do not support PF bit - if (GetController()->GetCmd(1) & 0x10) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + if (GetController()->GetCmdByte(1) & 0x10) { + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } // Do not support parameter list - if ((GetController()->GetCmd(3) != 0) || (GetController()->GetCmd(4) != 0)) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + if ((GetController()->GetCmdByte(3) != 0) || (GetController()->GetCmdByte(4) != 0)) { + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } EnterStatusPhase(); @@ -190,24 +185,24 @@ void PrimaryDevice::CheckReady() // Not ready if reset if (IsReset()) { SetReset(false); - GetLogger().Trace("Device in reset"); - throw scsi_exception(sense_key::UNIT_ATTENTION, asc::POWER_ON_OR_RESET); + LogTrace("Device in reset"); + throw scsi_exception(sense_key::unit_attention, asc::power_on_or_reset); } // Not ready if it needs attention if (IsAttn()) { SetAttn(false); - GetLogger().Trace("Device in needs attention"); - throw scsi_exception(sense_key::UNIT_ATTENTION, asc::NOT_READY_TO_READY_CHANGE); + LogTrace("Device in needs attention"); + throw scsi_exception(sense_key::unit_attention, asc::not_ready_to_ready_change); } // Return status if not ready if (!IsReady()) { - GetLogger().Trace("Device not ready"); - throw scsi_exception(sense_key::NOT_READY, asc::MEDIUM_NOT_PRESENT); + LogTrace("Device not ready"); + throw scsi_exception(sense_key::not_ready, asc::medium_not_present); } - GetLogger().Trace("Device is ready"); + LogTrace("Device is ready"); } vector PrimaryDevice::HandleInquiry(device_type type, scsi_level level, bool is_removable) const @@ -223,8 +218,8 @@ vector PrimaryDevice::HandleInquiry(device_type type, scsi_level level, buf[0] = static_cast(type); buf[1] = is_removable ? 0x80 : 0x00; buf[2] = static_cast(level); - buf[3] = level >= scsi_level::SCSI_2 ? - static_cast(scsi_level::SCSI_2) : static_cast(scsi_level::SCSI_1_CCS); + buf[3] = level >= scsi_level::scsi_2 ? + static_cast(scsi_level::scsi_2) : static_cast(scsi_level::scsi_1_ccs); buf[4] = 0x1F; // Padded vendor, product, revision @@ -237,7 +232,7 @@ vector PrimaryDevice::HandleRequestSense() const { // Return not ready only if there are no errors if (!GetStatusCode() && !IsReady()) { - throw scsi_exception(sense_key::NOT_READY, asc::MEDIUM_NOT_PRESENT); + throw scsi_exception(sense_key::not_ready, asc::medium_not_present); } // Set 18 bytes including extended sense data @@ -257,14 +252,14 @@ vector PrimaryDevice::HandleRequestSense() const << "Status $" << static_cast(GetController()->GetStatus()) << ", Sense Key $" << static_cast(buf[2]) << ", ASC $" << static_cast(buf[12]); - GetLogger().Trace(s.str()); + LogTrace(s.str()); return buf; } -bool PrimaryDevice::WriteByteSequence(vector&, uint32_t) +bool PrimaryDevice::WriteByteSequence(span) { - GetLogger().Error("Writing bytes is not supported by this device"); + LogError("Writing bytes is not supported by this device"); return false; } @@ -274,10 +269,10 @@ void PrimaryDevice::ReserveUnit() reserving_initiator = GetController()->GetInitiatorId(); if (reserving_initiator != -1) { - GetLogger().Trace("Reserved device for initiator ID " + to_string(reserving_initiator)); + LogTrace("Reserved device for initiator ID " + to_string(reserving_initiator)); } else { - GetLogger().Trace("Reserved device for unknown initiator"); + LogTrace("Reserved device for unknown initiator"); } EnterStatusPhase(); @@ -286,10 +281,10 @@ void PrimaryDevice::ReserveUnit() void PrimaryDevice::ReleaseUnit() { if (reserving_initiator != -1) { - GetLogger().Trace("Released device reserved by initiator ID " + to_string(reserving_initiator)); + LogTrace("Released device reserved by initiator ID " + to_string(reserving_initiator)); } else { - GetLogger().Trace("Released device reserved by unknown initiator"); + LogTrace("Released device reserved by unknown initiator"); } DiscardReservation(); @@ -313,10 +308,10 @@ bool PrimaryDevice::CheckReservation(int initiator_id, scsi_command cmd, bool pr } if (initiator_id != -1) { - GetLogger().Trace("Initiator ID " + to_string(initiator_id) + " tries to access reserved device"); + LogTrace("Initiator ID " + to_string(initiator_id) + " tries to access reserved device"); } else { - GetLogger().Trace("Unknown initiator tries to access reserved device"); + LogTrace("Unknown initiator tries to access reserved device"); } return false; diff --git a/cpp/devices/primary_device.h b/cpp/devices/primary_device.h index e8b10bf1..c30bc123 100644 --- a/cpp/devices/primary_device.h +++ b/cpp/devices/primary_device.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // A device implementing mandatory SCSI primary commands, to be used for subclassing // @@ -18,6 +18,7 @@ #include "device_logger.h" #include #include +#include #include using namespace std; @@ -25,6 +26,8 @@ using namespace scsi_defs; class PrimaryDevice: private ScsiPrimaryCommands, public Device { + friend class AbstractController; + using operation = function; public: @@ -32,15 +35,16 @@ public: PrimaryDevice(PbDeviceType type, int lun) : Device(type, lun) {} ~PrimaryDevice() override = default; - virtual bool Init(const unordered_map&); + virtual bool Init(const param_map&); + virtual void CleanUp() { + // Override if cleanup work is required for a derived device + }; virtual void Dispatch(scsi_command); int GetId() const override; - void SetController(shared_ptr); - - virtual bool WriteByteSequence(vector&, uint32_t); + virtual bool WriteByteSequence(span); int GetSendDelay() const { return send_delay; } @@ -50,15 +54,19 @@ public: void Reset() override; virtual void FlushCache() { - // Devices with a cache have to implement this method + // Devices with a cache have to override this method + } + + virtual vector GetStatistics() const { + // Devices which provide statistics have to override this method + + return vector(); } protected: void AddCommand(scsi_command, const operation&); - const DeviceLogger& GetLogger() const { return logger; } - vector HandleInquiry(scsi_defs::device_type, scsi_level, bool) const; virtual vector InquiryInternal() const = 0; void CheckReady(); @@ -69,16 +77,24 @@ protected: void ReserveUnit() override; void ReleaseUnit() override; - void EnterStatusPhase() const { controller.lock()->Status(); } - void EnterDataInPhase() const { controller.lock()->DataIn(); } - void EnterDataOutPhase() const { controller.lock()->DataOut(); } + void EnterStatusPhase() const { controller->Status(); } + void EnterDataInPhase() const { controller->DataIn(); } + void EnterDataOutPhase() const { controller->DataOut(); } - inline shared_ptr GetController() const { return controller.lock(); } + auto GetController() const { return controller; } + + void LogTrace(const string& s) const { device_logger.Trace(s); } + void LogDebug(const string& s) const { device_logger.Debug(s); } + void LogInfo(const string& s) const { device_logger.Info(s); } + void LogWarn(const string& s) const { device_logger.Warn(s); } + void LogError(const string& s) const { device_logger.Error(s); } private: static const int NOT_RESERVED = -2; + void SetController(AbstractController *); + void TestUnitReady() override; void RequestSense() override; void ReportLuns() override; @@ -86,9 +102,11 @@ private: vector HandleRequestSense() const; - DeviceLogger logger; + // TODO Try to remove this field and use controller->Log*() methods instead + DeviceLogger device_logger; - weak_ptr controller; + // Owned by the controller manager + AbstractController *controller = nullptr; unordered_map commands; diff --git a/cpp/devices/scsi_command_util.cpp b/cpp/devices/scsi_command_util.cpp index 346bdf6f..20bb83f7 100644 --- a/cpp/devices/scsi_command_util.cpp +++ b/cpp/devices/scsi_command_util.cpp @@ -3,31 +3,31 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "shared/piscsi_exceptions.h" -#include "device_logger.h" #include "scsi_command_util.h" +#include #include -#include #include #include using namespace scsi_defs; -void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd, const vector& cdb, - const vector& buf, int length, int sector_size) +string scsi_command_util::ModeSelect(scsi_command cmd, cdb_t cdb, span buf, int length, int sector_size) { assert(cmd == scsi_command::eCmdModeSelect6 || cmd == scsi_command::eCmdModeSelect10); assert(length >= 0); + string result; + // PF if (!(cdb[1] & 0x10)) { // Vendor-specific parameters (SCSI-1) are not supported. // Do not report an error in order to support Apple's HD SC Setup. - return; + return result; } // Skip block descriptors @@ -45,9 +45,9 @@ void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd, // Parse the pages while (length > 0) { // Format device page - if (int page = buf[offset]; page == 0x03) { + if (const int page = buf[offset]; page == 0x03) { if (length < 14) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_PARAMETER_LIST); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_parameter_list); } // With this page the sector size for a subsequent FORMAT can be selected, but only very few @@ -56,8 +56,8 @@ void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd, if (GetInt16(buf, offset + 12) != sector_size) { // With piscsi it is not possible to permanently (by formatting) change the sector size, // because the size is an externally configurable setting only - logger.Warn("In order to change the sector size use the -b option when launching piscsi"); - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_PARAMETER_LIST); + spdlog::warn("In order to change the sector size use the -b option when launching piscsi"); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_parameter_list); } has_valid_page_code = true; @@ -65,7 +65,7 @@ void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd, else { stringstream s; s << "Unknown MODE SELECT page code: $" << setfill('0') << setw(2) << hex << page; - logger.Warn(s.str()); + result = s.str(); } // Advance to the next page @@ -76,8 +76,10 @@ void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd, } if (!has_valid_page_code) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_PARAMETER_LIST); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_parameter_list); } + + return result; } void scsi_command_util::EnrichFormatPage(map>& pages, bool changeable, int sector_size) @@ -97,33 +99,19 @@ void scsi_command_util::AddAppleVendorModePage(map>& pages, bo // No changeable area if (!changeable) { - const char APPLE_DATA[] = "APPLE COMPUTER, INC "; + constexpr const char APPLE_DATA[] = "APPLE COMPUTER, INC "; memcpy(&pages[48].data()[2], APPLE_DATA, sizeof(APPLE_DATA)); } } -int scsi_command_util::GetInt16(const vector& buf, int offset) -{ - assert(buf.size() > static_cast(offset) + 1); - - return (static_cast(buf[offset]) << 8) | buf[offset + 1]; -} - -int scsi_command_util::GetInt16(const vector& buf, int offset) -{ - assert(buf.size() > static_cast(offset) + 1); - - return (buf[offset] << 8) | buf[offset + 1]; -} - -int scsi_command_util::GetInt24(const vector& buf, int offset) +int scsi_command_util::GetInt24(span buf, int offset) { assert(buf.size() > static_cast(offset) + 2); return (buf[offset] << 16) | (buf[offset + 1] << 8) | buf[offset + 2]; } -uint32_t scsi_command_util::GetInt32(const vector& buf, int offset) +uint32_t scsi_command_util::GetInt32(span buf, int offset) { assert(buf.size() > static_cast(offset) + 3); @@ -131,7 +119,7 @@ uint32_t scsi_command_util::GetInt32(const vector& buf, int offset) (static_cast(buf[offset + 2]) << 8) | static_cast(buf[offset + 3]); } -uint64_t scsi_command_util::GetInt64(const vector& buf, int offset) +uint64_t scsi_command_util::GetInt64(span buf, int offset) { assert(buf.size() > static_cast(offset) + 7); @@ -141,42 +129,6 @@ uint64_t scsi_command_util::GetInt64(const vector& buf, int offset) (static_cast(buf[offset + 6]) << 8) | static_cast(buf[offset + 7]); } -void scsi_command_util::SetInt16(vector& buf, int offset, int value) -{ - assert(buf.size() > static_cast(offset) + 1); - - buf[offset] = static_cast(value >> 8); - buf[offset + 1] = static_cast(value); -} - -void scsi_command_util::SetInt32(vector& buf, int offset, uint32_t value) -{ - assert(buf.size() > static_cast(offset) + 3); - - buf[offset] = static_cast(value >> 24); - buf[offset + 1] = static_cast(value >> 16); - buf[offset + 2] = static_cast(value >> 8); - buf[offset + 3] = static_cast(value); -} - -void scsi_command_util::SetInt16(vector& buf, int offset, int value) -{ - assert(buf.size() > static_cast(offset) + 1); - - buf[offset] = static_cast(value >> 8); - buf[offset + 1] = static_cast(value); -} - -void scsi_command_util::SetInt32(vector& buf, int offset, uint32_t value) -{ - assert(buf.size() > static_cast(offset) + 3); - - buf[offset] = static_cast(value >> 24); - buf[offset + 1] = static_cast(value >> 16); - buf[offset + 2] = static_cast(value >> 8); - buf[offset + 3] = static_cast(value); -} - void scsi_command_util::SetInt64(vector& buf, int offset, uint64_t value) { assert(buf.size() > static_cast(offset) + 7); diff --git a/cpp/devices/scsi_command_util.h b/cpp/devices/scsi_command_util.h index bc688252..0fecac8d 100644 --- a/cpp/devices/scsi_command_util.h +++ b/cpp/devices/scsi_command_util.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // Shared code for SCSI command implementations // @@ -12,27 +12,49 @@ #pragma once #include "shared/scsi.h" +#include +#include +#include #include #include using namespace std; -class DeviceLogger; - namespace scsi_command_util { - void ModeSelect(const DeviceLogger&, scsi_defs::scsi_command, const vector&, const vector&, int, int); + string ModeSelect(scsi_defs::scsi_command, cdb_t, span, int, int); void EnrichFormatPage(map>&, bool, int); void AddAppleVendorModePage(map>&, bool); - int GetInt16(const vector&, int); - int GetInt16(const vector&, int); - int GetInt24(const vector&, int); - uint32_t GetInt32(const vector&, int); - uint64_t GetInt64(const vector&, int); - void SetInt16(vector&, int, int); - void SetInt32(vector&, int, uint32_t); - void SetInt16(vector&, int, int); - void SetInt32(vector&, int, uint32_t); + int GetInt16(const auto buf, int offset) + { + assert(buf.size() > static_cast(offset) + 1); + + return (static_cast(buf[offset]) << 8) | buf[offset + 1]; + }; + + template + void SetInt16(vector& buf, int offset, int value) + { + assert(buf.size() > static_cast(offset) + 1); + + buf[offset] = static_cast(value >> 8); + buf[offset + 1] = static_cast(value); + } + + template + void SetInt32(vector& buf, int offset, uint32_t value) + { + assert(buf.size() > static_cast(offset) + 3); + + buf[offset] = static_cast(value >> 24); + buf[offset + 1] = static_cast(value >> 16); + buf[offset + 2] = static_cast(value >> 8); + buf[offset + 3] = static_cast(value); + } + + int GetInt24(span, int); + uint32_t GetInt32(span , int); + uint64_t GetInt64(span, int); void SetInt64(vector&, int, uint64_t); } diff --git a/cpp/devices/scsi_daynaport.cpp b/cpp/devices/scsi_daynaport.cpp index 1fab6e6e..0629ccae 100644 --- a/cpp/devices/scsi_daynaport.cpp +++ b/cpp/devices/scsi_daynaport.cpp @@ -38,7 +38,7 @@ SCSIDaynaPort::SCSIDaynaPort(int lun) : PrimaryDevice(SCDP, lun) SupportsParams(true); } -bool SCSIDaynaPort::Init(const unordered_map& params) +bool SCSIDaynaPort::Init(const param_map& params) { PrimaryDevice::Init(params); @@ -54,16 +54,14 @@ bool SCSIDaynaPort::Init(const unordered_map& params) // In the MacOS driver, it looks like the driver is doing two "READ" system calls. SetSendDelay(DAYNAPORT_READ_HEADER_SZ); - m_bTapEnable = m_tap.Init(GetParams()); - if(!m_bTapEnable){ - GetLogger().Error("Unable to open the TAP interface"); - + tap_enabled = tap.Init(GetParams()); + if (!tap_enabled) { // Not terminating on regular Linux PCs is helpful for testing #if !defined(__x86_64__) && !defined(__X86__) - return false; + return false; #endif } else { - GetLogger().Trace("Tap interface created"); + LogTrace("Tap interface created"); } Reset(); @@ -73,9 +71,14 @@ bool SCSIDaynaPort::Init(const unordered_map& params) return true; } +void SCSIDaynaPort::CleanUp() +{ + tap.CleanUp(); +} + vector SCSIDaynaPort::InquiryInternal() const { - vector buf = HandleInquiry(device_type::PROCESSOR, scsi_level::SCSI_2, false); + vector buf = HandleInquiry(device_type::processor, scsi_level::scsi_2, false); // The Daynaport driver for the Mac expects 37 bytes: Increase additional length and // add a vendor-specific byte in order to satisfy this driver. @@ -116,14 +119,14 @@ vector SCSIDaynaPort::InquiryInternal() const // - The SCSI/Link apparently has about 6KB buffer space for packets. // //--------------------------------------------------------------------------- -int SCSIDaynaPort::Read(const vector& cdb, vector& buf, uint64_t) +int SCSIDaynaPort::Read(cdb_t cdb, vector& buf, uint64_t) { int rx_packet_size = 0; const auto response = (scsi_resp_read_t*)buf.data(); const int requested_length = cdb[4]; - GetLogger().Trace("Read maximum length: " + to_string(requested_length)); + LogTrace("Read maximum length: " + to_string(requested_length)); // At startup the host may send a READ(6) command with a sector count of 1 to read the root sector. // We should respond by going into the status mode with a code of 0x02. @@ -142,17 +145,19 @@ int SCSIDaynaPort::Read(const vector& cdb, vector& buf, uint64_t) // The first 2 bytes are reserved for the length of the packet // The next 4 bytes are reserved for a flag field //rx_packet_size = m_tap.Rx(response->data); - rx_packet_size = m_tap.Receive(&buf[DAYNAPORT_READ_HEADER_SZ]); + rx_packet_size = tap.Receive(&buf[DAYNAPORT_READ_HEADER_SZ]); // If we didn't receive anything, return size of 0 if (rx_packet_size <= 0) { - GetLogger().Trace("No packet received"); + LogTrace("No packet received"); response->length = 0; response->flags = read_data_flags_t::e_no_more_data; return DAYNAPORT_READ_HEADER_SZ; } - GetLogger().Trace("Packet Size " + to_string(rx_packet_size) + ", read count: " + to_string(read_count)); + byte_read_count += rx_packet_size; + + LogTrace("Packet Size " + to_string(rx_packet_size) + ", read count: " + to_string(read_count)); // This is a very basic filter to prevent unnecessary packets from // being sent to the SCSI initiator. @@ -187,11 +192,11 @@ int SCSIDaynaPort::Read(const vector& cdb, vector& buf, uint64_t) for (int i = 0 ; i < 6; i++) { s << " $" << static_cast(response->data[i]); } - GetLogger().Debug(s.str()); + LogDebug(s.str()); // If there are pending packets to be processed, we'll tell the host that the read // length was 0. - if (!m_tap.PendingPackets()) { + if (!tap.HasPendingPackets()) { response->length = 0; response->flags = read_data_flags_t::e_no_more_data; return DAYNAPORT_READ_HEADER_SZ; @@ -216,7 +221,7 @@ int SCSIDaynaPort::Read(const vector& cdb, vector& buf, uint64_t) size = 64; } SetInt16(buf, 0, size); - SetInt32(buf, 2, m_tap.PendingPackets() ? 0x10 : 0x00); + SetInt32(buf, 2, tap.HasPendingPackets() ? 0x10 : 0x00); // Return the packet size + 2 for the length + 4 for the flag field // The CRC was already appended by the ctapdriver @@ -249,25 +254,25 @@ int SCSIDaynaPort::Read(const vector& cdb, vector& buf, uint64_t) // XX XX ... is the actual packet // //--------------------------------------------------------------------------- -bool SCSIDaynaPort::WriteBytes(const vector& cdb, vector& buf, uint32_t) +bool SCSIDaynaPort::Write(cdb_t cdb, span buf) { - const int data_format = cdb[5]; - int data_length = GetInt16(cdb, 3); - - if (data_format == 0x00) { - m_tap.Send(buf.data(), data_length); - GetLogger().Trace("Transmitted " + to_string(data_length) + " byte(s) (00 format)"); + if (const int data_format = cdb[5]; data_format == 0x00) { + const int data_length = GetInt16(cdb, 3); + tap.Send(buf.data(), data_length); + byte_write_count += data_length; + LogTrace("Transmitted " + to_string(data_length) + " byte(s) (00 format)"); } else if (data_format == 0x80) { // The data length is specified in the first 2 bytes of the payload - data_length = buf[1] + ((static_cast(buf[0]) & 0xff) << 8); - m_tap.Send(&(buf.data()[4]), data_length); - GetLogger().Trace("Transmitted " + to_string(data_length) + "byte(s) (80 format)"); + const int data_length = buf[1] + ((static_cast(buf[0]) & 0xff) << 8); + tap.Send(&(buf.data()[4]), data_length); + byte_write_count += data_length; + LogTrace("Transmitted " + to_string(data_length) + "byte(s) (80 format)"); } else { stringstream s; s << "Unknown data format: " << setfill('0') << setw(2) << hex << data_format; - GetLogger().Warn(s.str()); + LogWarn(s.str()); } GetController()->SetBlocks(0); @@ -291,7 +296,7 @@ bool SCSIDaynaPort::WriteBytes(const vector& cdb, vector& buf, uin // - long #3: frames lost // //--------------------------------------------------------------------------- -int SCSIDaynaPort::RetrieveStats(const vector& cdb, vector& buf) const +int SCSIDaynaPort::RetrieveStats(cdb_t cdb, vector& buf) const { memcpy(buf.data(), &m_scsi_link_stats, sizeof(m_scsi_link_stats)); @@ -312,17 +317,17 @@ void SCSIDaynaPort::Read6() // If any commands have a bogus control value, they were probably not // generated by the DaynaPort driver so ignore them - if (GetController()->GetCmd(5) != 0xc0 && GetController()->GetCmd(5) != 0x80) { - GetLogger().Trace("Control value: " + to_string(GetController()->GetCmd(5))); - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + if (GetController()->GetCmdByte(5) != 0xc0 && GetController()->GetCmdByte(5) != 0x80) { + LogTrace("Control value: " + to_string(GetController()->GetCmdByte(5))); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } stringstream s; s << "READ(6) command, record: $" << setfill('0') << setw(8) << hex << record; - GetLogger().Trace(s.str() + ", blocks: " + to_string(GetController()->GetBlocks())); + LogTrace(s.str()); GetController()->SetLength(Read(GetController()->GetCmd(), GetController()->GetBuffer(), record)); - GetLogger().Trace("Length is " + to_string(GetController()->GetLength())); + LogTrace("Length is " + to_string(GetController()->GetLength())); // Set next block GetController()->SetNext(record + 1); @@ -335,7 +340,7 @@ void SCSIDaynaPort::Write6() const // Ensure a sufficient buffer size (because it is not transfer for each block) GetController()->AllocateBuffer(DAYNAPORT_BUFFER_SIZE); - const int data_format = GetController()->GetCmd(5); + const int data_format = GetController()->GetCmdByte(5); if (data_format == 0x00) { GetController()->SetLength(GetInt16(GetController()->GetCmd(), 3)); @@ -346,15 +351,15 @@ void SCSIDaynaPort::Write6() const else { stringstream s; s << "Unknown data format: " << setfill('0') << setw(2) << hex << data_format; - GetLogger().Warn(s.str()); + LogWarn(s.str()); } stringstream s; s << "Length: " << GetController()->GetLength() << ", format: $" << setfill('0') << setw(2) << hex << data_format; - GetLogger().Trace(s.str()); + LogTrace(s.str()); if (GetController()->GetLength() <= 0) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } // Set next block @@ -406,7 +411,7 @@ void SCSIDaynaPort::SetInterfaceMode() const // Check whether this command is telling us to "Set Interface Mode" or "Set MAC Address" GetController()->SetLength(RetrieveStats(GetController()->GetCmd(), GetController()->GetBuffer())); - switch(GetController()->GetCmd(5)){ + switch(GetController()->GetCmdByte(5)){ case CMD_SCSILINK_SETMODE: // Not implemented, do nothing EnterStatusPhase(); @@ -419,21 +424,21 @@ void SCSIDaynaPort::SetInterfaceMode() const default: stringstream s; - s << "Unsupported SetInterface command: " << setfill('0') << setw(2) << hex << GetController()->GetCmd(5); - GetLogger().Warn(s.str()); - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE); + s << "Unsupported SetInterface command: " << setfill('0') << setw(2) << hex << GetController()->GetCmdByte(5); + LogWarn(s.str()); + throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code); break; } } void SCSIDaynaPort::SetMcastAddr() const { - GetController()->SetLength(GetController()->GetCmd(4)); + GetController()->SetLength(GetController()->GetCmdByte(4)); if (GetController()->GetLength() == 0) { stringstream s; - s << "Unsupported SetMcastAddr command: " << setfill('0') << setw(2) << hex << GetController()->GetCmd(2); - GetLogger().Warn(s.str()); - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + s << "Unsupported SetMcastAddr command: " << setfill('0') << setw(2) << hex << GetController()->GetCmdByte(2); + LogWarn(s.str()); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } EnterDataOutPhase(); @@ -451,29 +456,49 @@ void SCSIDaynaPort::SetMcastAddr() const // seconds // //--------------------------------------------------------------------------- -void SCSIDaynaPort::EnableInterface() +void SCSIDaynaPort::EnableInterface() const { - if (GetController()->GetCmd(5) & 0x80) { - if (!m_tap.Enable()) { - GetLogger().Warn("Unable to enable the DaynaPort Interface"); + if (GetController()->GetCmdByte(5) & 0x80) { + if (const string error = tap.IpLink(true); !error.empty()) { + LogWarn("Unable to enable the DaynaPort Interface: " + error); - throw scsi_exception(sense_key::ABORTED_COMMAND); + throw scsi_exception(sense_key::aborted_command); } - m_tap.Flush(); + tap.Flush(); - GetLogger().Info("The DaynaPort interface has been ENABLED"); + LogInfo("The DaynaPort interface has been ENABLED"); } else { - if (!m_tap.Disable()) { - GetLogger().Warn("Unable to disable the DaynaPort Interface"); + if (const string error = tap.IpLink(false); !error.empty()) { + LogWarn("Unable to disable the DaynaPort Interface: " + error); - throw scsi_exception(sense_key::ABORTED_COMMAND); + throw scsi_exception(sense_key::aborted_command); } - GetLogger().Info("The DaynaPort interface has been DISABLED"); + LogInfo("The DaynaPort interface has been DISABLED"); } EnterStatusPhase(); } +vector SCSIDaynaPort::GetStatistics() const +{ + vector statistics = PrimaryDevice::GetStatistics(); + + PbStatistics s; + s.set_id(GetId()); + s.set_unit(GetLun()); + + s.set_category(PbStatisticsCategory::CATEGORY_INFO); + + s.set_key(BYTE_READ_COUNT); + s.set_value(byte_read_count); + statistics.push_back(s); + + s.set_key(BYTE_WRITE_COUNT); + s.set_value(byte_write_count); + statistics.push_back(s); + + return statistics; +} diff --git a/cpp/devices/scsi_daynaport.h b/cpp/devices/scsi_daynaport.h index f0ca53f4..94f45551 100644 --- a/cpp/devices/scsi_daynaport.h +++ b/cpp/devices/scsi_daynaport.h @@ -29,10 +29,11 @@ #pragma once -#include "interfaces/byte_writer.h" #include "primary_device.h" #include "ctapdriver.h" +#include #include +#include #include #include @@ -41,21 +42,30 @@ // DaynaPort SCSI Link // //=========================================================================== -class SCSIDaynaPort : public PrimaryDevice, public ByteWriter +class SCSIDaynaPort : public PrimaryDevice { + uint64_t byte_read_count = 0; + uint64_t byte_write_count = 0; + + inline static const string BYTE_READ_COUNT = "byte_read_count"; + inline static const string BYTE_WRITE_COUNT = "byte_write_count"; + public: explicit SCSIDaynaPort(int); ~SCSIDaynaPort() override = default; - bool Init(const unordered_map&) override; + bool Init(const param_map&) override; + void CleanUp() override; + + param_map GetDefaultParams() const override { return tap.GetDefaultParams(); } // Commands vector InquiryInternal() const override; - int Read(const vector&, vector&, uint64_t); - bool WriteBytes(const vector&, vector&, uint32_t) override; + int Read(cdb_t, vector&, uint64_t); + bool Write(cdb_t, span); - int RetrieveStats(const vector&, vector&) const; + int RetrieveStats(cdb_t, vector&) const; void TestUnitReady() override; void Read6(); @@ -63,7 +73,9 @@ public: void RetrieveStatistics() const; void SetInterfaceMode() const; void SetMcastAddr() const; - void EnableInterface(); + void EnableInterface() const; + + vector GetStatistics() const override; static const int DAYNAPORT_BUFFER_SIZE = 0x1000000; @@ -114,8 +126,7 @@ private: .frames_lost = 0, }; - CTapDriver m_tap; + CTapDriver tap; - // TAP valid flag - bool m_bTapEnable = false; + bool tap_enabled = false; }; diff --git a/cpp/devices/scsi_host_bridge.cpp b/cpp/devices/scsi_host_bridge.cpp index 6e2038c8..394441c1 100644 --- a/cpp/devices/scsi_host_bridge.cpp +++ b/cpp/devices/scsi_host_bridge.cpp @@ -20,7 +20,6 @@ #include "scsi_command_util.h" #include "scsi_host_bridge.h" #include -#include using namespace std; using namespace scsi_defs; @@ -31,7 +30,7 @@ SCSIBR::SCSIBR(int lun) : PrimaryDevice(SCBR, lun) SupportsParams(true); } -bool SCSIBR::Init(const unordered_map& params) +bool SCSIBR::Init(const param_map& params) { PrimaryDevice::Init(params); @@ -43,16 +42,14 @@ bool SCSIBR::Init(const unordered_map& params) AddCommand(scsi_command::eCmdSendMessage10, [this] { SendMessage10(); }); #ifdef __linux__ - // TAP Driver Generation - m_bTapEnable = tap.Init(GetParams()); - if (!m_bTapEnable){ - GetLogger().Error("Unable to open the TAP interface"); + tap_enabled = tap.Init(GetParams()); + if (!tap_enabled){ return false; } #endif // Generate MAC Address - if (m_bTapEnable) { + if (tap_enabled) { tap.GetMacAddr(mac_addr.data()); mac_addr[5]++; } @@ -60,19 +57,24 @@ bool SCSIBR::Init(const unordered_map& params) // Packet reception flag OFF packet_enable = false; - SetReady(m_bTapEnable); + SetReady(tap_enabled); // Not terminating on regular Linux PCs is helpful for testing #if defined(__x86_64__) || defined(__X86__) return true; #else - return m_bTapEnable; + return tap_enabled; #endif } +void SCSIBR::CleanUp() +{ + tap.CleanUp(); +} + vector SCSIBR::InquiryInternal() const { - vector buf = HandleInquiry(device_type::COMMUNICATIONS, scsi_level::SCSI_2, false); + vector buf = HandleInquiry(device_type::communications, scsi_level::scsi_2, false); // The bridge returns more additional bytes than the other devices buf.resize(0x1F + 8 + 5); @@ -83,7 +85,7 @@ vector SCSIBR::InquiryInternal() const buf[36] = '0'; // TAP Enable - if (m_bTapEnable) { + if (tap_enabled) { buf[37] = '1'; } @@ -99,7 +101,7 @@ void SCSIBR::TestUnitReady() EnterStatusPhase(); } -int SCSIBR::GetMessage10(const vector& cdb, vector& buf) +int SCSIBR::GetMessage10(cdb_t cdb, vector& buf) { // Type const int type = cdb[2]; @@ -113,7 +115,7 @@ int SCSIBR::GetMessage10(const vector& cdb, vector& buf) switch (type) { case 1: // Ethernet // Do not process if TAP is invalid - if (!m_bTapEnable) { + if (!tap_enabled) { return 0; } @@ -187,7 +189,7 @@ int SCSIBR::GetMessage10(const vector& cdb, vector& buf) return 0; } -bool SCSIBR::WriteBytes(const vector& cdb, vector& buf, uint32_t) +bool SCSIBR::ReadWrite(cdb_t cdb, vector& buf) { // Type const int type = cdb[2]; @@ -204,7 +206,7 @@ bool SCSIBR::WriteBytes(const vector& cdb, vector& buf, uint32_t) switch (type) { case 1: // Ethernet // Do not process if TAP is invalid - if (!m_bTapEnable) { + if (!tap_enabled) { return false; } @@ -252,7 +254,7 @@ void SCSIBR::GetMessage10() GetController()->SetLength(GetMessage10(GetController()->GetCmd(), GetController()->GetBuffer())); if (GetController()->GetLength() <= 0) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } // Set next block @@ -273,7 +275,7 @@ void SCSIBR::SendMessage10() const { GetController()->SetLength(GetInt24(GetController()->GetCmd(), 6)); if (GetController()->GetLength() <= 0) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } // Ensure a sufficient buffer size (because it is not a transfer for each block) @@ -292,7 +294,7 @@ int SCSIBR::GetMacAddr(vector& mac) const return static_cast(mac_addr.size()); } -void SCSIBR::SetMacAddr(const vector& mac) +void SCSIBR::SetMacAddr(span mac) { memcpy(mac_addr.data(), mac.data(), mac_addr.size()); } @@ -339,7 +341,7 @@ void SCSIBR::GetPacketBuf(vector& buf, int index) packet_enable = false; } -void SCSIBR::SendPacket(const vector& buf, int len) +void SCSIBR::SendPacket(span buf, int len) const { tap.Send(buf.data(), len); } @@ -837,7 +839,7 @@ void SCSIBR::FS_GetCapacity(vector& buf) auto dp = (uint32_t*)buf.data(); const uint32_t nUnit = ntohl(*dp); - Human68k::capacity_t cap; + Human68k::capacity_t cap = {}; fsresult = fs.GetCapacity(nUnit, &cap); cap.freearea = htons(cap.freearea); @@ -878,7 +880,7 @@ void SCSIBR::FS_GetDPB(vector& buf) auto dp = (uint32_t*)buf.data(); const uint32_t nUnit = ntohl(*dp); - Human68k::dpb_t dpb; + Human68k::dpb_t dpb = {}; fsresult = fs.GetDPB(nUnit, &dpb); dpb.sector_size = htons(dpb.sector_size); diff --git a/cpp/devices/scsi_host_bridge.h b/cpp/devices/scsi_host_bridge.h index e5f2dbcd..b2cdf981 100644 --- a/cpp/devices/scsi_host_bridge.h +++ b/cpp/devices/scsi_host_bridge.h @@ -17,16 +17,16 @@ //--------------------------------------------------------------------------- #pragma once -#include "interfaces/byte_writer.h" #include "primary_device.h" #include "ctapdriver.h" #include "cfilesystem.h" #include +#include #include using namespace std; -class SCSIBR : public PrimaryDevice, public ByteWriter +class SCSIBR : public PrimaryDevice { static constexpr const array bcast_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; @@ -35,12 +35,15 @@ public: explicit SCSIBR(int); ~SCSIBR() override = default; - bool Init(const unordered_map&) override; + bool Init(const param_map&) override; + void CleanUp() override; + + param_map GetDefaultParams() const override { return tap.GetDefaultParams(); } // Commands vector InquiryInternal() const override; - int GetMessage10(const vector&, vector&); - bool WriteBytes(const vector&, vector&, uint32_t) override; + int GetMessage10(cdb_t, vector&); + bool ReadWrite(cdb_t, vector&); void TestUnitReady() override; void GetMessage10(); void SendMessage10() const; @@ -48,16 +51,16 @@ public: private: int GetMacAddr(vector&) const; // Get MAC address - void SetMacAddr(const vector&); // Set MAC address + void SetMacAddr(span); // Set MAC address void ReceivePacket(); // Receive a packet - void GetPacketBuf(vector&, int); // Get a packet - void SendPacket(const vector&, int); // Send a packet + void GetPacketBuf(vector&, int); // Get a packet + void SendPacket(span, int) const; // Send a packet CTapDriver tap; // TAP driver - bool m_bTapEnable = false; // TAP valid flag - array mac_addr = {}; // MAC Address + bool tap_enabled = false; // TAP valid flag + array mac_addr = {}; // MAC Address int packet_len = 0; // Receive packet size - array packet_buf; // Receive packet buffer + array packet_buf; // Receive packet buffer bool packet_enable = false; // Received packet valid int ReadFsResult(vector&) const; // Read filesystem (result code) diff --git a/cpp/devices/scsi_printer.cpp b/cpp/devices/scsi_printer.cpp index 23046ee4..393e053e 100644 --- a/cpp/devices/scsi_printer.cpp +++ b/cpp/devices/scsi_printer.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // Implementation of a SCSI printer (see SCSI-2 specification for a command description) // @@ -32,7 +32,6 @@ #include "shared/piscsi_exceptions.h" #include "scsi_command_util.h" #include "scsi_printer.h" -#include #include using namespace std; @@ -45,7 +44,7 @@ SCSIPrinter::SCSIPrinter(int lun) : PrimaryDevice(SCLP, lun) SupportsParams(true); } -bool SCSIPrinter::Init(const unordered_map& params) +bool SCSIPrinter::Init(const param_map& params) { PrimaryDevice::Init(params); @@ -61,7 +60,7 @@ bool SCSIPrinter::Init(const unordered_map& params) AddCommand(scsi_command::eCmdSendDiagnostic, [this] { SendDiagnostic(); }); if (GetParam("cmd").find("%f") == string::npos) { - GetLogger().Trace("Missing filename specifier %f"); + LogTrace("Missing filename specifier %f"); return false; } @@ -74,6 +73,27 @@ bool SCSIPrinter::Init(const unordered_map& params) return true; } +void SCSIPrinter::CleanUp() +{ + PrimaryDevice::CleanUp(); + + if (out.is_open()) { + out.close(); + + error_code error; + remove(path(filename), error); + + filename = ""; + } +} + +param_map SCSIPrinter::GetDefaultParams() const +{ + return { + { "cmd", "lp -oraw %f" } + }; +} + void SCSIPrinter::TestUnitReady() { // The printer is always ready @@ -82,20 +102,22 @@ void SCSIPrinter::TestUnitReady() vector SCSIPrinter::InquiryInternal() const { - return HandleInquiry(device_type::PRINTER, scsi_level::SCSI_2, false); + return HandleInquiry(device_type::printer, scsi_level::scsi_2, false); } void SCSIPrinter::Print() { const uint32_t length = GetInt24(GetController()->GetCmd(), 2); - GetLogger().Trace("Receiving " + to_string(length) + " byte(s) to be printed"); + LogTrace("Expecting to receive " + to_string(length) + " byte(s) to be printed"); if (length > GetController()->GetBuffer().size()) { - GetLogger().Error("Transfer buffer overflow: Buffer size is " + to_string(GetController()->GetBuffer().size()) + + LogError("Transfer buffer overflow: Buffer size is " + to_string(GetController()->GetBuffer().size()) + " bytes, " + to_string(length) + " bytes expected"); - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + ++print_error_count; + + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } GetController()->SetLength(length); @@ -107,9 +129,11 @@ void SCSIPrinter::Print() void SCSIPrinter::SynchronizeBuffer() { if (!out.is_open()) { - GetLogger().Warn("Nothing to print"); + LogWarn("Nothing to print"); - throw scsi_exception(sense_key::ABORTED_COMMAND); + ++print_warning_count; + + throw scsi_exception(sense_key::aborted_command); } string cmd = GetParam("cmd"); @@ -118,26 +142,29 @@ void SCSIPrinter::SynchronizeBuffer() cmd.replace(file_position, 2, filename); error_code error; + LogTrace("Printing file '" + filename + "' with " + to_string(file_size(path(filename), error)) + " byte(s)"); - GetLogger().Trace("Printing file '" + filename + "' with " + to_string(file_size(path(filename), error)) + " byte(s)"); - - GetLogger().Debug("Executing '" + cmd + "'"); + LogDebug("Executing print command '" + cmd + "'"); if (system(cmd.c_str())) { - GetLogger().Error("Printing file '" + filename + "' failed, the printing system might not be configured"); + LogError("Printing file '" + filename + "' failed, the printing system might not be configured"); - Cleanup(); + ++print_error_count; - throw scsi_exception(sense_key::ABORTED_COMMAND); + CleanUp(); + + throw scsi_exception(sense_key::aborted_command); } - Cleanup(); + CleanUp(); EnterStatusPhase(); } -bool SCSIPrinter::WriteByteSequence(vector& buf, uint32_t length) +bool SCSIPrinter::WriteByteSequence(span buf) { + byte_receive_count += buf.size(); + if (!out.is_open()) { vector f(file_template.begin(), file_template.end()); f.push_back(0); @@ -145,7 +172,10 @@ bool SCSIPrinter::WriteByteSequence(vector& buf, uint32_t length) // There is no C++ API that generates a file with a unique name const int fd = mkstemp(f.data()); if (fd == -1) { - GetLogger().Error("Can't create printer output file for pattern '" + filename + "': " + strerror(errno)); + LogError("Can't create printer output file for pattern '" + filename + "': " + strerror(errno)); + + ++print_error_count; + return false; } close(fd); @@ -154,27 +184,55 @@ bool SCSIPrinter::WriteByteSequence(vector& buf, uint32_t length) out.open(filename, ios::binary); if (out.fail()) { - throw scsi_exception(sense_key::ABORTED_COMMAND); + ++print_error_count; + + throw scsi_exception(sense_key::aborted_command); } - GetLogger().Trace("Created printer output file '" + filename + "'"); + LogTrace("Created printer output file '" + filename + "'"); } - GetLogger().Trace("Appending " + to_string(length) + " byte(s) to printer output file ''" + filename + "'"); + LogTrace("Appending " + to_string(buf.size()) + " byte(s) to printer output file ''" + filename + "'"); - out.write((const char*)buf.data(), length); + out.write((const char *)buf.data(), buf.size()); - return !out.fail(); + const bool status = out.fail(); + if (!status) { + ++print_error_count; + } + + return !status; } -void SCSIPrinter::Cleanup() +vector SCSIPrinter::GetStatistics() const { - if (out.is_open()) { - out.close(); + vector statistics = PrimaryDevice::GetStatistics(); - error_code error; - remove(path(filename), error); + PbStatistics s; + s.set_id(GetId()); + s.set_unit(GetLun()); - filename = ""; - } + s.set_category(PbStatisticsCategory::CATEGORY_INFO); + + s.set_key(FILE_PRINT_COUNT); + s.set_value(file_print_count); + statistics.push_back(s); + + s.set_key(BYTE_RECEIVE_COUNT); + s.set_value(byte_receive_count); + statistics.push_back(s); + + s.set_category(PbStatisticsCategory::CATEGORY_ERROR); + + s.set_key(PRINT_ERROR_COUNT); + s.set_value(print_error_count); + statistics.push_back(s); + + s.set_category(PbStatisticsCategory::CATEGORY_WARNING); + + s.set_key(PRINT_WARNING_COUNT); + s.set_value(print_warning_count); + statistics.push_back(s); + + return statistics; } diff --git a/cpp/devices/scsi_printer.h b/cpp/devices/scsi_printer.h index bbad49d1..50ef94b9 100644 --- a/cpp/devices/scsi_printer.h +++ b/cpp/devices/scsi_printer.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // Implementation of a SCSI printer (see SCSI-2 specification for a command description) // @@ -15,25 +15,41 @@ #include #include #include +#include using namespace std; class SCSIPrinter : public PrimaryDevice, private ScsiPrinterCommands { + uint64_t file_print_count = 0; + uint64_t byte_receive_count = 0; + uint64_t print_error_count = 0; + uint64_t print_warning_count = 0; + static const int NOT_RESERVED = -2; static constexpr const char *PRINTER_FILE_PATTERN = "/piscsi_sclp-XXXXXX"; + inline static const string FILE_PRINT_COUNT = "file_print_count"; + inline static const string BYTE_RECEIVE_COUNT = "byte_receive_count"; + inline static const string PRINT_ERROR_COUNT = "print_error_count"; + inline static const string PRINT_WARNING_COUNT = "print_warning_count"; + public: explicit SCSIPrinter(int); ~SCSIPrinter() override = default; - bool Init(const unordered_map&) override; + bool Init(const param_map&) override; + void CleanUp() override; + + param_map GetDefaultParams() const override; vector InquiryInternal() const override; - bool WriteByteSequence(vector&, uint32_t) override; + bool WriteByteSequence(span) override; + + vector GetStatistics() const override; private: @@ -44,8 +60,6 @@ private: void Print() override; void SynchronizeBuffer(); - void Cleanup(); - string file_template; string filename; diff --git a/cpp/devices/scsicd.cpp b/cpp/devices/scsicd.cpp index bcea0386..200c36c3 100644 --- a/cpp/devices/scsicd.cpp +++ b/cpp/devices/scsicd.cpp @@ -21,7 +21,7 @@ using namespace scsi_defs; using namespace scsi_command_util; -SCSICD::SCSICD(int lun, const unordered_set& sector_sizes, scsi_defs::scsi_level level) +SCSICD::SCSICD(int lun, const unordered_set& sector_sizes, scsi_defs::scsi_level level) : Disk(SCCD, lun), scsi_level(level) { SetSectorSizes(sector_sizes); @@ -31,7 +31,7 @@ SCSICD::SCSICD(int lun, const unordered_set& sector_sizes, scsi_defs:: SetLockable(true); } -bool SCSICD::Init(const unordered_map& params) +bool SCSICD::Init(const param_map& params) { Disk::Init(params); @@ -116,7 +116,7 @@ void SCSICD::OpenIso() if (rawfile) { if (size % 2536) { - GetLogger().Warn("Raw ISO CD-ROM file size is not a multiple of 2536 bytes but is " + LogWarn("Raw ISO CD-ROM file size is not a multiple of 2536 bytes but is " + to_string(size) + " bytes"); } @@ -165,7 +165,7 @@ void SCSICD::ReadToc() vector SCSICD::InquiryInternal() const { - return HandleInquiry(device_type::CD_ROM, scsi_level, true); + return HandleInquiry(device_type::cd_rom, scsi_level, true); } void SCSICD::SetUpModePages(map>& pages, int page, bool changeable) const @@ -216,13 +216,13 @@ void SCSICD::AddVendorPage(map>& pages, int page, bool changea } } -int SCSICD::Read(const vector& cdb, vector& buf, uint64_t block) +int SCSICD::Read(span buf, uint64_t block) { CheckReady(); const int index = SearchTrack(static_cast(block)); if (index < 0) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE); + throw scsi_exception(sense_key::illegal_request, asc::lba_out_of_range); } assert(tracks[index]); @@ -241,15 +241,15 @@ int SCSICD::Read(const vector& cdb, vector& buf, uint64_t block) } assert(dataindex >= 0); - return Disk::Read(cdb, buf, block); + return Disk::Read(buf, block); } -int SCSICD::ReadTocInternal(const vector& cdb, vector& buf) +int SCSICD::ReadTocInternal(cdb_t cdb, vector& buf) { CheckReady(); // If ready, there is at least one track - assert(tracks.size() > 0); + assert(!tracks.empty()); assert(tracks[0]); // Get allocation length, clear buffer @@ -263,7 +263,7 @@ int SCSICD::ReadTocInternal(const vector& cdb, vector& buf) const int last = tracks[tracks.size() - 1]->GetTrackNo(); // Except for AA if (cdb[6] > last && cdb[6] != 0xaa) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } // Check start index @@ -280,7 +280,7 @@ int SCSICD::ReadTocInternal(const vector& cdb, vector& buf) // AA if not found or internal error if (!tracks[index]) { if (cdb[6] != 0xaa) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } // Returns the final LBA+1 because it is AA diff --git a/cpp/devices/scsicd.h b/cpp/devices/scsicd.h index e30ab743..db2620de 100644 --- a/cpp/devices/scsicd.h +++ b/cpp/devices/scsicd.h @@ -17,20 +17,23 @@ #include "cd_track.h" #include "disk.h" #include "interfaces/scsi_mmc_commands.h" +#include +#include +#include class SCSICD : public Disk, private ScsiMmcCommands { public: - SCSICD(int, const unordered_set&, scsi_defs::scsi_level = scsi_level::SCSI_2); + SCSICD(int, const unordered_set&, scsi_defs::scsi_level = scsi_level::scsi_2); ~SCSICD() override = default; - bool Init(const unordered_map&) override; + bool Init(const param_map&) override; void Open() override; vector InquiryInternal() const override; - int Read(const vector&, vector&, uint64_t) override; + int Read(span, uint64_t) override; protected: @@ -39,7 +42,7 @@ protected: private: - int ReadTocInternal(const vector&, vector&); + int ReadTocInternal(cdb_t, vector&); void AddCDROMPage(map>&, bool) const; void AddCDDAPage(map>&, bool) const; diff --git a/cpp/devices/scsihd.cpp b/cpp/devices/scsihd.cpp index 0b0cca89..316c7575 100644 --- a/cpp/devices/scsihd.cpp +++ b/cpp/devices/scsihd.cpp @@ -5,7 +5,7 @@ // // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) // Copyright (C) 2014-2020 GIMONS -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // Copyright (C) akuker // // Licensed under the BSD 3-Clause License. @@ -14,8 +14,8 @@ //--------------------------------------------------------------------------- #include "shared/piscsi_exceptions.h" -#include "scsihd.h" #include "scsi_command_util.h" +#include "scsihd.h" using namespace scsi_command_util; @@ -70,7 +70,7 @@ void SCSIHD::Open() { assert(!IsReady()); - off_t size = GetFileSize(); + const off_t size = GetFileSize(); // Sector size (default 512 bytes) and number of blocks SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 512); @@ -81,12 +81,15 @@ void SCSIHD::Open() vector SCSIHD::InquiryInternal() const { - return HandleInquiry(device_type::DIRECT_ACCESS, scsi_level, IsRemovable()); + return HandleInquiry(device_type::direct_access, scsi_level, IsRemovable()); } -void SCSIHD::ModeSelect(scsi_command cmd, const vector& cdb, const vector& buf, int length) const +void SCSIHD::ModeSelect(scsi_command cmd, cdb_t cdb, span buf, int length) const { - scsi_command_util::ModeSelect(GetLogger(), cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount()); + if (const string result = scsi_command_util::ModeSelect(cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount()); + !result.empty()) { + LogWarn(result); + } } void SCSIHD::AddFormatPage(map>& pages, bool changeable) const diff --git a/cpp/devices/scsihd.h b/cpp/devices/scsihd.h index 44cef7ed..8216ae2a 100644 --- a/cpp/devices/scsihd.h +++ b/cpp/devices/scsihd.h @@ -5,7 +5,7 @@ // // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) // Copyright (C) 2014-2020 GIMONS -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // Copyright (C) akuker // // Licensed under the BSD 3-Clause License. @@ -18,6 +18,7 @@ #include "shared/scsi.h" #include "disk.h" #include +#include #include #include @@ -27,7 +28,7 @@ class SCSIHD : public Disk public: - SCSIHD(int, const unordered_set&, bool, scsi_defs::scsi_level = scsi_level::SCSI_2); + SCSIHD(int, const unordered_set&, bool, scsi_defs::scsi_level = scsi_level::scsi_2); ~SCSIHD() override = default; void FinalizeSetup(off_t); @@ -36,7 +37,7 @@ public: // Commands vector InquiryInternal() const override; - void ModeSelect(scsi_defs::scsi_command, const vector&, const vector&, int) const override; + void ModeSelect(scsi_defs::scsi_command, cdb_t, span, int) const override; void AddFormatPage(map>&, bool) const override; void AddVendorPage(map>&, int, bool) const override; diff --git a/cpp/devices/scsihd_nec.cpp b/cpp/devices/scsihd_nec.cpp index 4a50d8a0..5592e95c 100644 --- a/cpp/devices/scsihd_nec.cpp +++ b/cpp/devices/scsihd_nec.cpp @@ -1,14 +1,15 @@ //--------------------------------------------------------------------------- // -// SCSI Target Emulator PiSCSI -// for Raspberry Pi +// SCSI Target Emulator PiSCSI +// for Raspberry Pi // -// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) -// Copyright (C) 2014-2020 GIMONS -// Copyright (C) akuker +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) 2021-2023 Uwe Seimet +// Copyright (C) akuker // -// Licensed under the BSD 3-Clause License. -// See LICENSE file in the project root folder. +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. // //--------------------------------------------------------------------------- @@ -47,7 +48,7 @@ void SCSIHD_NEC::Open() FinalizeSetup(image_offset); } -pair SCSIHD_NEC::SetParameters(const array& data, int size) +pair SCSIHD_NEC::SetParameters(span data, int size) { array root_sector = {}; memcpy(root_sector.data(), data.data(), root_sector.size()); @@ -107,12 +108,12 @@ pair SCSIHD_NEC::SetParameters(const array& data, int size) throw io_exception("Invalid NEC sector size of " + to_string(sector_size) + " byte(s)"); } - return make_pair(image_size, sector_size); + return { image_size, sector_size }; } vector SCSIHD_NEC::InquiryInternal() const { - return HandleInquiry(device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, false); + return HandleInquiry(device_type::direct_access, scsi_level::scsi_1_ccs, false); } void SCSIHD_NEC::AddFormatPage(map>& pages, bool changeable) const diff --git a/cpp/devices/scsihd_nec.h b/cpp/devices/scsihd_nec.h index ce710347..eec3c1be 100644 --- a/cpp/devices/scsihd_nec.h +++ b/cpp/devices/scsihd_nec.h @@ -33,7 +33,7 @@ class SCSIHD_NEC : public SCSIHD //NOSONAR The inheritance hierarchy depth is ac { public: - explicit SCSIHD_NEC(int lun) : SCSIHD(lun, sector_sizes, false) {} + explicit SCSIHD_NEC(int lun) : SCSIHD(lun, { 512 }, false) {} ~SCSIHD_NEC() override = default; void Open() override; @@ -47,13 +47,11 @@ protected: private: - pair SetParameters(const array&, int); + pair SetParameters(span, int); static int GetInt16LittleEndian(const uint8_t *); static int GetInt32LittleEndian(const uint8_t *); - static inline const unordered_set sector_sizes = { 512 }; - // Image file offset off_t image_offset = 0; diff --git a/cpp/devices/scsimo.cpp b/cpp/devices/scsimo.cpp index 2eed267d..167064a1 100644 --- a/cpp/devices/scsimo.cpp +++ b/cpp/devices/scsimo.cpp @@ -1,14 +1,15 @@ //--------------------------------------------------------------------------- // -// SCSI Target Emulator PiSCSI -// for Raspberry Pi +// SCSI Target Emulator PiSCSI +// for Raspberry Pi // -// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) -// Copyright (C) 2014-2020 GIMONS -// Copyright (C) akuker +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) akuker // -// Licensed under the BSD 3-Clause License. -// See LICENSE file in the project root folder. +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. // //--------------------------------------------------------------------------- @@ -23,13 +24,13 @@ SCSIMO::SCSIMO(int lun, const unordered_set& sector_sizes) : Disk(SCMO SetSectorSizes(sector_sizes); // 128 MB, 512 bytes per sector, 248826 sectors - geometries[512 * 248826] = make_pair(512, 248826); + geometries[512 * 248826] = { 512, 248826 }; // 230 MB, 512 bytes per block, 446325 sectors - geometries[512 * 446325] = make_pair(512, 446325); + geometries[512 * 446325] = { 512, 446325 }; // 540 MB, 512 bytes per sector, 1041500 sectors - geometries[512 * 1041500] = make_pair(512, 1041500); + geometries[512 * 1041500] = { 512, 1041500 }; // 640 MB, 20248 bytes per sector, 310352 sectors - geometries[2048 * 310352] = make_pair(2048, 310352); + geometries[2048 * 310352] = { 2048, 310352 }; SetProtectable(true); SetRemovable(true); @@ -61,7 +62,7 @@ void SCSIMO::Open() vector SCSIMO::InquiryInternal() const { - return HandleInquiry(device_type::OPTICAL_MEMORY, scsi_level::SCSI_2, true); + return HandleInquiry(device_type::optical_memory, scsi_level::scsi_2, true); } void SCSIMO::SetUpModePages(map>& pages, int page, bool changeable) const @@ -89,9 +90,12 @@ void SCSIMO::AddOptionPage(map>& pages, bool) const // Do not report update blocks } -void SCSIMO::ModeSelect(scsi_command cmd, const vector& cdb, const vector& buf, int length) const +void SCSIMO::ModeSelect(scsi_command cmd, cdb_t cdb, span buf, int length) const { - scsi_command_util::ModeSelect(GetLogger(), cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount()); + if (const string result = scsi_command_util::ModeSelect(cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount()); + !result.empty()) { + LogWarn(result); + } } // diff --git a/cpp/devices/scsimo.h b/cpp/devices/scsimo.h index c3253efc..41735d3b 100644 --- a/cpp/devices/scsimo.h +++ b/cpp/devices/scsimo.h @@ -5,6 +5,7 @@ // // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) // Copyright (C) 2014-2020 GIMONS +// Copyright (C) 2022-2023 Uwe Seimet // Copyright (C) akuker // // Licensed under the BSD 3-Clause License. @@ -15,6 +16,7 @@ #pragma once #include "disk.h" +#include #include #include @@ -30,7 +32,7 @@ public: void Open() override; vector InquiryInternal() const override; - void ModeSelect(scsi_defs::scsi_command, const vector&, const vector&, int) const override; + void ModeSelect(scsi_defs::scsi_command, cdb_t, span, int) const override; protected: diff --git a/cpp/devices/storage_device.cpp b/cpp/devices/storage_device.cpp index 991c1073..41b0ad8a 100644 --- a/cpp/devices/storage_device.cpp +++ b/cpp/devices/storage_device.cpp @@ -3,15 +3,13 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "shared/piscsi_exceptions.h" #include "storage_device.h" -#include #include -#include using namespace std; using namespace filesystem; @@ -22,22 +20,43 @@ StorageDevice::StorageDevice(PbDeviceType type, int lun) : ModePageDevice(type, SetStoppable(true); } +void StorageDevice::CleanUp() +{ + UnreserveFile(); + + ModePageDevice::CleanUp(); +} + +void StorageDevice::SetFilename(string_view f) +{ + filename = filesystem::path(f); + + // Permanently write-protected + SetReadOnly(IsReadOnlyFile()); + + SetProtectable(!IsReadOnlyFile()); + + if (IsReadOnlyFile()) { + SetProtected(false); + } +} + void StorageDevice::ValidateFile() { if (blocks == 0) { throw io_exception(string(GetTypeString()) + " device has 0 blocks"); } - if (!exists(path(filename))) { - throw file_not_found_exception("Image file '" + filename + "' for " + GetTypeString() + " device does not exist"); + if (!exists(filename)) { + throw file_not_found_exception("Image file '" + filename.string() + "' for " + GetTypeString() + " device does not exist"); } if (GetFileSize() > 2LL * 1024 * 1024 * 1024 * 1024) { - throw io_exception("Drive capacity cannot exceed 2 TiB"); + throw io_exception("Image files > 2 TiB are not supported"); } // TODO Check for duplicate handling of these properties (-> piscsi_executor.cpp) - if (access(filename.c_str(), W_OK)) { + if (IsReadOnlyFile()) { // Permanently write-protected SetReadOnly(true); SetProtectable(false); @@ -50,28 +69,28 @@ void StorageDevice::ValidateFile() SetReady(true); } -void StorageDevice::ReserveFile(const string& file, int id, int lun) const +void StorageDevice::ReserveFile() const { - assert(!file.empty()); - assert(reserved_files.find(file) == reserved_files.end()); + assert(!filename.empty()); + assert(!reserved_files.contains(filename.string())); - reserved_files[file] = make_pair(id, lun); + reserved_files[filename.string()] = { GetId(), GetLun() }; } void StorageDevice::UnreserveFile() { - reserved_files.erase(filename); + reserved_files.erase(filename.string()); - filename = ""; + filename.clear(); } id_set StorageDevice::GetIdsForReservedFile(const string& file) { if (const auto& it = reserved_files.find(file); it != reserved_files.end()) { - return make_pair(it->second.first, it->second.second); + return { it->second.first, it->second.second }; } - return make_pair(-1, -1); + return { -1, -1 }; } void StorageDevice::UnreserveAll() @@ -79,9 +98,9 @@ void StorageDevice::UnreserveAll() reserved_files.clear(); } -bool StorageDevice::FileExists(const string& file) +bool StorageDevice::FileExists(string_view file) { - return exists(file); + return exists(path(file)); } bool StorageDevice::IsReadOnlyFile() const @@ -91,10 +110,10 @@ bool StorageDevice::IsReadOnlyFile() const off_t StorageDevice::GetFileSize() const { - // filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle more than 2 GiB - if (struct stat st; !stat(filename.c_str(), &st)) { - return st.st_size; + try { + return file_size(filename); + } + catch (const filesystem_error& e) { + throw io_exception("Can't get size of '" + filename.string() + "': " + e.what()); } - - throw io_exception("Can't get size of '" + filename + "'"); } diff --git a/cpp/devices/storage_device.h b/cpp/devices/storage_device.h index 10c6b37a..85fc3c6d 100644 --- a/cpp/devices/storage_device.h +++ b/cpp/devices/storage_device.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // The base class for all mass storage devices with image file support // @@ -11,14 +11,14 @@ #pragma once +#include "shared/piscsi_util.h" #include "mode_page_device.h" #include #include +#include using namespace std; -using id_set = pair; - class StorageDevice : public ModePageDevice { public: @@ -26,24 +26,27 @@ public: StorageDevice(PbDeviceType, int); ~StorageDevice() override = default; + void CleanUp() override; + virtual void Open() = 0; - string GetFilename() const { return filename; } - void SetFilename(string_view f) { filename = f; } + string GetFilename() const { return filename.string(); } + void SetFilename(string_view); uint64_t GetBlockCount() const { return blocks; } - void ReserveFile(const string&, int, int) const; + void ReserveFile() const; void UnreserveFile(); + // TODO Remove this method, it is only used by the unit tests static void UnreserveAll(); - static bool FileExists(const string&); - bool IsReadOnlyFile() const; + static bool FileExists(string_view); void SetMediumChanged(bool b) { medium_changed = b; } - static unordered_map GetReservedFiles() { return reserved_files; } - static void SetReservedFiles(const unordered_map& r) { reserved_files = r; } + static auto GetReservedFiles() { return reserved_files; } + static void SetReservedFiles(const unordered_map>& r) + { reserved_files = r; } static id_set GetIdsForReservedFile(const string&); protected: @@ -58,13 +61,14 @@ protected: private: - // Total number of blocks + bool IsReadOnlyFile() const; + uint64_t blocks = 0; - string filename; + filesystem::path filename; bool medium_changed = false; // The list of image files in use and the IDs and LUNs using these files - static inline unordered_map reserved_files; + static inline unordered_map> reserved_files; }; diff --git a/cpp/hal/connection_type/connection_aibom.h b/cpp/hal/connection_type/connection_aibom.h index 0dd3c314..1328f5bc 100644 --- a/cpp/hal/connection_type/connection_aibom.h +++ b/cpp/hal/connection_type/connection_aibom.h @@ -10,7 +10,6 @@ #pragma once -#include "hal/pi_defs/bpi-m2p.h" #include // @@ -55,33 +54,3 @@ const static int PIN_CD = 11; // CD const static int PIN_IO = 23; // IO const static int PIN_BSY = 24; // BSY const static int PIN_SEL = 8; // SEL - -// Warning: The Allwinner/Banana Pi GPIO numbers DO NOT correspond to -// the Raspberry Pi GPIO numbers. -// For example, Pin 7 is GPIO4 on a Raspberry Pi. Its GPIO 6 on a Banana Pi -// For Banana Pi, the pins are specified by physical pin number, NOT GPIO number -// (The Macro's convert the pin number to logical BPi GPIO number) -const static int BPI_PIN_ACT = BPI_M2P_07; // ACTIVE -const static int BPI_PIN_ENB = BPI_M2P_11; // ENABLE -const static int BPI_PIN_IND = BPI_M2P_13; // INITIATOR CTRL DIRECTION -const static int BPI_PIN_TAD = -1; // TARGET CTRL DIRECTION -const static int BPI_PIN_DTD = BPI_M2P_12; // DATA DIRECTION - -const static int BPI_PIN_DT0 = BPI_M2P_31; // Data 0 -const static int BPI_PIN_DT1 = BPI_M2P_32; // Data 1 -const static int BPI_PIN_DT2 = BPI_M2P_33; // Data 2 -const static int BPI_PIN_DT3 = BPI_M2P_36; // Data 3 -const static int BPI_PIN_DT4 = BPI_M2P_35; // Data 4 -const static int BPI_PIN_DT5 = BPI_M2P_38; // Data 5 -const static int BPI_PIN_DT6 = BPI_M2P_37; // Data 6 -const static int BPI_PIN_DT7 = BPI_M2P_40; // Data 7 -const static int BPI_PIN_DP = BPI_M2P_29; // Data parity -const static int BPI_PIN_ATN = BPI_M2P_15; // ATN -const static int BPI_PIN_RST = BPI_M2P_22; // RST -const static int BPI_PIN_ACK = BPI_M2P_19; // ACK -const static int BPI_PIN_REQ = BPI_M2P_26; // REQ -const static int BPI_PIN_MSG = BPI_M2P_21; // MSG -const static int BPI_PIN_CD = BPI_M2P_23; // CD -const static int BPI_PIN_IO = BPI_M2P_16; // IO -const static int BPI_PIN_BSY = BPI_M2P_18; // BSY -const static int BPI_PIN_SEL = BPI_M2P_24; // SEL diff --git a/cpp/hal/connection_type/connection_fullspec.h b/cpp/hal/connection_type/connection_fullspec.h index b5e706f2..9a21c3c7 100644 --- a/cpp/hal/connection_type/connection_fullspec.h +++ b/cpp/hal/connection_type/connection_fullspec.h @@ -10,7 +10,6 @@ #pragma once -#include "hal/pi_defs/bpi-m2p.h" #include // @@ -55,33 +54,3 @@ const static int PIN_CD = 24; // CD const static int PIN_IO = 25; // IO const static int PIN_BSY = 26; // BSY const static int PIN_SEL = 27; // SEL - -// Warning: The Allwinner/Banana Pi GPIO numbers DO NOT correspond to -// the Raspberry Pi GPIO numbers. -// For example, Pin 7 is GPIO4 on a Raspberry Pi. Its GPIO 6 on a Banana Pi -// For Banana Pi, the pins are specified by physical pin number, NOT GPIO number -// (The Macro's convert the pin number to logical BPi GPIO number) -const static int BPI_PIN_ACT = BPI_M2P_07; // ACTIVE -const static int BPI_PIN_ENB = BPI_M2P_29; // ENABLE -const static int BPI_PIN_IND = BPI_M2P_31; // INITIATOR CTRL DIRECTION -const static int BPI_PIN_TAD = BPI_M2P_26; // TARGET CTRL DIRECTION -const static int BPI_PIN_DTD = BPI_M2P_24; // DATA DIRECTION - -const static int BPI_PIN_DT0 = BPI_M2P_19; // Data 0 -const static int BPI_PIN_DT1 = BPI_M2P_23; // Data 1 -const static int BPI_PIN_DT2 = BPI_M2P_32; // Data 2 -const static int BPI_PIN_DT3 = BPI_M2P_33; // Data 3 -const static int BPI_PIN_DT4 = BPI_M2P_08; // Data 4 -const static int BPI_PIN_DT5 = BPI_M2P_10; // Data 5 -const static int BPI_PIN_DT6 = BPI_M2P_36; // Data 6 -const static int BPI_PIN_DT7 = BPI_M2P_11; // Data 7 -const static int BPI_PIN_DP = BPI_M2P_12; // Data parity -const static int BPI_PIN_ATN = BPI_M2P_35; // ATN -const static int BPI_PIN_RST = BPI_M2P_38; // RST -const static int BPI_PIN_ACK = BPI_M2P_40; // ACK -const static int BPI_PIN_REQ = BPI_M2P_15; // REQ -const static int BPI_PIN_MSG = BPI_M2P_16; // MSG -const static int BPI_PIN_CD = BPI_M2P_18; // CD -const static int BPI_PIN_IO = BPI_M2P_22; // IO -const static int BPI_PIN_BSY = BPI_M2P_37; // BSY -const static int BPI_PIN_SEL = BPI_M2P_13; // SEL diff --git a/cpp/hal/connection_type/connection_gamernium.h b/cpp/hal/connection_type/connection_gamernium.h index eeeb4a5d..70751560 100644 --- a/cpp/hal/connection_type/connection_gamernium.h +++ b/cpp/hal/connection_type/connection_gamernium.h @@ -10,7 +10,6 @@ #pragma once -#include "hal/pi_defs/bpi-m2p.h" #include // @@ -55,33 +54,3 @@ const static int PIN_CD = 18; // CD const static int PIN_IO = 4; // IO const static int PIN_BSY = 27; // BSY const static int PIN_SEL = 23; // SEL - -// Warning: The Allwinner/Banana Pi GPIO numbers DO NOT correspond to -// the Raspberry Pi GPIO numbers. -// For example, Pin 7 is GPIO4 on a Raspberry Pi. Its GPIO 6 on a Banana Pi -// For Banana Pi, the pins are specified by physical pin number, NOT GPIO number -// (The Macro's convert the pin number to logical BPi GPIO number) -const static int BPI_PIN_ACT = BPI_M2P_08; // ACTIVE -const static int BPI_PIN_ENB = BPI_M2P_31; // ENABLE -const static int BPI_PIN_IND = BPI_M2P_04; // INITIATOR CTRL DIRECTION -const static int BPI_PIN_TAD = BPI_M2P_24; // TARGET CTRL DIRECTION -const static int BPI_PIN_DTD = BPI_M2P_29; // DATA DIRECTION - -const static int BPI_PIN_DT0 = BPI_M2P_40; // Data 0 -const static int BPI_PIN_DT1 = BPI_M2P_37; // Data 1 -const static int BPI_PIN_DT2 = BPI_M2P_38; // Data 2 -const static int BPI_PIN_DT3 = BPI_M2P_35; // Data 3 -const static int BPI_PIN_DT4 = BPI_M2P_36; // Data 4 -const static int BPI_PIN_DT5 = BPI_M2P_33; // Data 5 -const static int BPI_PIN_DT6 = BPI_M2P_32; // Data 6 -const static int BPI_PIN_DT7 = BPI_M2P_23; // Data 7 -const static int BPI_PIN_DP = BPI_M2P_22; // Data parity -const static int BPI_PIN_ATN = BPI_M2P_19; // ATN -const static int BPI_PIN_RST = BPI_M2P_15; // RST -const static int BPI_PIN_ACK = BPI_M2P_18; // ACK -const static int BPI_PIN_REQ = BPI_M2P_10; // REQ -const static int BPI_PIN_MSG = BPI_M2P_11; // MSG -const static int BPI_PIN_CD = BPI_M2P_12; // CD -const static int BPI_PIN_IO = BPI_M2P_07; // IO -const static int BPI_PIN_BSY = BPI_M2P_13; // BSY -const static int BPI_PIN_SEL = BPI_M2P_16; // SEL diff --git a/cpp/hal/connection_type/connection_standard.h b/cpp/hal/connection_type/connection_standard.h index fdde2784..7772347f 100644 --- a/cpp/hal/connection_type/connection_standard.h +++ b/cpp/hal/connection_type/connection_standard.h @@ -10,7 +10,6 @@ #pragma once -#include "hal/pi_defs/bpi-m2p.h" #include // @@ -55,33 +54,3 @@ const static int PIN_CD = 24; // CD const static int PIN_IO = 25; // IO const static int PIN_BSY = 26; // BSY const static int PIN_SEL = 27; // SEL - -// Warning: The Allwinner/Banana Pi GPIO numbers DO NOT correspond to -// the Raspberry Pi GPIO numbers. -// For example, Pin 7 is GPIO4 on a Raspberry Pi. Its GPIO 6 on a Banana Pi -// For Banana Pi, the pins are specified by physical pin number, NOT GPIO number -// (The Macro's convert the pin number to logical BPi GPIO number) -const static int BPI_PIN_ACT = BPI_M2P_07; // ACTIVE -const static int BPI_PIN_ENB = BPI_M2P_29; // ENABLE -const static int BPI_PIN_IND = -1; // INITIATOR CTRL DIRECTION -const static int BPI_PIN_TAD = -1; // TARGET CTRL DIRECTION -const static int BPI_PIN_DTD = -1; // DATA DIRECTION - -const static int BPI_PIN_DT0 = BPI_M2P_19; // Data 0 -const static int BPI_PIN_DT1 = BPI_M2P_23; // Data 1 -const static int BPI_PIN_DT2 = BPI_M2P_32; // Data 2 -const static int BPI_PIN_DT3 = BPI_M2P_33; // Data 3 -const static int BPI_PIN_DT4 = BPI_M2P_08; // Data 4 -const static int BPI_PIN_DT5 = BPI_M2P_10; // Data 5 -const static int BPI_PIN_DT6 = BPI_M2P_36; // Data 6 -const static int BPI_PIN_DT7 = BPI_M2P_11; // Data 7 -const static int BPI_PIN_DP = BPI_M2P_12; // Data parity -const static int BPI_PIN_ATN = BPI_M2P_35; // ATN -const static int BPI_PIN_RST = BPI_M2P_38; // RST -const static int BPI_PIN_ACK = BPI_M2P_40; // ACK -const static int BPI_PIN_REQ = BPI_M2P_15; // REQ -const static int BPI_PIN_MSG = BPI_M2P_16; // MSG -const static int BPI_PIN_CD = BPI_M2P_18; // CD -const static int BPI_PIN_IO = BPI_M2P_22; // IO -const static int BPI_PIN_BSY = BPI_M2P_37; // BSY -const static int BPI_PIN_SEL = BPI_M2P_13; // SEL diff --git a/cpp/hal/data_sample.h b/cpp/hal/data_sample.h index 189c519d..c5a57e30 100644 --- a/cpp/hal/data_sample.h +++ b/cpp/hal/data_sample.h @@ -12,6 +12,7 @@ #pragma once #include "shared/scsi.h" +#include #include using namespace scsi_defs; diff --git a/cpp/hal/data_sample_bananam2p.cpp b/cpp/hal/data_sample_bananam2p.cpp deleted file mode 100644 index 8e00c15b..00000000 --- a/cpp/hal/data_sample_bananam2p.cpp +++ /dev/null @@ -1,64 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Copyright (C) 2022 akuker -// -// [ SCSI Bus Monitor ] -// -//--------------------------------------------------------------------------- - -#include "data_sample_bananam2p.h" -#include "hal/sunxi_utils.h" -#include - -uint8_t DataSample_BananaM2p::GetDAT() const -{ - uint8_t ret_val = 0; - ret_val |= GetSignal(BPI_PIN_DT0) ? 0x01 : 0x00; // NOSONAR: GCC 10 doesn't fully support std::byte - ret_val |= GetSignal(BPI_PIN_DT1) ? 0x02 : 0x00; // NOSONAR: GCC 10 doesn't fully support std::byte - ret_val |= GetSignal(BPI_PIN_DT2) ? 0x04 : 0x00; // NOSONAR: GCC 10 doesn't fully support std::byte - ret_val |= GetSignal(BPI_PIN_DT3) ? 0x08 : 0x00; // NOSONAR: GCC 10 doesn't fully support std::byte - ret_val |= GetSignal(BPI_PIN_DT4) ? 0x10 : 0x00; // NOSONAR: GCC 10 doesn't fully support std::byte - ret_val |= GetSignal(BPI_PIN_DT5) ? 0x20 : 0x00; // NOSONAR: GCC 10 doesn't fully support std::byte - ret_val |= GetSignal(BPI_PIN_DT6) ? 0x40 : 0x00; // NOSONAR: GCC 10 doesn't fully support std::byte - ret_val |= GetSignal(BPI_PIN_DT7) ? 0x80 : 0x00; // NOSONAR: GCC 10 doesn't fully support std::byte - return ret_val; -} - -bool DataSample_BananaM2p::GetSignal(int pin) const -{ - int bank = SunXI::GPIO_BANK(pin); - int num = SunXI::GPIO_NUM(pin); - - return (bool)((data[bank] >> num) & 0x1); -} - -// This will return the Banana Pi data in the "Raspberry Pi" data format. -uint32_t DataSample_BananaM2p::GetRawCapture() const -{ - uint32_t rpi_data = 0; - - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_BSY)) << PIN_BSY; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_SEL)) << PIN_SEL; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_ATN)) << PIN_ATN; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_ACK)) << PIN_ACK; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_RST)) << PIN_RST; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_MSG)) << PIN_MSG; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_CD)) << PIN_CD; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_IO)) << PIN_IO; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_REQ)) << PIN_REQ; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_ACT)) << PIN_ACT; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_DP)) << PIN_DP; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_DT0)) << PIN_DT0; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_DT1)) << PIN_DT1; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_DT2)) << PIN_DT2; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_DT3)) << PIN_DT3; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_DT4)) << PIN_DT4; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_DT5)) << PIN_DT5; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_DT6)) << PIN_DT6; - rpi_data |= ((uint32_t)GetSignal(BPI_PIN_DT7)) << PIN_DT7; - - return rpi_data; -} \ No newline at end of file diff --git a/cpp/hal/data_sample_bananam2p.h b/cpp/hal/data_sample_bananam2p.h deleted file mode 100644 index 35f04ee6..00000000 --- a/cpp/hal/data_sample_bananam2p.h +++ /dev/null @@ -1,94 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Copyright (C) 2022 akuker -// -// [ Logical representation of a single data sample ] -// -//--------------------------------------------------------------------------- - -#pragma once - -#include "hal/data_sample.h" -#include "shared/scsi.h" -#include - -#if defined CONNECT_TYPE_STANDARD -#include "hal/connection_type/connection_standard.h" -#elif defined CONNECT_TYPE_FULLSPEC -#include "hal/connection_type/connection_fullspec.h" -#elif defined CONNECT_TYPE_AIBOM -#include "hal/connection_type/connection_aibom.h" -#elif defined CONNECT_TYPE_GAMERNIUM -#include "hal/connection_type/connection_gamernium.h" -#else -#error Invalid connection type or none specified -#endif - -class DataSample_BananaM2p final : public DataSample -{ - public: - bool GetSignal(int pin) const override; - - bool GetBSY() const override - { - return GetSignal(BPI_PIN_BSY); - } - bool GetSEL() const override - { - return GetSignal(BPI_PIN_SEL); - } - bool GetATN() const override - { - return GetSignal(BPI_PIN_ATN); - } - bool GetACK() const override - { - return GetSignal(BPI_PIN_ACK); - } - bool GetRST() const override - { - return GetSignal(BPI_PIN_RST); - } - bool GetMSG() const override - { - return GetSignal(BPI_PIN_MSG); - } - bool GetCD() const override - { - return GetSignal(BPI_PIN_CD); - } - bool GetIO() const override - { - return GetSignal(BPI_PIN_IO); - } - bool GetREQ() const override - { - return GetSignal(BPI_PIN_REQ); - } - bool GetACT() const override - { - return GetSignal(BPI_PIN_ACT); - } - bool GetDP() const override - { - return GetSignal(BPI_PIN_DP); - } - - uint8_t GetDAT() const override; - - uint32_t GetRawCapture() const override; - - DataSample_BananaM2p(const array &in_data, uint64_t in_timestamp) - : DataSample{in_timestamp}, data{in_data} - { - } - DataSample_BananaM2p() = default; - - ~DataSample_BananaM2p() override = default; - - private: - array data = {0}; -}; \ No newline at end of file diff --git a/cpp/hal/gpiobus.cpp b/cpp/hal/gpiobus.cpp index e028883c..837fc6d7 100644 --- a/cpp/hal/gpiobus.cpp +++ b/cpp/hal/gpiobus.cpp @@ -6,15 +6,12 @@ // Powered by XM6 TypeG Technology. // Copyright (C) 2016-2020 GIMONS // -// [ GPIO-SCSI bus ] -// //--------------------------------------------------------------------------- #include "hal/gpiobus.h" #include "hal/sbc_version.h" #include "hal/systimer.h" -#include "shared/log.h" -#include +#include #include #include #include @@ -41,11 +38,10 @@ bool GPIOBUS::Init(mode_e mode) //--------------------------------------------------------------------------- int GPIOBUS::CommandHandShake(vector &buf) { - GPIO_FUNCTION_TRACE // Only works in TARGET mode - if (actmode != mode_e::TARGET) { - return 0; - } + assert(actmode == mode_e::TARGET); + + GPIO_FUNCTION_TRACE DisableIRQ(); @@ -280,8 +276,8 @@ int GPIOBUS::SendHandShake(uint8_t *buf, int count, int delay_after_bytes) if (actmode == mode_e::TARGET) { for (i = 0; i < count; i++) { if (i == delay_after_bytes) { - LOGTRACE("%s DELAYING for %dus after %d bytes", __PRETTY_FUNCTION__, SCSI_DELAY_SEND_DATA_DAYNAPORT_US, - (int)delay_after_bytes) + spdlog::trace("DELAYING for " + to_string(SCSI_DELAY_SEND_DATA_DAYNAPORT_US) + " us after " + + to_string(delay_after_bytes) + " bytes"); SysTimer::SleepUsec(SCSI_DELAY_SEND_DATA_DAYNAPORT_US); } @@ -324,12 +320,6 @@ int GPIOBUS::SendHandShake(uint8_t *buf, int count, int delay_after_bytes) phase_t phase = GetPhase(); for (i = 0; i < count; i++) { - if (i == delay_after_bytes) { - LOGTRACE("%s DELAYING for %dus after %d bytes", __PRETTY_FUNCTION__, SCSI_DELAY_SEND_DATA_DAYNAPORT_US, - (int)delay_after_bytes) - SysTimer::SleepUsec(SCSI_DELAY_SEND_DATA_DAYNAPORT_US); - } - // Set the DATA signals SetDAT(*buf); @@ -341,6 +331,11 @@ int GPIOBUS::SendHandShake(uint8_t *buf, int count, int delay_after_bytes) break; } + // Signal the last MESSAGE OUT byte + if (phase == phase_t::msgout && i == count - 1) { + SetATN(false); + } + // Phase error Acquire(); if (GetPhase() != phase) { @@ -392,27 +387,18 @@ bool GPIOBUS::PollSelectEvent() return false; #else GPIO_FUNCTION_TRACE - LOGTRACE("%s", __PRETTY_FUNCTION__) - errno = 0; - int prev_mode = -1; - if (SBC_Version::IsBananaPi()) { - prev_mode = GetMode(BPI_PIN_SEL); - SetMode(BPI_PIN_SEL, GPIO_IRQ_IN); - } + errno = 0; if (epoll_event epev; epoll_wait(epfd, &epev, 1, -1) <= 0) { - LOGWARN("%s epoll_wait failed", __PRETTY_FUNCTION__) + spdlog::warn("epoll_wait failed"); return false; } if (gpioevent_data gpev; read(selevreq.fd, &gpev, sizeof(gpev)) < 0) { - LOGWARN("%s read failed", __PRETTY_FUNCTION__) + spdlog::warn("read failed"); return false; } - if (SBC_Version::IsBananaPi()) { - SetMode(BPI_PIN_SEL, prev_mode); - } return true; #endif } @@ -435,10 +421,10 @@ void GPIOBUS::ClearSelectEvent() bool GPIOBUS::WaitSignal(int pin, bool ast) { // Get current time - uint32_t now = SysTimer::GetTimerLow(); + const uint32_t now = SysTimer::GetTimerLow(); // Calculate timeout (3000ms) - uint32_t timeout = 3000 * 1000; + const uint32_t timeout = 3000 * 1000; do { // Immediately upon receiving a reset @@ -455,4 +441,4 @@ bool GPIOBUS::WaitSignal(int pin, bool ast) // We timed out waiting for the signal return false; -} \ No newline at end of file +} diff --git a/cpp/hal/gpiobus.h b/cpp/hal/gpiobus.h index f7f927b4..fe774115 100644 --- a/cpp/hal/gpiobus.h +++ b/cpp/hal/gpiobus.h @@ -5,7 +5,6 @@ // // Powered by XM6 TypeG Technology. // Copyright (C) 2016-2020 GIMONS -// [ GPIO-SCSI bus ] // //--------------------------------------------------------------------------- @@ -13,7 +12,6 @@ #include "hal/bus.h" #include "shared/scsi.h" -#include #include #include @@ -176,11 +174,11 @@ class GPIOBUS : public BUS bool Init(mode_e mode = mode_e::TARGET) override; // Command receive handshake - int CommandHandShake(vector &) override; + int CommandHandShake(vector&) override; // Data receive handshake - int ReceiveHandShake(uint8_t *buf, int count) override; + int ReceiveHandShake(uint8_t *, int) override; // Data transmission handshake - int SendHandShake(uint8_t *buf, int count, int delay_after_bytes) override; + int SendHandShake(uint8_t *, int, int) override; // SEL signal event polling bool PollSelectEvent() override; @@ -208,13 +206,13 @@ class GPIOBUS : public BUS virtual void DrvConfig(uint32_t drive) = 0; // Operation mode - mode_e actmode = mode_e::TARGET; // NOSONAR: This protected so derived classes can access it + mode_e actmode = mode_e::TARGET; #ifdef __linux__ // SEL signal event request - struct gpioevent_request selevreq = {}; // NOSONAR: This protected so derived classes can access it + struct gpioevent_request selevreq = {}; // epoll file descriptor - int epfd; // NOSONAR: This protected so derived classes can access it + int epfd = 0; #endif }; diff --git a/cpp/hal/gpiobus_bananam2p.cpp b/cpp/hal/gpiobus_bananam2p.cpp deleted file mode 100644 index a1e1a332..00000000 --- a/cpp/hal/gpiobus_bananam2p.cpp +++ /dev/null @@ -1,1053 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi (And Banana Pi) -// -// Copyright (c) 2012-2015 Ben Croston -// Copyright (C) 2022 akuker -// -// Large portions of this functionality were derived from c_gpio.c, which -// is part of the RPI.GPIO library available here: -// https://github.com/BPI-SINOVOIP/RPi.GPIO/blob/master/source/c_gpio.c -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -// of the Software, and to permit persons to whom the Software is furnished to do -// so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// -//--------------------------------------------------------------------------- - -#include -#include -#include -#include -#include -#include -#include - -#include "hal/gpiobus.h" -#include "hal/gpiobus_bananam2p.h" -#include "hal/pi_defs/bpi-gpio.h" -#include "hal/sunxi_utils.h" -#include "hal/systimer.h" -#include "shared/log.h" - -#define ARRAY_SIZE(x) (sizeof(x) / (sizeof(x[0]))) - -bool GPIOBUS_BananaM2p::Init(mode_e mode) -{ - GPIO_FUNCTION_TRACE - GPIOBUS::Init(mode); - SysTimer::Init(); - - sbc_version = SBC_Version::GetSbcVersion(); - - for (auto const gpio_num : SignalTable) { - if (gpio_num == -1) { - break; - } - - int gpio_bank = SunXI::GPIO_BANK(gpio_num); - - if (std::find(gpio_banks.begin(), gpio_banks.end(), gpio_bank) != gpio_banks.end()) { - LOGTRACE("Duplicate bank: %d", gpio_bank) - - } else { - LOGDEBUG("New bank: %d", gpio_bank) - gpio_banks.push_back(gpio_bank); - } - } - - if (int result = sunxi_setup(); result != SETUP_OK) { - return false; - } - - InitializeGpio(); - - MakeTable(); - - // SetupSelEvent needs to be called AFTER Initialize GPIO. This function - // reconfigures the SEL signal. - if (!SetupSelEvent()) { - LOGERROR("Failed to setup SELECT poll event") - return false; - } - LOGTRACE("SetupSelEvent OK!") - - // Set drive strength to maximum - DrvConfig(3); - - return true; -} - -void GPIOBUS_BananaM2p::InitializeGpio() -{ - GPIO_FUNCTION_TRACE - - // Set pull up/pull down -#if SIGNAL_CONTROL_MODE == 0 - int pullmode = GPIO_PULLNONE; -#elif SIGNAL_CONTROL_MODE == 1 - int pullmode = GPIO_PULLUP; -#else - int pullmode = GPIO_PULLDOWN; -#endif - - // Initialize all signals - for (int i = 0; SignalTable[i] >= 0; i++) { - int j = SignalTable[i]; - PinConfig(j, GPIO_INPUT); - PullConfig(j, pullmode); - PinSetSignal(j, OFF); - } - - // Set control signals - PinConfig(BPI_PIN_ACT, GPIO_OUTPUT); - PinConfig(BPI_PIN_TAD, GPIO_OUTPUT); - PinConfig(BPI_PIN_IND, GPIO_OUTPUT); - PinConfig(BPI_PIN_DTD, GPIO_OUTPUT); - PinSetSignal(BPI_PIN_ACT, OFF); - PinSetSignal(BPI_PIN_TAD, OFF); - PinSetSignal(BPI_PIN_IND, OFF); - PinSetSignal(BPI_PIN_DTD, OFF); - - // Set the ENABLE signal - // This is used to show that the application is running - PinConfig(BPI_PIN_ENB, GPIO_OUTPUT); - PinSetSignal(BPI_PIN_ENB, ON); -} - -void GPIOBUS_BananaM2p::Cleanup() -{ - GPIO_FUNCTION_TRACE -#if defined(__x86_64__) || defined(__X86__) || defined(__aarch64__) - dummy_var--; // Need to do something to prevent Sonar from claiming this should be a const function - return; -#else - -#ifdef USE_SEL_EVENT_ENABLE - // Release SEL signal interrupt - close(selevreq.fd); -#endif // USE_SEL_EVENT_ENABLE - - // Set control signals - PinConfig(BPI_PIN_ACT, GPIO_INPUT); - PinConfig(BPI_PIN_TAD, GPIO_INPUT); - PinConfig(BPI_PIN_IND, GPIO_INPUT); - PinConfig(BPI_PIN_DTD, GPIO_INPUT); - PinSetSignal(BPI_PIN_ENB, OFF); - PinSetSignal(BPI_PIN_ACT, OFF); - PinSetSignal(BPI_PIN_TAD, OFF); - PinSetSignal(BPI_PIN_IND, OFF); - PinSetSignal(BPI_PIN_DTD, OFF); - - // Initialize all signals - for (int i = 0; SignalTable[i] >= 0; i++) { - int pin = SignalTable[i]; - PinConfig(pin, GPIO_INPUT); - PullConfig(pin, GPIO_PULLNONE); - PinSetSignal(pin, OFF); - } - - // Set drive strength back to Default (Level 1) - DrvConfig(1); - - munmap((void *)gpio_map, SunXI::BLOCK_SIZE); - munmap((void *)r_gpio_map, SunXI::BLOCK_SIZE); - -#endif -} - -void GPIOBUS_BananaM2p::Reset() -{ -#if defined(__x86_64__) || defined(__X86__) || defined(__aarch64__) - dummy_var++; - return; -#else - int i; - int j; - - // Turn off active signal - SetControl(BPI_PIN_ACT, ACT_OFF); - - // Set all signals to off - for (i = 0;; i++) { - j = SignalTable[i]; - if (j < 0) { - break; - } - - SetSignal(j, OFF); - } - - if (actmode == mode_e::TARGET) { - // Target mode - - // Set target signal to input - SetControl(BPI_PIN_TAD, TAD_IN); - SetMode(BPI_PIN_BSY, IN); - SetMode(BPI_PIN_MSG, IN); - SetMode(BPI_PIN_CD, IN); - SetMode(BPI_PIN_REQ, IN); - SetMode(BPI_PIN_IO, IN); - - // Set the initiator signal to input - SetControl(BPI_PIN_IND, IND_IN); - SetMode(BPI_PIN_SEL, IN); - SetMode(BPI_PIN_ATN, IN); - SetMode(BPI_PIN_ACK, IN); - SetMode(BPI_PIN_RST, IN); - - // Set data bus signals to input - SetControl(BPI_PIN_DTD, DTD_IN); - SetMode(BPI_PIN_DT0, IN); - SetMode(BPI_PIN_DT1, IN); - SetMode(BPI_PIN_DT2, IN); - SetMode(BPI_PIN_DT3, IN); - SetMode(BPI_PIN_DT4, IN); - SetMode(BPI_PIN_DT5, IN); - SetMode(BPI_PIN_DT6, IN); - SetMode(BPI_PIN_DT7, IN); - SetMode(BPI_PIN_DP, IN); - } else { - // Initiator mode - - // Set target signal to input - SetControl(BPI_PIN_TAD, TAD_IN); - SetMode(BPI_PIN_BSY, IN); - SetMode(BPI_PIN_MSG, IN); - SetMode(BPI_PIN_CD, IN); - SetMode(BPI_PIN_REQ, IN); - SetMode(BPI_PIN_IO, IN); - - // Set the initiator signal to output - SetControl(BPI_PIN_IND, IND_OUT); - SetMode(BPI_PIN_SEL, OUT); - SetMode(BPI_PIN_ATN, OUT); - SetMode(BPI_PIN_ACK, OUT); - SetMode(BPI_PIN_RST, OUT); - - // Set the data bus signals to output - SetControl(BPI_PIN_DTD, DTD_OUT); - SetMode(BPI_PIN_DT0, OUT); - SetMode(BPI_PIN_DT1, OUT); - SetMode(BPI_PIN_DT2, OUT); - SetMode(BPI_PIN_DT3, OUT); - SetMode(BPI_PIN_DT4, OUT); - SetMode(BPI_PIN_DT5, OUT); - SetMode(BPI_PIN_DT6, OUT); - SetMode(BPI_PIN_DT7, OUT); - SetMode(BPI_PIN_DP, OUT); - } - - // Initialize all signals - // TODO!! For now, just re-run Acquire - Acquire(); -#endif // ifdef __x86_64__ || __X86__ -} - -bool GPIOBUS_BananaM2p::SetupSelEvent() -{ -#if defined(__x86_64__) || defined(__X86__) || defined(__aarch64__) - dummy_var += 2; // Need to do something to prevent Sonar from claiming this should be a const function - return false; -#else - GPIO_FUNCTION_TRACE - int gpio_pin = BPI_PIN_SEL; - - // GPIO chip open - LOGTRACE("%s GPIO chip open [%d]", __PRETTY_FUNCTION__, gpio_pin) - std::string gpio_dev = "/dev/gpiochip0"; - if (SunXI::GPIO_BANK(gpio_pin) >= 11) { - gpio_dev = "/dev/gpiochip1"; - LOGWARN("gpiochip1 support isn't implemented yet....") - LOGWARN("THIS PROBABLY WONT WORK!") - } - - int gpio_fd = open(gpio_dev.c_str(), 0); - if (gpio_fd == -1) { - LOGERROR("Unable to open /dev/gpiochip0. Is PiSCSI or RaSCSI already running?") - return false; - } - - // Event request setting - LOGTRACE("%s Event request setting (pin sel: %d)", __PRETTY_FUNCTION__, gpio_pin) - strncpy(selevreq.consumer_label, "PiSCSI", ARRAY_SIZE(selevreq.consumer_label)); - selevreq.lineoffset = gpio_pin; - selevreq.handleflags = GPIOHANDLE_REQUEST_INPUT; -#if SIGNAL_CONTROL_MODE < 2 - selevreq.eventflags = GPIOEVENT_REQUEST_FALLING_EDGE; - LOGTRACE("%s eventflags = GPIOEVENT_REQUEST_FALLING_EDGE", __PRETTY_FUNCTION__) -#else - selevreq.eventflags = GPIOEVENT_REQUEST_RISING_EDGE; - LOGTRACE("%s eventflags = GPIOEVENT_REQUEST_RISING_EDGE", __PRETTY_FUNCTION__) -#endif // SIGNAL_CONTROL_MODE - - // Get event request - if (ioctl(gpio_fd, GPIO_GET_LINEEVENT_IOCTL, &selevreq) == -1) { - LOGERROR("selevreq.fd = %d %08X", selevreq.fd, (unsigned int)selevreq.fd) - LOGERROR("Unable to register event request. Is PiSCSI or RaSCSI already running?") - LOGERROR("[%08X] %s", errno, strerror(errno)) - close(gpio_fd); - return false; - } - - // Close GPIO chip file handle - LOGTRACE("%s Close GPIO chip file handle", __PRETTY_FUNCTION__) - close(gpio_fd); - - // epoll initialization - LOGTRACE("%s epoll initialization", __PRETTY_FUNCTION__) - epfd = epoll_create(1); - if (epfd == -1) { - LOGERROR("Unable to create the epoll event") - return false; - } - epoll_event ev = {}; - memset(&ev, 0, sizeof(ev)); - ev.events = EPOLLIN | EPOLLPRI; - ev.data.fd = selevreq.fd; - if (epoll_ctl(epfd, EPOLL_CTL_ADD, selevreq.fd, &ev) < 0) { - return false; - } - - return true; -#endif -} - -void GPIOBUS_BananaM2p::SetENB(bool ast) -{ - PinSetSignal(BPI_PIN_ENB, ast ? ENB_ON : ENB_OFF); -} - -bool GPIOBUS_BananaM2p::GetBSY() const -{ - return GetSignal(BPI_PIN_BSY); -} - -void GPIOBUS_BananaM2p::SetBSY(bool ast) -{ - if (actmode == mode_e::TARGET) { - if (ast) { - // Turn on ACTIVE signal - SetControl(BPI_PIN_ACT, ACT_ON); - - // Set Target signal to output - SetControl(BPI_PIN_TAD, TAD_OUT); - - SetMode(BPI_PIN_BSY, OUT); - SetMode(BPI_PIN_MSG, OUT); - SetMode(BPI_PIN_CD, OUT); - SetMode(BPI_PIN_REQ, OUT); - SetMode(BPI_PIN_IO, OUT); - - // Set BSY signal - SetSignal(BPI_PIN_BSY, ast); - - } else { - // Turn off the ACTIVE signal - SetControl(BPI_PIN_ACT, ACT_OFF); - - // Set the target signal to input - SetControl(BPI_PIN_TAD, TAD_IN); - - SetMode(BPI_PIN_BSY, IN); - SetMode(BPI_PIN_MSG, IN); - SetMode(BPI_PIN_CD, IN); - SetMode(BPI_PIN_REQ, IN); - SetMode(BPI_PIN_IO, IN); - } - } else { - // Set BSY signal - SetSignal(BPI_PIN_BSY, ast); - } -} - -bool GPIOBUS_BananaM2p::GetSEL() const -{ - return GetSignal(BPI_PIN_SEL); -} - -void GPIOBUS_BananaM2p::SetSEL(bool ast) -{ - if (actmode == mode_e::INITIATOR && ast) { - // Turn on ACTIVE signal - SetControl(BPI_PIN_ACT, ACT_ON); - } - - // Set SEL signal - SetSignal(BPI_PIN_SEL, ast); -} - -bool GPIOBUS_BananaM2p::GetATN() const -{ - return GetSignal(BPI_PIN_ATN); -} - -void GPIOBUS_BananaM2p::SetATN(bool ast) -{ - SetSignal(BPI_PIN_ATN, ast); -} - -bool GPIOBUS_BananaM2p::GetACK() const -{ - return GetSignal(BPI_PIN_ACK); -} - -void GPIOBUS_BananaM2p::SetACK(bool ast) -{ - SetSignal(BPI_PIN_ACK, ast); -} - -bool GPIOBUS_BananaM2p::GetACT() const -{ - return GetSignal(BPI_PIN_ACT); -} - -void GPIOBUS_BananaM2p::SetACT(bool ast) -{ - SetSignal(BPI_PIN_ACT, ast); -} - -bool GPIOBUS_BananaM2p::GetRST() const -{ - return GetSignal(BPI_PIN_RST); -} - -void GPIOBUS_BananaM2p::SetRST(bool ast) -{ - SetSignal(BPI_PIN_RST, ast); -} - -bool GPIOBUS_BananaM2p::GetMSG() const -{ - return GetSignal(BPI_PIN_MSG); -} - -void GPIOBUS_BananaM2p::SetMSG(bool ast) -{ - SetSignal(BPI_PIN_MSG, ast); -} - -bool GPIOBUS_BananaM2p::GetCD() const -{ - return GetSignal(BPI_PIN_CD); -} - -void GPIOBUS_BananaM2p::SetCD(bool ast) -{ - SetSignal(BPI_PIN_CD, ast); -} - -bool GPIOBUS_BananaM2p::GetIO() -{ - bool ast = GetSignal(BPI_PIN_IO); - - if (actmode == mode_e::INITIATOR) { - // Change the data input/output direction by IO signal - if (ast) { - SetControl(BPI_PIN_DTD, DTD_IN); - SetMode(BPI_PIN_DT0, IN); - SetMode(BPI_PIN_DT1, IN); - SetMode(BPI_PIN_DT2, IN); - SetMode(BPI_PIN_DT3, IN); - SetMode(BPI_PIN_DT4, IN); - SetMode(BPI_PIN_DT5, IN); - SetMode(BPI_PIN_DT6, IN); - SetMode(BPI_PIN_DT7, IN); - SetMode(BPI_PIN_DP, IN); - } else { - SetControl(BPI_PIN_DTD, DTD_OUT); - SetMode(BPI_PIN_DT0, OUT); - SetMode(BPI_PIN_DT1, OUT); - SetMode(BPI_PIN_DT2, OUT); - SetMode(BPI_PIN_DT3, OUT); - SetMode(BPI_PIN_DT4, OUT); - SetMode(BPI_PIN_DT5, OUT); - SetMode(BPI_PIN_DT6, OUT); - SetMode(BPI_PIN_DT7, OUT); - SetMode(BPI_PIN_DP, OUT); - } - } - - return ast; -} - -void GPIOBUS_BananaM2p::SetIO(bool ast) -{ - if (actmode == mode_e::TARGET) { - // Change the data input/output direction by IO signal - if (ast) { - SetControl(BPI_PIN_DTD, DTD_OUT); - SetMode(BPI_PIN_DT0, OUT); - SetMode(BPI_PIN_DT1, OUT); - SetMode(BPI_PIN_DT2, OUT); - SetMode(BPI_PIN_DT3, OUT); - SetMode(BPI_PIN_DT4, OUT); - SetMode(BPI_PIN_DT5, OUT); - SetMode(BPI_PIN_DT6, OUT); - SetMode(BPI_PIN_DT7, OUT); - SetMode(BPI_PIN_DP, OUT); - - SetDAT(0); - SetSignal(BPI_PIN_IO, ast); - - } else { - SetControl(BPI_PIN_DTD, DTD_IN); - SetMode(BPI_PIN_DT0, IN); - SetMode(BPI_PIN_DT1, IN); - SetMode(BPI_PIN_DT2, IN); - SetMode(BPI_PIN_DT3, IN); - SetMode(BPI_PIN_DT4, IN); - SetMode(BPI_PIN_DT5, IN); - SetMode(BPI_PIN_DT6, IN); - SetMode(BPI_PIN_DT7, IN); - SetMode(BPI_PIN_DP, IN); - } - } else { - SetSignal(BPI_PIN_IO, ast); - } -} - -bool GPIOBUS_BananaM2p::GetREQ() const -{ - return GetSignal(BPI_PIN_REQ); -} - -void GPIOBUS_BananaM2p::SetREQ(bool ast) -{ - SetSignal(BPI_PIN_REQ, ast); -} - -uint8_t GPIOBUS_BananaM2p::GetDAT() -{ - GPIO_FUNCTION_TRACE - - Acquire(); - uint32_t data = - ((GetSignal(BPI_PIN_DT0) ? 0x01 : 0x00) << 0) | ((GetSignal(BPI_PIN_DT1) ? 0x01 : 0x00) << 1) | - ((GetSignal(BPI_PIN_DT2) ? 0x01 : 0x00) << 2) | ((GetSignal(BPI_PIN_DT3) ? 0x01 : 0x00) << 3) | - ((GetSignal(BPI_PIN_DT4) ? 0x01 : 0x00) << 4) | ((GetSignal(BPI_PIN_DT5) ? 0x01 : 0x00) << 5) | - ((GetSignal(BPI_PIN_DT6) ? 0x01 : 0x00) << 6) | - ((GetSignal(BPI_PIN_DT7) ? 0x01 : 0x00) << 7); // NOSONAR: GCC 10 doesn't support shift operations on std::byte - - return (uint8_t)(data & 0xFF); -} - -void GPIOBUS_BananaM2p::SetDAT(uint8_t dat) -{ - GPIO_FUNCTION_TRACE - - array gpio_reg_values = {0}; - - for (size_t gpio_num = 0; gpio_num < pintbl.size(); gpio_num++) { - bool value; - if (gpio_num < 8) { - // data bits - value = !(dat & (1 << gpio_num)); // NOSONAR: GCC 10 doesn't support shift operations on std::byte - } else { - // parity bit - value = (__builtin_parity(dat) == 1); - } - - if (value) { - uint32_t this_gpio = pintbl[gpio_num]; - int bank = SunXI::GPIO_BANK(this_gpio); - int offset = SunXI::GPIO_NUM(this_gpio); - gpio_reg_values[bank] |= (1 << offset); - } - } - - sunxi_set_all_gpios(gpio_data_masks, gpio_reg_values); -} - -//--------------------------------------------------------------------------- -// -// Signal table -// -//--------------------------------------------------------------------------- -const array GPIOBUS_BananaM2p::SignalTable = {BPI_PIN_DT0, BPI_PIN_DT1, BPI_PIN_DT2, BPI_PIN_DT3, BPI_PIN_DT4, - BPI_PIN_DT5, BPI_PIN_DT6, BPI_PIN_DT7, BPI_PIN_DP, BPI_PIN_SEL, - BPI_PIN_ATN, BPI_PIN_RST, BPI_PIN_ACK, BPI_PIN_BSY, BPI_PIN_MSG, - BPI_PIN_CD, BPI_PIN_IO, BPI_PIN_REQ, -1}; - -//--------------------------------------------------------------------------- -// -// Create work table -// -//--------------------------------------------------------------------------- -void GPIOBUS_BananaM2p::MakeTable(void) -{ - for (auto this_gpio : pintbl) { - int bank = SunXI::GPIO_BANK(this_gpio); - int offset = (SunXI::GPIO_NUM(this_gpio)); - - gpio_data_masks[bank] |= (1 << offset); - } -} - -bool GPIOBUS_BananaM2p::GetDP() const -{ - return GetSignal(BPI_PIN_DP); -} - -void GPIOBUS_BananaM2p::SetControl(int pin, bool ast) -{ - GPIO_FUNCTION_TRACE - PinSetSignal(pin, ast); -} - -// Set direction -int GPIOBUS_BananaM2p::GetMode(int pin) -{ - GPIO_FUNCTION_TRACE - - uint32_t regval = 0; - int bank = SunXI::GPIO_BANK(pin); - int index = SunXI::GPIO_CFG_INDEX(pin); - int offset = SunXI::GPIO_CFG_OFFSET(pin); - - volatile SunXI::sunxi_gpio_t *pio = &(pio_map->gpio_bank[bank]); - /* DK, for PL and PM */ - if (bank >= 11) { - bank -= 11; - pio = &(r_pio_map->gpio_bank[bank]); - } - - regval = pio->CFG[0 + index]; - - // Extract the CFG field - regval &= (0x7 << offset); // 0xf? - // Shift it down to the LSB - regval >>= offset; - return (int)regval; -} - -// Set direction -void GPIOBUS_BananaM2p::SetMode(int pin, int mode) -{ - GPIO_FUNCTION_TRACE - int direction = mode; - - uint32_t regval = 0; - int bank = SunXI::GPIO_BANK(pin); // gpio >> 5 - int index = SunXI::GPIO_CFG_INDEX(pin); // (gpio & 0x1F) >> 3 - int offset = SunXI::GPIO_CFG_OFFSET(pin); // ((gpio & 0x1F) & 0x7) << 2 - LOGTRACE("%s gpio(%d) bank(%d) index(%d) offset(%d) dir(%s)", __PRETTY_FUNCTION__, pin, bank, index, offset, - (GPIO_INPUT == direction) ? "IN" - : (GPIO_IRQ_IN == direction) ? "IRQ" - : "OUT") - - volatile SunXI::sunxi_gpio_t *pio = &(pio_map->gpio_bank[bank]); - /* DK, for PL and PM */ - if (bank >= 11) { - bank -= 11; - pio = &(r_pio_map->gpio_bank[bank]); - } - - regval = pio->CFG[0 + index]; - - // Clear the cfg field - regval &= ~(0x7 << offset); // 0xf? - if (GPIO_INPUT == direction) { - regval |= (((uint32_t)SunXI::gpio_configure_values_e::gpio_input) << offset); - } else if (GPIO_OUTPUT == direction) { - regval |= (((uint32_t)SunXI::gpio_configure_values_e::gpio_output) << offset); - } else if (GPIO_IRQ_IN == direction) { - regval |= (((uint32_t)SunXI::gpio_configure_values_e::gpio_interupt) << offset); - } else { - LOGERROR("line:%d gpio number error %d", __LINE__, pin) - } - pio->CFG[0 + index] = regval; -} - -bool GPIOBUS_BananaM2p::GetSignal(int pin) const -{ - GPIO_FUNCTION_TRACE - int gpio_num = pin; - - uint32_t regval = 0; - int bank = SunXI::GPIO_BANK(gpio_num); // gpio >> 5 - int num = SunXI::GPIO_NUM(gpio_num); // gpio & 0x1F - - regval = (signals[bank] >> num) & 0x1; - return regval != 0; -} - -void GPIOBUS_BananaM2p::SetSignal(int pin, bool ast) -{ - GPIO_FUNCTION_TRACE - int gpio_num = pin; - -#if SIGNAL_CONTROL_MODE == 0 - // True : 0V - // False : Open collector output (disconnect from bus) - int sunxi_gpio_state = (ast == true) ? SunXI::HIGH : SunXI::LOW; -#elif SIGNAL_CONTROL_MODE == 1 - // True : 0V -> (CONVERT) -> 0V - // False : 3.3V -> (CONVERT) -> Open collector output - LOGWARN("%s:%d THIS LOGIC NEEDS TO BE VALIDATED/TESTED", __PRETTY_FUNCTION__, __LINE__) - int sunxi_gpio_state = (ast == true) ? SunXI::HIGH : SunXI::LOW; -#elif SIGNAL_CONTROL_MODE == 2 - // True : 3.3V -> (CONVERT) -> 0V - // False : 0V -> (CONVERT) -> Open collector output - LOGWARN("%s:%d THIS LOGIC NEEDS TO BE VALIDATED/TESTED", __PRETTY_FUNCTION__, __LINE__) - int sunxi_gpio_state = (ast == true) ? SunXI::LOW : SunXI::HIGH; -#endif // SIGNAL_CONTROL_MODE - - LOGTRACE("gpio(%d) sunxi_state(%d)", gpio_num, sunxi_gpio_state) - - int bank = SunXI::GPIO_BANK(gpio_num); // gpio >> 5 - int num = SunXI::GPIO_NUM(gpio_num); // gpio & 0x1F - - volatile SunXI::sunxi_gpio_t *pio = &(pio_map->gpio_bank[bank]); - - /* DK, for PL and PM */ - if (bank >= 11) { - LOGTRACE("bank > 11") - bank -= 11; - pio = &(r_pio_map->gpio_bank[bank]); - } - - if (sunxi_gpio_state == SunXI::HIGH) - pio->DAT &= ~(1 << num); - else - pio->DAT |= (1 << num); -} - -void GPIOBUS_BananaM2p::DisableIRQ() -{ - *tmr_ctrl = 0b00; -} - -void GPIOBUS_BananaM2p::EnableIRQ() -{ - *tmr_ctrl = 0b11; -} - -void GPIOBUS_BananaM2p::PinConfig(int pin, int mode) -{ - GPIO_FUNCTION_TRACE - int gpio_num = pin; - int sunxi_direction = (mode == GPIO_INPUT) ? SunXI::INPUT : SunXI::OUTPUT; - - sunxi_setup_gpio(gpio_num, sunxi_direction, -1); -} - -void GPIOBUS_BananaM2p::PullConfig(int pin, int mode) -{ - GPIO_FUNCTION_TRACE -#if defined(__x86_64__) || defined(__X86__) || defined(__aarch64__) - dummy_var++; // Need to do something to prevent Sonar from claiming this should be a const function - (void)pin; - (void)mode; - return; -#else - - // Note: this will throw an exception if an invalid pin is specified - int gpio_num = pin; - int pull_up_down_state = 0; - - switch (mode) { - case GPIO_PULLNONE: - pull_up_down_state = SunXI::PUD_OFF; - break; - case GPIO_PULLUP: - pull_up_down_state = SunXI::PUD_UP; - break; - case GPIO_PULLDOWN: - pull_up_down_state = SunXI::PUD_DOWN; - break; - default: - LOGERROR("%s INVALID PIN MODE", __PRETTY_FUNCTION__); - return; - } - - uint32_t regval = 0; - int bank = SunXI::GPIO_BANK(gpio_num); // gpio >> 5 - int index = SunXI::GPIO_PUL_INDEX(gpio_num); // (gpio & 0x1f) >> 4 - int offset = SunXI::GPIO_PUL_OFFSET(gpio_num); // (gpio) & 0x0F) << 1 - LOGTRACE("%s gpio(%d) bank(%d) index(%d) offset(%d)", __PRETTY_FUNCTION__, gpio_num, bank, index, offset) - - volatile SunXI::sunxi_gpio_t *pio = &(pio_map->gpio_bank[bank]); - /* DK, for PL and PM */ - if (bank >= 11) { - bank -= 11; - pio = &(r_pio_map->gpio_bank[bank]); - } - - regval = *(&pio->PULL[0] + index); - regval &= ~(3 << offset); - regval |= pull_up_down_state << offset; - pio->PULL[0 + index] = regval; -#endif -} - -void GPIOBUS_BananaM2p::PinSetSignal(int pin, bool ast) -{ - GPIO_FUNCTION_TRACE - int gpio_num = pin; - - int sunxi_gpio_state = (ast == true) ? SunXI::HIGH : SunXI::LOW; - sunxi_output_gpio(gpio_num, sunxi_gpio_state); -} - -void GPIOBUS_BananaM2p::DrvConfig(uint32_t drive) -{ - GPIO_FUNCTION_TRACE - - for (auto pin : SignalTable) { - if (pin == -1) { - continue; - } - - LOGTRACE("Configuring GPIO %d to drive strength %d", pin, drive) - - uint32_t regval = 0; - int bank = SunXI::GPIO_BANK(pin); // gpio >> 5 - int index = SunXI::GPIO_DRV_INDEX(pin); // (gpio & 0x1F) >> 3 - int offset = SunXI::GPIO_DRV_OFFSET(pin); // ((gpio & 0x1F) & 0x7) << 2 - LOGTRACE("%s gpio(%d) bank(%d) index(%d) offset(%d)", __PRETTY_FUNCTION__, pin, bank, index, offset) - - volatile SunXI::sunxi_gpio_t *pio = &(pio_map->gpio_bank[bank]); - /* DK, for PL and PM */ - if (bank >= 11) { - bank -= 11; - pio = &(r_pio_map->gpio_bank[bank]); - } - - // Get current register value - regval = pio->DRV[0 + index]; - // Clear the DRV value for that gpio - regval &= ~(0x7 << offset); // 0xf? - // Set the new DRV strength - regval |= (drive & 0b11) << offset; - // Save back to the register - pio->DRV[0 + index] = regval; - } - - // #endif // if __arm__ -} - -uint32_t GPIOBUS_BananaM2p::Acquire() -{ - GPIO_FUNCTION_TRACE - - for (auto bank : gpio_banks) { - volatile SunXI::sunxi_gpio_t *pio = &(pio_map->gpio_bank[bank]); - /* DK, for PL and PM */ - if (bank >= 11) { - pio = &(r_pio_map->gpio_bank[bank - 11]); - } - - uint32_t regval = pio->DAT; - -#if SIGNAL_CONTROL_MODE < 2 - // Invert if negative logic (internal processing is unified to positive logic) - regval = ~regval; -#endif // SIGNAL_CONTROL_MODE - signals[bank] = regval; - } - // TODO: This should do something someday.... - return 0; -} - -int GPIOBUS_BananaM2p::sunxi_setup(void) -{ - GPIO_FUNCTION_TRACE -#if defined(__x86_64__) || defined(__X86__) || defined(__aarch64__) - dummy_var++; // Need to do something to prevent Sonar from claiming this should be a const function - return SunXI::SETUP_MMAP_FAIL; -#else - int mem_fd; - uint8_t *gpio_mem; - - // mmap the GPIO memory registers - if ((mem_fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) { - LOGERROR("Error: Unable to open /dev/mem. Are you running as root?") - LOGDEBUG("errno: [%08X] %s", errno, strerror(errno)); - return SunXI::SETUP_DEVMEM_FAIL; - } - - if ((gpio_mem = (uint8_t *)malloc(SunXI::BLOCK_SIZE + (SunXI::PAGE_SIZE - 1))) == NULL) { - LOGERROR("Error: Unable to allocate gpio memory. Are you running as root?") - LOGDEBUG("errno: [%08X] %s", errno, strerror(errno)); - return SunXI::SETUP_DEVMEM_FAIL; - } - - if ((uint32_t)gpio_mem % SunXI::PAGE_SIZE) - gpio_mem += SunXI::PAGE_SIZE - ((uint32_t)gpio_mem % SunXI::PAGE_SIZE); - - gpio_map = (uint32_t *)mmap((caddr_t)gpio_mem, SunXI::BLOCK_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, - mem_fd, SunXI::SUNXI_GPIO_BASE); - if ((void *)gpio_map == MAP_FAILED) { - LOGERROR("Error: Unable to map gpio memory. Are you running as root?") - LOGDEBUG("errno: [%08X] %s", errno, strerror(errno)); - return SunXI::SETUP_MMAP_FAIL; - } - pio_map = (volatile SunXI::sunxi_gpio_reg *)(gpio_map + (SunXI::SUNXI_GPIO_REG_OFFSET >> 2)); - LOGTRACE("gpio_mem[%p] gpio_map[%p] pio_map[%p]", gpio_mem, gpio_map, pio_map) - // R_PIO GPIO LMN - r_gpio_map = (uint32_t *)mmap((caddr_t)0, SunXI::BLOCK_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, - SunXI::SUNXI_R_GPIO_BASE); - if ((void *)r_gpio_map == MAP_FAILED) { - LOGERROR("Error: Unable to map r_gpio memory. Are you running as root?") - LOGDEBUG("errno: [%08X] %s", errno, strerror(errno)); - return SunXI::SETUP_MMAP_FAIL; - } - r_pio_map = (volatile SunXI::sunxi_gpio_reg *)(r_gpio_map + (SunXI::SUNXI_R_GPIO_REG_OFFSET >> 2)); - LOGTRACE("r_gpio_map[%p] r_pio_map[%p]", r_gpio_map, r_pio_map) - - tmr_ctrl = gpio_map + ((SunXI::TMR_REGISTER_BASE - SunXI::SUNXI_GPIO_BASE) >> 2); - // LOGINFO("tmr_ctrl offset: %08X value: %08X", (TMR_REGISTER_BASE - SUNXI_GPIO_BASE), *tmr_ctrl); - - close(mem_fd); - return SETUP_OK; -#endif -} - -void GPIOBUS_BananaM2p::sunxi_setup_gpio(int pin, int direction, int pud) -{ - GPIO_FUNCTION_TRACE -#if defined(__x86_64__) || defined(__X86__) || defined(__aarch64__) - dummy_var++; // Need to do something to prevent Sonar from claiming this should be a const function - (void)pin; - (void)direction; - (void)pud; - return; -#else - uint32_t regval = 0; - int bank = SunXI::GPIO_BANK(pin); // gpio >> 5 - int index = SunXI::GPIO_CFG_INDEX(pin); // (gpio & 0x1F) >> 3 - int offset = SunXI::GPIO_CFG_OFFSET(pin); // ((gpio & 0x1F) & 0x7) << 2 - LOGTRACE("%s gpio(%d) bank(%d) index(%d) offset(%d)", __PRETTY_FUNCTION__, pin, bank, index, offset) - - volatile SunXI::sunxi_gpio_t *pio = &(pio_map->gpio_bank[bank]); - /* DK, for PL and PM */ - if (bank >= 11) { - bank -= 11; - pio = &(r_pio_map->gpio_bank[bank]); - } - - if (pud != -1) { - set_pullupdn(pin, pud); - } - regval = *(&pio->CFG[0 + index]); - regval &= ~(0x7 << offset); // 0xf? - if (SunXI::INPUT == direction) { - pio->CFG[0 + index] = regval; - } else if (SunXI::OUTPUT == direction) { - regval |= (1 << offset); - pio->CFG[0 + index] = regval; - } else { - LOGERROR("line:%d gpio number error %d", __LINE__, pin) - } -#endif -} - -void GPIOBUS_BananaM2p::sunxi_output_gpio(int pin, int value) -{ - GPIO_FUNCTION_TRACE - if (pin < 0) { - LOGWARN("Invalid GPIO Num") - return; - } - int bank = SunXI::GPIO_BANK(pin); // gpio >> 5 - int num = SunXI::GPIO_NUM(pin); // gpio & 0x1F - - LOGTRACE("%s gpio(%d) bank(%d) num(%d) value(%d)", __PRETTY_FUNCTION__, pin, bank, num, value) - volatile SunXI::sunxi_gpio_t *pio = &(pio_map->gpio_bank[bank]); - - /* DK, for PL and PM */ - if (bank >= 11) { - bank -= 11; - pio = &(r_pio_map->gpio_bank[bank]); - } - - if (value == 0) - pio->DAT &= ~(1 << num); - else - pio->DAT |= (1 << num); -} - -void GPIOBUS_BananaM2p::sunxi_set_all_gpios(array &mask, array &value) -{ - GPIO_FUNCTION_TRACE - for (size_t bank = 0; bank < mask.size(); bank++) { - if (mask[bank] == 0) { - continue; - } - - volatile SunXI::sunxi_gpio_t *pio; - if (bank < 11) { - pio = &(pio_map->gpio_bank[bank]); - } else { - pio = &(r_pio_map->gpio_bank[bank - 11]); - } - - uint32_t reg_val = pio->DAT; - reg_val &= ~mask[bank]; - reg_val |= value[bank]; - pio->DAT = reg_val; - } -} - -int GPIOBUS_BananaM2p::sunxi_input_gpio(int pin) const -{ - GPIO_FUNCTION_TRACE - uint32_t regval = 0; - int bank = SunXI::GPIO_BANK(pin); // gpio >> 5 - int num = SunXI::GPIO_NUM(pin); // gpio & 0x1F - - LOGTRACE("%s gpio(%d) bank(%d) num(%d)", __PRETTY_FUNCTION__, pin, bank, num) - volatile SunXI::sunxi_gpio_t *pio = &(pio_map->gpio_bank[bank]); - /* DK, for PL and PM */ - if (bank >= 11) { - bank -= 11; - pio = &(r_pio_map->gpio_bank[bank]); - } - - regval = pio->DAT; - regval = regval >> num; - regval &= 1; - return regval; -} - -void GPIOBUS_BananaM2p::set_pullupdn(int pin, int pud) -{ - GPIO_FUNCTION_TRACE - int clk_offset = SunXI::PULLUPDNCLK_OFFSET + (pin / 32); - int shift = (pin % 32); - -#ifdef BPI - if (bpi_found == 1) { - gpio = *(pinTobcm_BP + pin); - return sunxi_set_pullupdn(pin, pud); - } -#endif - if (pud == SunXI::PUD_DOWN) - *(gpio_map + SunXI::PULLUPDN_OFFSET) = (*(gpio_map + SunXI::PULLUPDN_OFFSET) & ~3) | SunXI::PUD_DOWN; - else if (pud == SunXI::PUD_UP) - *(gpio_map + SunXI::PULLUPDN_OFFSET) = (*(gpio_map + SunXI::PULLUPDN_OFFSET) & ~3) | SunXI::PUD_UP; - else // pud == PUD_OFF - *(gpio_map + SunXI::PULLUPDN_OFFSET) &= ~3; - - SunXI::short_wait(); - *(gpio_map + clk_offset) = 1 << shift; - SunXI::short_wait(); - *(gpio_map + SunXI::PULLUPDN_OFFSET) &= ~3; - *(gpio_map + clk_offset) = 0; -} diff --git a/cpp/hal/gpiobus_bananam2p.h b/cpp/hal/gpiobus_bananam2p.h deleted file mode 100644 index 2da28f7a..00000000 --- a/cpp/hal/gpiobus_bananam2p.h +++ /dev/null @@ -1,204 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Powered by XM6 TypeG Technology. -// Copyright (C) 2016-2020 GIMONS -// [ GPIO-SCSI bus ] -// -//--------------------------------------------------------------------------- - -#pragma once - -#include "hal/data_sample_bananam2p.h" -#include "hal/gpiobus.h" -#include "hal/pi_defs/bpi-gpio.h" -#include "hal/sbc_version.h" -#include "hal/sunxi_utils.h" -#include "shared/log.h" -#include "shared/scsi.h" -#include - -//--------------------------------------------------------------------------- -// -// Class definition -// -//--------------------------------------------------------------------------- -class GPIOBUS_BananaM2p : public GPIOBUS -{ - public: - // Basic Functions - GPIOBUS_BananaM2p() = default; - ~GPIOBUS_BananaM2p() override = default; - // Destructor - bool Init(mode_e mode = mode_e::TARGET) override; - - void Cleanup() override; - void Reset() override; - - //--------------------------------------------------------------------------- - // - // Bus signal acquisition - // - //--------------------------------------------------------------------------- - uint32_t Acquire() override; - - void SetENB(bool ast) override; - // Set ENB signal - - bool GetBSY() const override; - // Get BSY signal - void SetBSY(bool ast) override; - // Set BSY signal - - bool GetSEL() const override; - // Get SEL signal - void SetSEL(bool ast) override; - // Set SEL signal - - bool GetATN() const override; - // Get ATN signal - void SetATN(bool ast) override; - // Set ATN signal - - bool GetACK() const override; - // Get ACK signal - void SetACK(bool ast) override; - // Set ACK signal - - bool GetACT() const override; - // Get ACT signal - void SetACT(bool ast) override; - // Set ACT signal - - bool GetRST() const override; - // Get RST signal - void SetRST(bool ast) override; - // Set RST signal - - bool GetMSG() const override; - // Get MSG signal - void SetMSG(bool ast) override; - // Set MSG signal - - bool GetCD() const override; - // Get CD signal - void SetCD(bool ast) override; - // Set CD signal - - bool GetIO() override; - // Get IO signal - void SetIO(bool ast) override; - // Set IO signal - - bool GetREQ() const override; - // Get REQ signal - void SetREQ(bool ast) override; - // Set REQ signal - - bool GetDP() const override; - - uint8_t GetDAT() override; - // Get DAT signal - void SetDAT(uint8_t dat) override; - // Set DAT signal - - bool WaitREQ(bool ast) override - { - return WaitSignal(BPI_PIN_REQ, ast); - } - bool WaitACK(bool ast) override - { - return WaitSignal(BPI_PIN_ACK, ast); - } - - // TODO: Restore these back to protected - // protected: - // SCSI I/O signal control - void MakeTable() override; - // Create work data - void SetControl(int pin, bool ast) override; - // Set Control Signal - void SetMode(int pin, int mode) override; - // Set SCSI I/O mode - int GetMode(int pin) override; - - inline bool GetSignal(int pin) const override; - - // Set SCSI output signal value - void SetSignal(int pin, bool ast) override; - - // Interrupt control - // IRQ Disabled - void DisableIRQ() override; - // IRQ Enabled - void EnableIRQ() override; - - // GPIO pin functionality settings - void PinConfig(int pin, int mode) override; - // GPIO pin direction setting - void PullConfig(int pin, int mode) override; - // GPIO pin pull up/down resistor setting - void PinSetSignal(int pin, bool ast) override; - // Set GPIO output signal - void DrvConfig(uint32_t drive) override; - // Set GPIO drive strength - - unique_ptr GetSample(uint64_t timestamp) override - { - Acquire(); - return make_unique(signals, timestamp); - } - - bool SetupSelEvent(); - - volatile uint32_t *gpio_map = nullptr; - - // Timer control register - volatile uint32_t *tmr_ctrl; - array signals = {0}; // All bus signals - - int sunxi_setup(void); - - void sunxi_set_pullupdn(int gpio, int pud); - void sunxi_setup_gpio(int gpio, int direction, int pud); - - void sunxi_output_gpio(int gpio, int value); - int sunxi_input_gpio(int gpio) const; - - int bpi_found = -1; - - volatile SunXI::sunxi_gpio_reg_t *pio_map; - volatile SunXI::sunxi_gpio_reg_t *r_pio_map; - - volatile uint32_t *r_gpio_map; - - uint8_t *gpio_mmap_reg; - uint32_t sunxi_capture_all_gpio(); - void set_pullupdn(int gpio, int pud); - - // These definitions are from c_gpio.c and should be removed at some point!! - const int SETUP_OK = 0; - - SBC_Version::sbc_version_type sbc_version; - - static const array SignalTable; - - void InitializeGpio(); - std::vector gpio_banks; - - // Note: These MUST be in order from bit 0 to bit 7 with parity as the last item in the array - const array pintbl = {BPI_PIN_DT0, BPI_PIN_DT1, BPI_PIN_DT2, BPI_PIN_DT3, BPI_PIN_DT4, - BPI_PIN_DT5, BPI_PIN_DT6, BPI_PIN_DT7, BPI_PIN_DP}; - - void sunxi_set_all_gpios(array &mask, array &value); - - array gpio_data_masks = {0}; - -#if defined(__x86_64__) || defined(__X86__) || defined(__aarch64__) - // The SEL_EVENT functions need to do something to prevent SonarCloud from - // claiming they should be const - int dummy_var = 0; -#endif -}; diff --git a/cpp/hal/gpiobus_factory.cpp b/cpp/hal/gpiobus_factory.cpp index 3a4a6914..a9351a56 100644 --- a/cpp/hal/gpiobus_factory.cpp +++ b/cpp/hal/gpiobus_factory.cpp @@ -1,51 +1,48 @@ //--------------------------------------------------------------------------- // -// SCSI Target Emulator PiSCSI -// for Raspberry Pi +// SCSI Target Emulator PiSCSI +// for Raspberry Pi // -// Copyright (C) 2022 akuker -// [ GPIO bus factory ] +// Copyright (C) 2022 akuker +// Copyright (C) 2023 Uwe Seimet // //--------------------------------------------------------------------------- #include -#include "hal/gpiobus_bananam2p.h" #include "hal/gpiobus_factory.h" #include "hal/gpiobus_raspberry.h" #include "hal/gpiobus_virtual.h" #include "hal/sbc_version.h" -#include "shared/log.h" +#include +#include using namespace std; unique_ptr GPIOBUS_Factory::Create(BUS::mode_e mode) { - unique_ptr return_ptr; + unique_ptr bus; try { - // TODO Make the factory a friend of GPIOBUS and make the GPIOBUS constructor private - // so that clients cannot use it anymore but have to use the factory. - // Also make Init() private. SBC_Version::Init(); - if (SBC_Version::IsBananaPi()) { - LOGTRACE("Creating GPIOBUS_BananaM2p") - return_ptr = make_unique(); - } else if (SBC_Version::IsRaspberryPi()) { - LOGTRACE("Creating GPIOBUS_Raspberry") - return_ptr = make_unique(); + if (SBC_Version::IsRaspberryPi()) { + if (getuid()) { + spdlog::error("GPIO bus access requires root permissions. Are you running as root?"); + return nullptr; + } + + bus = make_unique(); } else { - LOGINFO("Creating Virtual GPIOBUS") - return_ptr = make_unique(); + bus = make_unique(); } - if (!return_ptr->Init(mode)) { - return nullptr; + + if (bus->Init(mode)) { + bus->Reset(); } - return_ptr->Reset(); - } catch (const invalid_argument&) { - LOGERROR("Exception while trying to initialize GPIO bus. Are you running as root?") - return_ptr = nullptr; + } catch (const invalid_argument& e) { + spdlog::error(string("Exception while trying to initialize GPIO bus: ") + e.what()); + return nullptr; } - return return_ptr; + return bus; } diff --git a/cpp/hal/gpiobus_raspberry.cpp b/cpp/hal/gpiobus_raspberry.cpp index c3dfe3ca..5b6f77f7 100644 --- a/cpp/hal/gpiobus_raspberry.cpp +++ b/cpp/hal/gpiobus_raspberry.cpp @@ -15,13 +15,15 @@ // //--------------------------------------------------------------------------- +#include #include "hal/gpiobus_raspberry.h" #include "hal/gpiobus.h" #include "hal/systimer.h" -#include "shared/log.h" #include -#include +#include +#ifdef __linux__ #include +#endif #include #include #include @@ -80,14 +82,14 @@ bool GPIOBUS_Raspberry::Init(mode_e mode) // Open /dev/mem int fd = open("/dev/mem", O_RDWR | O_SYNC); if (fd == -1) { - LOGERROR("Error: Unable to open /dev/mem. Are you running as root?") + spdlog::error("Error: Unable to open /dev/mem. Are you running as root?"); return false; } // Map peripheral region memory void *map = mmap(NULL, 0x1000100, PROT_READ | PROT_WRITE, MAP_SHARED, fd, baseaddr); if (map == MAP_FAILED) { - LOGERROR("Error: Unable to map memory: %s", strerror(errno)) + spdlog::error("Error: Unable to map memory: "+ string(strerror(errno))); close(fd); return false; } @@ -183,7 +185,7 @@ bool GPIOBUS_Raspberry::Init(mode_e mode) // GPIO chip open fd = open("/dev/gpiochip0", 0); if (fd == -1) { - LOGERROR("Unable to open /dev/gpiochip0. Is PiSCSI or RaSCSI already running?") + spdlog::error("Unable to open /dev/gpiochip0. If PiSCSI is running, please shut it down first."); return false; } @@ -199,7 +201,7 @@ bool GPIOBUS_Raspberry::Init(mode_e mode) // Get event request if (ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, &selevreq) == -1) { - LOGERROR("Unable to register event request. Is PiSCSI or RaSCSI already running?") + spdlog::error("Unable to register event request. If PiSCSI is running, please shut it down first."); close(fd); return false; } diff --git a/cpp/hal/gpiobus_raspberry.h b/cpp/hal/gpiobus_raspberry.h index 3ebd0175..a79df0e5 100644 --- a/cpp/hal/gpiobus_raspberry.h +++ b/cpp/hal/gpiobus_raspberry.h @@ -13,7 +13,6 @@ #include "hal/data_sample_raspberry.h" #include "hal/gpiobus.h" -#include "shared/log.h" #include "shared/scsi.h" #include @@ -163,9 +162,9 @@ class GPIOBUS_Raspberry : public GPIOBUS protected: // All bus signals - uint32_t signals = 0; // NOSONAR: Must be protected (not private) for testability + uint32_t signals = 0; // GPIO input level - volatile uint32_t *level = nullptr; // NOSONAR: Must be protected (not private) for testability + volatile uint32_t *level = nullptr; private: // SCSI I/O signal control diff --git a/cpp/hal/gpiobus_virtual.cpp b/cpp/hal/gpiobus_virtual.cpp index 5f396d18..136dae24 100644 --- a/cpp/hal/gpiobus_virtual.cpp +++ b/cpp/hal/gpiobus_virtual.cpp @@ -6,19 +6,19 @@ // Powered by XM6 TypeG Technology. // Copyright (C) 2016-2020 GIMONS // -// [ GPIO-SCSI bus ] -// //--------------------------------------------------------------------------- #include "hal/gpiobus_virtual.h" #include "hal/gpiobus.h" #include "hal/systimer.h" -#include "shared/log.h" +#include "hal/log.h" #include #include #include #include +#ifdef __linux__ #include +#endif #include #include #include diff --git a/cpp/hal/gpiobus_virtual.h b/cpp/hal/gpiobus_virtual.h index dbfcd83a..85e00c79 100644 --- a/cpp/hal/gpiobus_virtual.h +++ b/cpp/hal/gpiobus_virtual.h @@ -5,7 +5,6 @@ // // Powered by XM6 TypeG Technology. // Copyright (C) 2016-2020 GIMONS -// [ GPIO-SCSI bus ] // //--------------------------------------------------------------------------- @@ -13,7 +12,6 @@ #include "hal/data_sample_raspberry.h" #include "hal/gpiobus.h" -#include "shared/log.h" #include "shared/scsi.h" #include @@ -171,4 +169,4 @@ class GPIOBUS_Virtual final : public GPIOBUS sem_t *mutex_sem, *buffer_count_sem, *spool_signal_sem; int fd_shm, fd_log; #endif -}; \ No newline at end of file +}; diff --git a/cpp/shared/log.h b/cpp/hal/log.h similarity index 86% rename from cpp/shared/log.h rename to cpp/hal/log.h index 379a7732..31286504 100644 --- a/cpp/shared/log.h +++ b/cpp/hal/log.h @@ -9,9 +9,11 @@ // //--------------------------------------------------------------------------- +// The legacy code in this file is deprecated and can cause a buffer overflow. Use spdlog directly instead. + #pragma once -#include "spdlog/spdlog.h" +#include static const int LOGBUF_SIZE = 512; diff --git a/cpp/hal/pi_defs/bpi-gpio.h b/cpp/hal/pi_defs/bpi-gpio.h deleted file mode 100644 index 89541fe8..00000000 --- a/cpp/hal/pi_defs/bpi-gpio.h +++ /dev/null @@ -1,356 +0,0 @@ -/* -Copyright (c) 2014-2017 Banana Pi -Updates Copyright (C) 2022 akuker - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -#pragma once - -const int GPIO_NOT_USED = -1; - -const int GPIO_PA00 = 0; -const int GPIO_PA01 = 1; -const int GPIO_PA02 = 2; -const int GPIO_PA03 = 3; -const int GPIO_PA04 = 4; -const int GPIO_PA05 = 5; -const int GPIO_PA06 = 6; -const int GPIO_PA07 = 7; -const int GPIO_PA08 = 8; -const int GPIO_PA09 = 9; -const int GPIO_PA10 = 10; -const int GPIO_PA11 = 11; -const int GPIO_PA12 = 12; -const int GPIO_PA13 = 13; -const int GPIO_PA14 = 14; -const int GPIO_PA15 = 15; -const int GPIO_PA16 = 16; -const int GPIO_PA17 = 17; -const int GPIO_PA18 = 18; -const int GPIO_PA19 = 19; -const int GPIO_PA20 = 20; -const int GPIO_PA21 = 21; -const int GPIO_PA22 = 22; -const int GPIO_PA23 = 23; -const int GPIO_PA24 = 24; -const int GPIO_PA25 = 25; -const int GPIO_PA26 = 26; -const int GPIO_PA27 = 27; -const int GPIO_PA28 = 28; -const int GPIO_PA29 = 29; -const int GPIO_PA30 = 30; -const int GPIO_PA31 = 31; - -const int GPIO_PB00 = 32; -const int GPIO_PB01 = 1 + GPIO_PB00; -const int GPIO_PB02 = 2 + GPIO_PB00; -const int GPIO_PB03 = 3 + GPIO_PB00; -const int GPIO_PB04 = 4 + GPIO_PB00; -const int GPIO_PB05 = 5 + GPIO_PB00; -const int GPIO_PB06 = 6 + GPIO_PB00; -const int GPIO_PB07 = 7 + GPIO_PB00; -const int GPIO_PB08 = 8 + GPIO_PB00; -const int GPIO_PB09 = 9 + GPIO_PB00; -const int GPIO_PB10 = 10 + GPIO_PB00; -const int GPIO_PB11 = 11 + GPIO_PB00; -const int GPIO_PB12 = 12 + GPIO_PB00; -const int GPIO_PB13 = 13 + GPIO_PB00; -const int GPIO_PB14 = 14 + GPIO_PB00; -const int GPIO_PB15 = 15 + GPIO_PB00; -const int GPIO_PB16 = 16 + GPIO_PB00; -const int GPIO_PB17 = 17 + GPIO_PB00; -const int GPIO_PB18 = 18 + GPIO_PB00; -const int GPIO_PB19 = 19 + GPIO_PB00; -const int GPIO_PB20 = 20 + GPIO_PB00; -const int GPIO_PB21 = 21 + GPIO_PB00; -const int GPIO_PB22 = 22 + GPIO_PB00; -const int GPIO_PB23 = 23 + GPIO_PB00; -const int GPIO_PB24 = 24 + GPIO_PB00; -const int GPIO_PB25 = 25 + GPIO_PB00; -const int GPIO_PB26 = 26 + GPIO_PB00; -const int GPIO_PB27 = 27 + GPIO_PB00; -const int GPIO_PB28 = 28 + GPIO_PB00; -const int GPIO_PB29 = 29 + GPIO_PB00; -const int GPIO_PB30 = 30 + GPIO_PB00; -const int GPIO_PB31 = 31 + GPIO_PB00; - -const int GPIO_PC00 = 64; -const int GPIO_PC01 = 1 + GPIO_PC00; -const int GPIO_PC02 = 2 + GPIO_PC00; -const int GPIO_PC03 = 3 + GPIO_PC00; -const int GPIO_PC04 = 4 + GPIO_PC00; -const int GPIO_PC05 = 5 + GPIO_PC00; -const int GPIO_PC06 = 6 + GPIO_PC00; -const int GPIO_PC07 = 7 + GPIO_PC00; -const int GPIO_PC08 = 8 + GPIO_PC00; -const int GPIO_PC09 = 9 + GPIO_PC00; -const int GPIO_PC10 = 10 + GPIO_PC00; -const int GPIO_PC11 = 11 + GPIO_PC00; -const int GPIO_PC12 = 12 + GPIO_PC00; -const int GPIO_PC13 = 13 + GPIO_PC00; -const int GPIO_PC14 = 14 + GPIO_PC00; -const int GPIO_PC15 = 15 + GPIO_PC00; -const int GPIO_PC16 = 16 + GPIO_PC00; -const int GPIO_PC17 = 17 + GPIO_PC00; -const int GPIO_PC18 = 18 + GPIO_PC00; -const int GPIO_PC19 = 19 + GPIO_PC00; -const int GPIO_PC20 = 20 + GPIO_PC00; -const int GPIO_PC21 = 21 + GPIO_PC00; -const int GPIO_PC22 = 22 + GPIO_PC00; -const int GPIO_PC23 = 23 + GPIO_PC00; -const int GPIO_PC24 = 24 + GPIO_PC00; -const int GPIO_PC25 = 25 + GPIO_PC00; -const int GPIO_PC26 = 26 + GPIO_PC00; -const int GPIO_PC27 = 27 + GPIO_PC00; -const int GPIO_PC28 = 28 + GPIO_PC00; -const int GPIO_PC29 = 29 + GPIO_PC00; -const int GPIO_PC30 = 30 + GPIO_PC00; -const int GPIO_PC31 = 31 + GPIO_PC00; - -const int GPIO_PD00 = 96; -const int GPIO_PD01 = 1 + GPIO_PD00; -const int GPIO_PD02 = 2 + GPIO_PD00; -const int GPIO_PD03 = 3 + GPIO_PD00; -const int GPIO_PD04 = 4 + GPIO_PD00; -const int GPIO_PD05 = 5 + GPIO_PD00; -const int GPIO_PD06 = 6 + GPIO_PD00; -const int GPIO_PD07 = 7 + GPIO_PD00; -const int GPIO_PD08 = 8 + GPIO_PD00; -const int GPIO_PD09 = 9 + GPIO_PD00; -const int GPIO_PD10 = 10 + GPIO_PD00; -const int GPIO_PD11 = 11 + GPIO_PD00; -const int GPIO_PD12 = 12 + GPIO_PD00; -const int GPIO_PD13 = 13 + GPIO_PD00; -const int GPIO_PD14 = 14 + GPIO_PD00; -const int GPIO_PD15 = 15 + GPIO_PD00; -const int GPIO_PD16 = 16 + GPIO_PD00; -const int GPIO_PD17 = 17 + GPIO_PD00; -const int GPIO_PD18 = 18 + GPIO_PD00; -const int GPIO_PD19 = 19 + GPIO_PD00; -const int GPIO_PD20 = 20 + GPIO_PD00; -const int GPIO_PD21 = 21 + GPIO_PD00; -const int GPIO_PD22 = 22 + GPIO_PD00; -const int GPIO_PD23 = 23 + GPIO_PD00; -const int GPIO_PD24 = 24 + GPIO_PD00; -const int GPIO_PD25 = 25 + GPIO_PD00; -const int GPIO_PD26 = 26 + GPIO_PD00; -const int GPIO_PD27 = 27 + GPIO_PD00; -const int GPIO_PD28 = 28 + GPIO_PD00; -const int GPIO_PD29 = 29 + GPIO_PD00; -const int GPIO_PD30 = 30 + GPIO_PD00; -const int GPIO_PD31 = 31 + GPIO_PD00; - -const int GPIO_PE00 = 128; -const int GPIO_PE01 = 1 + GPIO_PE00; -const int GPIO_PE02 = 2 + GPIO_PE00; -const int GPIO_PE03 = 3 + GPIO_PE00; -const int GPIO_PE04 = 4 + GPIO_PE00; -const int GPIO_PE05 = 5 + GPIO_PE00; -const int GPIO_PE06 = 6 + GPIO_PE00; -const int GPIO_PE07 = 7 + GPIO_PE00; -const int GPIO_PE08 = 8 + GPIO_PE00; -const int GPIO_PE09 = 9 + GPIO_PE00; -const int GPIO_PE10 = 10 + GPIO_PE00; -const int GPIO_PE11 = 11 + GPIO_PE00; -const int GPIO_PE12 = 12 + GPIO_PE00; -const int GPIO_PE13 = 13 + GPIO_PE00; -const int GPIO_PE14 = 14 + GPIO_PE00; -const int GPIO_PE15 = 15 + GPIO_PE00; -const int GPIO_PE16 = 16 + GPIO_PE00; -const int GPIO_PE17 = 17 + GPIO_PE00; -const int GPIO_PE18 = 18 + GPIO_PE00; -const int GPIO_PE19 = 19 + GPIO_PE00; -const int GPIO_PE20 = 20 + GPIO_PE00; -const int GPIO_PE21 = 21 + GPIO_PE00; -const int GPIO_PE22 = 22 + GPIO_PE00; -const int GPIO_PE23 = 23 + GPIO_PE00; -const int GPIO_PE24 = 24 + GPIO_PE00; -const int GPIO_PE25 = 25 + GPIO_PE00; -const int GPIO_PE26 = 26 + GPIO_PE00; -const int GPIO_PE27 = 27 + GPIO_PE00; -const int GPIO_PE28 = 28 + GPIO_PE00; -const int GPIO_PE29 = 29 + GPIO_PE00; -const int GPIO_PE30 = 30 + GPIO_PE00; -const int GPIO_PE31 = 31 + GPIO_PE00; - -const int GPIO_PG00 = 192; -const int GPIO_PG01 = 1 + GPIO_PG00; -const int GPIO_PG02 = 2 + GPIO_PG00; -const int GPIO_PG03 = 3 + GPIO_PG00; -const int GPIO_PG04 = 4 + GPIO_PG00; -const int GPIO_PG05 = 5 + GPIO_PG00; -const int GPIO_PG06 = 6 + GPIO_PG00; -const int GPIO_PG07 = 7 + GPIO_PG00; -const int GPIO_PG08 = 8 + GPIO_PG00; -const int GPIO_PG09 = 9 + GPIO_PG00; -const int GPIO_PG10 = 10 + GPIO_PG00; -const int GPIO_PG11 = 11 + GPIO_PG00; -const int GPIO_PG12 = 12 + GPIO_PG00; -const int GPIO_PG13 = 13 + GPIO_PG00; -const int GPIO_PG14 = 14 + GPIO_PG00; -const int GPIO_PG15 = 15 + GPIO_PG00; -const int GPIO_PG16 = 16 + GPIO_PG00; -const int GPIO_PG17 = 17 + GPIO_PG00; -const int GPIO_PG18 = 18 + GPIO_PG00; -const int GPIO_PG19 = 19 + GPIO_PG00; -const int GPIO_PG20 = 20 + GPIO_PG00; -const int GPIO_PG21 = 21 + GPIO_PG00; -const int GPIO_PG22 = 22 + GPIO_PG00; -const int GPIO_PG23 = 23 + GPIO_PG00; -const int GPIO_PG24 = 24 + GPIO_PG00; -const int GPIO_PG25 = 25 + GPIO_PG00; -const int GPIO_PG26 = 26 + GPIO_PG00; -const int GPIO_PG27 = 27 + GPIO_PG00; -const int GPIO_PG28 = 28 + GPIO_PG00; -const int GPIO_PG29 = 29 + GPIO_PG00; -const int GPIO_PG30 = 30 + GPIO_PG00; -const int GPIO_PG31 = 31 + GPIO_PG00; - -const int GPIO_PH00 = 224; -const int GPIO_PH01 = 1 + GPIO_PH00; -const int GPIO_PH02 = 2 + GPIO_PH00; -const int GPIO_PH03 = 3 + GPIO_PH00; -const int GPIO_PH04 = 4 + GPIO_PH00; -const int GPIO_PH05 = 5 + GPIO_PH00; -const int GPIO_PH06 = 6 + GPIO_PH00; -const int GPIO_PH07 = 7 + GPIO_PH00; -const int GPIO_PH08 = 8 + GPIO_PH00; -const int GPIO_PH09 = 9 + GPIO_PH00; -const int GPIO_PH10 = 10 + GPIO_PH00; -const int GPIO_PH11 = 11 + GPIO_PH00; -const int GPIO_PH12 = 12 + GPIO_PH00; -const int GPIO_PH13 = 13 + GPIO_PH00; -const int GPIO_PH14 = 14 + GPIO_PH00; -const int GPIO_PH15 = 15 + GPIO_PH00; -const int GPIO_PH16 = 16 + GPIO_PH00; -const int GPIO_PH17 = 17 + GPIO_PH00; -const int GPIO_PH18 = 18 + GPIO_PH00; -const int GPIO_PH19 = 19 + GPIO_PH00; -const int GPIO_PH20 = 20 + GPIO_PH00; -const int GPIO_PH21 = 21 + GPIO_PH00; -const int GPIO_PH22 = 22 + GPIO_PH00; -const int GPIO_PH23 = 23 + GPIO_PH00; -const int GPIO_PH24 = 24 + GPIO_PH00; -const int GPIO_PH25 = 25 + GPIO_PH00; -const int GPIO_PH26 = 26 + GPIO_PH00; -const int GPIO_PH27 = 27 + GPIO_PH00; -const int GPIO_PH28 = 28 + GPIO_PH00; -const int GPIO_PH29 = 29 + GPIO_PH00; -const int GPIO_PH30 = 30 + GPIO_PH00; -const int GPIO_PH31 = 31 + GPIO_PH00; - -const int GPIO_PI00 = 256; -const int GPIO_PI01 = 1 + GPIO_PI00; -const int GPIO_PI02 = 2 + GPIO_PI00; -const int GPIO_PI03 = 3 + GPIO_PI00; -const int GPIO_PI04 = 4 + GPIO_PI00; -const int GPIO_PI05 = 5 + GPIO_PI00; -const int GPIO_PI06 = 6 + GPIO_PI00; -const int GPIO_PI07 = 7 + GPIO_PI00; -const int GPIO_PI08 = 8 + GPIO_PI00; -const int GPIO_PI09 = 9 + GPIO_PI00; -const int GPIO_PI10 = 10 + GPIO_PI00; -const int GPIO_PI11 = 11 + GPIO_PI00; -const int GPIO_PI12 = 12 + GPIO_PI00; -const int GPIO_PI13 = 13 + GPIO_PI00; -const int GPIO_PI14 = 14 + GPIO_PI00; -const int GPIO_PI15 = 15 + GPIO_PI00; -const int GPIO_PI16 = 16 + GPIO_PI00; -const int GPIO_PI17 = 17 + GPIO_PI00; -const int GPIO_PI18 = 18 + GPIO_PI00; -const int GPIO_PI19 = 19 + GPIO_PI00; -const int GPIO_PI20 = 20 + GPIO_PI00; -const int GPIO_PI21 = 21 + GPIO_PI00; -const int GPIO_PI22 = 22 + GPIO_PI00; -const int GPIO_PI23 = 23 + GPIO_PI00; -const int GPIO_PI24 = 24 + GPIO_PI00; -const int GPIO_PI25 = 25 + GPIO_PI00; -const int GPIO_PI26 = 26 + GPIO_PI00; -const int GPIO_PI27 = 27 + GPIO_PI00; -const int GPIO_PI28 = 28 + GPIO_PI00; -const int GPIO_PI29 = 29 + GPIO_PI00; -const int GPIO_PI30 = 30 + GPIO_PI00; -const int GPIO_PI31 = 31 + GPIO_PI00; - -const int GPIO_PL00 = 352; -const int GPIO_PL01 = 1 + GPIO_PL00; -const int GPIO_PL02 = 2 + GPIO_PL00; -const int GPIO_PL03 = 3 + GPIO_PL00; -const int GPIO_PL04 = 4 + GPIO_PL00; -const int GPIO_PL05 = 5 + GPIO_PL00; -const int GPIO_PL06 = 6 + GPIO_PL00; -const int GPIO_PL07 = 7 + GPIO_PL00; -const int GPIO_PL08 = 8 + GPIO_PL00; -const int GPIO_PL09 = 9 + GPIO_PL00; -const int GPIO_PL10 = 10 + GPIO_PL00; -const int GPIO_PL11 = 11 + GPIO_PL00; -const int GPIO_PL12 = 12 + GPIO_PL00; -const int GPIO_PL13 = 13 + GPIO_PL00; -const int GPIO_PL14 = 14 + GPIO_PL00; -const int GPIO_PL15 = 15 + GPIO_PL00; -const int GPIO_PL16 = 16 + GPIO_PL00; -const int GPIO_PL17 = 17 + GPIO_PL00; -const int GPIO_PL18 = 18 + GPIO_PL00; -const int GPIO_PL19 = 19 + GPIO_PL00; -const int GPIO_PL20 = 20 + GPIO_PL00; -const int GPIO_PL21 = 21 + GPIO_PL00; -const int GPIO_PL22 = 22 + GPIO_PL00; -const int GPIO_PL23 = 23 + GPIO_PL00; -const int GPIO_PL24 = 24 + GPIO_PL00; -const int GPIO_PL25 = 25 + GPIO_PL00; -const int GPIO_PL26 = 26 + GPIO_PL00; -const int GPIO_PL27 = 27 + GPIO_PL00; -const int GPIO_PL28 = 28 + GPIO_PL00; -const int GPIO_PL29 = 29 + GPIO_PL00; -const int GPIO_PL30 = 30 + GPIO_PL00; -const int GPIO_PL31 = 31 + GPIO_PL00; - -const int GPIO_PM00 = 384; -const int GPIO_PM01 = 1 + GPIO_PM00; -const int GPIO_PM02 = 2 + GPIO_PM00; -const int GPIO_PM03 = 3 + GPIO_PM00; -const int GPIO_PM04 = 4 + GPIO_PM00; -const int GPIO_PM05 = 5 + GPIO_PM00; -const int GPIO_PM06 = 6 + GPIO_PM00; -const int GPIO_PM07 = 7 + GPIO_PM00; -const int GPIO_PM08 = 8 + GPIO_PM00; -const int GPIO_PM09 = 9 + GPIO_PM00; -const int GPIO_PM10 = 10 + GPIO_PM00; -const int GPIO_PM11 = 11 + GPIO_PM00; -const int GPIO_PM12 = 12 + GPIO_PM00; -const int GPIO_PM13 = 13 + GPIO_PM00; -const int GPIO_PM14 = 14 + GPIO_PM00; -const int GPIO_PM15 = 15 + GPIO_PM00; -const int GPIO_PM16 = 16 + GPIO_PM00; -const int GPIO_PM17 = 17 + GPIO_PM00; -const int GPIO_PM18 = 18 + GPIO_PM00; -const int GPIO_PM19 = 19 + GPIO_PM00; -const int GPIO_PM20 = 20 + GPIO_PM00; -const int GPIO_PM21 = 21 + GPIO_PM00; -const int GPIO_PM22 = 22 + GPIO_PM00; -const int GPIO_PM23 = 23 + GPIO_PM00; -const int GPIO_PM24 = 24 + GPIO_PM00; -const int GPIO_PM25 = 25 + GPIO_PM00; -const int GPIO_PM26 = 26 + GPIO_PM00; -const int GPIO_PM27 = 27 + GPIO_PM00; -const int GPIO_PM28 = 28 + GPIO_PM00; -const int GPIO_PM29 = 29 + GPIO_PM00; -const int GPIO_PM30 = 30 + GPIO_PM00; -const int GPIO_PM31 = 31 + GPIO_PM00; diff --git a/cpp/hal/pi_defs/bpi-m2p.h b/cpp/hal/pi_defs/bpi-m2p.h deleted file mode 100755 index 096fd260..00000000 --- a/cpp/hal/pi_defs/bpi-m2p.h +++ /dev/null @@ -1,71 +0,0 @@ -/* -Copyright (c) 2014-2017 Banana Pi -Updates Copyright (C) 2022 akuker - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -#pragma once - -#include "hal/pi_defs/bpi-gpio.h" - -/* The following define the mapping of the Banana Pi CPU pins to the logical - * GPIO numbers. The GPIO numbers are used by the software to set/configure - * the CPU registers. */ -const int BPI_M2P_01 = GPIO_NOT_USED; -const int BPI_M2P_03 = GPIO_PA12; -const int BPI_M2P_05 = GPIO_PA11; -const int BPI_M2P_07 = GPIO_PA06; -const int BPI_M2P_09 = GPIO_NOT_USED; -const int BPI_M2P_11 = GPIO_PA01; -const int BPI_M2P_13 = GPIO_PA00; -const int BPI_M2P_15 = GPIO_PA03; -const int BPI_M2P_17 = GPIO_NOT_USED; -const int BPI_M2P_19 = GPIO_PC00; -const int BPI_M2P_21 = GPIO_PC01; -const int BPI_M2P_23 = GPIO_PC02; -const int BPI_M2P_25 = GPIO_NOT_USED; -const int BPI_M2P_27 = GPIO_PA19; -const int BPI_M2P_29 = GPIO_PA07; -const int BPI_M2P_31 = GPIO_PA08; -const int BPI_M2P_33 = GPIO_PA09; -const int BPI_M2P_35 = GPIO_PA10; -const int BPI_M2P_37 = GPIO_PA17; -const int BPI_M2P_39 = GPIO_NOT_USED; - -const int BPI_M2P_02 = GPIO_NOT_USED; -const int BPI_M2P_04 = GPIO_NOT_USED; -const int BPI_M2P_06 = GPIO_NOT_USED; -const int BPI_M2P_08 = GPIO_PA13; -const int BPI_M2P_10 = GPIO_PA14; -const int BPI_M2P_12 = GPIO_PA16; -const int BPI_M2P_14 = GPIO_NOT_USED; -const int BPI_M2P_16 = GPIO_PA15; -const int BPI_M2P_18 = GPIO_PC04; -const int BPI_M2P_20 = GPIO_NOT_USED; -const int BPI_M2P_22 = GPIO_PA02; -const int BPI_M2P_24 = GPIO_PC03; -const int BPI_M2P_26 = GPIO_PC07; -const int BPI_M2P_28 = GPIO_PA18; -const int BPI_M2P_30 = GPIO_NOT_USED; -const int BPI_M2P_32 = GPIO_PL02; -const int BPI_M2P_34 = GPIO_NOT_USED; -const int BPI_M2P_36 = GPIO_PL04; -const int BPI_M2P_38 = GPIO_PA21; -const int BPI_M2P_40 = GPIO_PA20; diff --git a/cpp/hal/sbc_version.cpp b/cpp/hal/sbc_version.cpp index 211d8c46..caebbedf 100644 --- a/cpp/hal/sbc_version.cpp +++ b/cpp/hal/sbc_version.cpp @@ -10,26 +10,18 @@ //--------------------------------------------------------------------------- #include "sbc_version.h" -#include "shared/log.h" +#include #include #include #include -SBC_Version::sbc_version_type SBC_Version::m_sbc_version = sbc_version_type::sbc_unknown; +SBC_Version::sbc_version_type SBC_Version::sbc_version = sbc_version_type::sbc_unknown; // TODO: THESE NEED TO BE VALIDATED!!!! -const std::string SBC_Version::m_str_raspberry_pi_1 = "Raspberry Pi 1"; -const std::string SBC_Version::m_str_raspberry_pi_2_3 = "Raspberry Pi 2/3"; -const std::string SBC_Version::m_str_raspberry_pi_4 = "Raspberry Pi 4"; -const std::string SBC_Version::m_str_bananapi_m1_plus = "Banana Pi M1 Plus"; -const std::string SBC_Version::m_str_bananapi_m2_berry = "Banana Pi M2 Berry/Ultra"; -const std::string SBC_Version::m_str_bananapi_m2_zero = "Banana Pi M2 Zero"; -const std::string SBC_Version::m_str_bananapi_m2_plus = "Banana Pi BPI-M2-Plus H3"; -const std::string SBC_Version::m_str_bananapi_m3 = "Banana Pi M3"; -const std::string SBC_Version::m_str_bananapi_m4 = "Banana Pi M4"; -const std::string SBC_Version::m_str_bananapi_m5 = "Banana Pi M5"; -const std::string SBC_Version::m_str_bananapi_m64 = "Banana Pi M64"; -const std::string SBC_Version::m_str_unknown_sbc = "Unknown SBC"; +const string SBC_Version::str_raspberry_pi_1 = "Raspberry Pi 1"; +const string SBC_Version::str_raspberry_pi_2_3 = "Raspberry Pi 2/3"; +const string SBC_Version::str_raspberry_pi_4 = "Raspberry Pi 4"; +const string SBC_Version::str_unknown_sbc = "Unknown SBC"; // The strings in this table should align with the 'model' embedded // in the device tree. This can be aquired by running: @@ -39,57 +31,41 @@ const std::string SBC_Version::m_str_unknown_sbc = "Unknown SBC"; // "Raspberry Pi 4 Model B" will match with both of the following: // - Raspberry Pi 4 Model B Rev 1.4 // - Raspberry Pi 4 Model B Rev 1.3 -const std::map> SBC_Version::m_proc_device_tree_mapping = { +// TODO Is there a better way to detect the Pi type than relying on strings? +const map> SBC_Version::proc_device_tree_mapping = { {"Raspberry Pi 1 Model ", sbc_version_type::sbc_raspberry_pi_1}, {"Raspberry Pi 2 Model ", sbc_version_type::sbc_raspberry_pi_2_3}, {"Raspberry Pi 3 Model ", sbc_version_type::sbc_raspberry_pi_2_3}, {"Raspberry Pi 4 Model ", sbc_version_type::sbc_raspberry_pi_4}, {"Raspberry Pi 400 ", sbc_version_type::sbc_raspberry_pi_4}, {"Raspberry Pi Zero W", sbc_version_type::sbc_raspberry_pi_1}, - {"Raspberry Pi Zero", sbc_version_type::sbc_raspberry_pi_1}, - {"Banana Pi BPI-M2-Zero ", sbc_version_type::sbc_bananapi_m2_zero}, - {"Banana Pi BPI-M2-Ultra ", sbc_version_type::sbc_bananapi_m2_berry}, - {"Banana Pi BPI-M2-Plus H3", sbc_version_type::sbc_bananapi_m2_plus}, - {"Banana Pi M2 Berry ", sbc_version_type::sbc_bananapi_m2_berry}, - // sbc_bananapi_m3, TBD.... - // sbc_bananapi_m4, + {"Raspberry Pi Zero", sbc_version_type::sbc_raspberry_pi_1} }; -const std::string SBC_Version::m_device_tree_model_path = "/proc/device-tree/model"; +const string SBC_Version::m_device_tree_model_path = "/proc/device-tree/model"; //--------------------------------------------------------------------------- // // Convert the SBC Version to a printable string // //--------------------------------------------------------------------------- -const std::string *SBC_Version::GetString() +string SBC_Version::GetAsString() { - switch (m_sbc_version) { + switch (sbc_version) { case sbc_version_type::sbc_raspberry_pi_1: - return &m_str_raspberry_pi_1; + return str_raspberry_pi_1; case sbc_version_type::sbc_raspberry_pi_2_3: - return &m_str_raspberry_pi_2_3; + return str_raspberry_pi_2_3; case sbc_version_type::sbc_raspberry_pi_4: - return &m_str_raspberry_pi_4; - case sbc_version_type::sbc_bananapi_m2_berry: - return &m_str_bananapi_m2_berry; - case sbc_version_type::sbc_bananapi_m2_zero: - return &m_str_bananapi_m2_zero; - case sbc_version_type::sbc_bananapi_m2_plus: - return &m_str_bananapi_m2_plus; - case sbc_version_type::sbc_bananapi_m3: - return &m_str_bananapi_m3; - case sbc_version_type::sbc_bananapi_m4: - return &m_str_bananapi_m4; + return str_raspberry_pi_4; default: - LOGERROR("Unknown type of sbc detected: %d", static_cast(m_sbc_version)) - return &m_str_unknown_sbc; + return str_unknown_sbc; } } SBC_Version::sbc_version_type SBC_Version::GetSbcVersion() { - return m_sbc_version; + return sbc_version; } //--------------------------------------------------------------------------- @@ -100,71 +76,43 @@ SBC_Version::sbc_version_type SBC_Version::GetSbcVersion() //--------------------------------------------------------------------------- void SBC_Version::Init() { - LOGTRACE("%s", __PRETTY_FUNCTION__) - std::string device_tree_model; - - const std::ifstream input_stream(SBC_Version::m_device_tree_model_path); + ifstream input_stream(SBC_Version::m_device_tree_model_path); if (input_stream.fail()) { #if defined(__x86_64__) || defined(__X86__) // We expect this to fail on x86 - LOGINFO("Detected device %s", GetString()->c_str()) - m_sbc_version = sbc_version_type::sbc_unknown; + spdlog::warn("Detected " + GetAsString()); + sbc_version = sbc_version_type::sbc_unknown; return; #else - LOGERROR("Failed to open %s. Are you running as root?", SBC_Version::m_device_tree_model_path.c_str()) - throw std::invalid_argument("Failed to open /proc/device-tree/model"); + spdlog::error("Failed to open " + SBC_Version::m_device_tree_model_path + ". Are you running as root?"); + throw invalid_argument("Failed to open /proc/device-tree/model"); #endif } - std::stringstream str_buffer; + stringstream str_buffer; str_buffer << input_stream.rdbuf(); - device_tree_model = str_buffer.str(); + const string device_tree_model = str_buffer.str(); - for (const auto &[key, value] : m_proc_device_tree_mapping) { - if (device_tree_model.rfind(key, 0) == 0) { - m_sbc_version = value; - LOGINFO("Detected device %s", GetString()->c_str()) - return; - } + for (const auto& [key, value] : proc_device_tree_mapping) { + if (device_tree_model.starts_with(key)) { + sbc_version = value; + spdlog::info("Detected " + GetAsString()); + return; + } } - LOGERROR("%s Unable to determine single board computer type. Defaulting to Raspberry Pi 4", __PRETTY_FUNCTION__) - m_sbc_version = sbc_version_type::sbc_raspberry_pi_4; + + sbc_version = sbc_version_type::sbc_raspberry_pi_4; + spdlog::error("Unable to determine single board computer type. Defaulting to Raspberry Pi 4"); } bool SBC_Version::IsRaspberryPi() { - LOGTRACE("%s", __PRETTY_FUNCTION__) - switch (m_sbc_version) { + switch (sbc_version) { case sbc_version_type::sbc_raspberry_pi_1: case sbc_version_type::sbc_raspberry_pi_2_3: case sbc_version_type::sbc_raspberry_pi_4: return true; - case sbc_version_type::sbc_bananapi_m2_berry: - case sbc_version_type::sbc_bananapi_m2_zero: - case sbc_version_type::sbc_bananapi_m2_plus: - case sbc_version_type::sbc_bananapi_m3: - case sbc_version_type::sbc_bananapi_m4: - return false; - default: - return false; - } -} - -bool SBC_Version::IsBananaPi() -{ - LOGTRACE("%s", __PRETTY_FUNCTION__) - switch (m_sbc_version) { - case sbc_version_type::sbc_raspberry_pi_1: - case sbc_version_type::sbc_raspberry_pi_2_3: - case sbc_version_type::sbc_raspberry_pi_4: - return false; - case sbc_version_type::sbc_bananapi_m2_berry: - case sbc_version_type::sbc_bananapi_m2_zero: - case sbc_version_type::sbc_bananapi_m2_plus: - case sbc_version_type::sbc_bananapi_m3: - case sbc_version_type::sbc_bananapi_m4: - return true; default: return false; } @@ -174,11 +122,10 @@ bool SBC_Version::IsBananaPi() // (imported from bcm_host.c) uint32_t SBC_Version::GetDeviceTreeRanges(const char *filename, uint32_t offset) { - LOGTRACE("%s", __PRETTY_FUNCTION__) uint32_t address = ~0; if (FILE *fp = fopen(filename, "rb"); fp) { fseek(fp, offset, SEEK_SET); - if (std::array buf; fread(buf.data(), 1, buf.size(), fp) == buf.size()) { + if (array buf; fread(buf.data(), 1, buf.size(), fp) == buf.size()) { address = (int)buf[0] << 24 | (int)buf[1] << 16 | (int)buf[2] << 8 | (int)buf[3] << 0; } fclose(fp); @@ -189,15 +136,12 @@ uint32_t SBC_Version::GetDeviceTreeRanges(const char *filename, uint32_t offset) #if defined __linux__ uint32_t SBC_Version::GetPeripheralAddress(void) { - LOGTRACE("%s", __PRETTY_FUNCTION__) uint32_t address = GetDeviceTreeRanges("/proc/device-tree/soc/ranges", 4); if (address == 0) { address = GetDeviceTreeRanges("/proc/device-tree/soc/ranges", 8); } address = (address == (uint32_t)~0) ? 0x20000000 : address; - LOGDEBUG("Peripheral address : 0x%8x\n", address) - return address; } #elif defined __NetBSD__ @@ -215,7 +159,6 @@ uint32_t SBC_Version::GetPeripheralAddress(void) // Use BCM2835 address address = 0x20000000; } - LOGDEBUG("Peripheral address : 0x%lx\n", address); return address; } #else diff --git a/cpp/hal/sbc_version.h b/cpp/hal/sbc_version.h index d55bdf21..c51100e4 100644 --- a/cpp/hal/sbc_version.h +++ b/cpp/hal/sbc_version.h @@ -11,9 +11,12 @@ #pragma once +#include #include #include +using namespace std; + //=========================================================================== // // Single Board Computer Versions @@ -27,16 +30,7 @@ class SBC_Version sbc_unknown = 0, sbc_raspberry_pi_1, sbc_raspberry_pi_2_3, - sbc_raspberry_pi_4, - sbc_bananapi_m1_plus, - sbc_bananapi_m2_ultra, - sbc_bananapi_m2_berry, - sbc_bananapi_m2_zero, - sbc_bananapi_m2_plus, - sbc_bananapi_m3, - sbc_bananapi_m4, - sbc_bananapi_m5, - sbc_bananapi_m64, + sbc_raspberry_pi_4 }; SBC_Version() = delete; @@ -47,32 +41,22 @@ class SBC_Version static sbc_version_type GetSbcVersion(); static bool IsRaspberryPi(); - static bool IsBananaPi(); - static const std::string *GetString(); + static string GetAsString(); static uint32_t GetPeripheralAddress(); private: - static sbc_version_type m_sbc_version; + static sbc_version_type sbc_version; - static const std::string m_str_raspberry_pi_1; - static const std::string m_str_raspberry_pi_2_3; - static const std::string m_str_raspberry_pi_4; - static const std::string m_str_bananapi_m1_plus; - static const std::string m_str_bananapi_m2_ultra; - static const std::string m_str_bananapi_m2_berry; - static const std::string m_str_bananapi_m2_zero; - static const std::string m_str_bananapi_m2_plus; - static const std::string m_str_bananapi_m3; - static const std::string m_str_bananapi_m4; - static const std::string m_str_bananapi_m5; - static const std::string m_str_bananapi_m64; - static const std::string m_str_unknown_sbc; + static const string str_raspberry_pi_1; + static const string str_raspberry_pi_2_3; + static const string str_raspberry_pi_4; + static const string str_unknown_sbc; - static const std::map> m_proc_device_tree_mapping; + static const map> proc_device_tree_mapping; - static const std::string m_device_tree_model_path; + static const string m_device_tree_model_path; static uint32_t GetDeviceTreeRanges(const char *filename, uint32_t offset); }; diff --git a/cpp/hal/sunxi_utils.cpp b/cpp/hal/sunxi_utils.cpp deleted file mode 100644 index 8c103d00..00000000 --- a/cpp/hal/sunxi_utils.cpp +++ /dev/null @@ -1,65 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Copyright (C) 2022 akuker -// -// [ Utility functions for working with Allwinner CPUs ] -// -// This should include generic functions that can be applicable to -// different variants of the SunXI (Allwinner) SoCs -// -// Large portions of this functionality were derived from c_gpio.c, which -// is part of the RPI.GPIO library available here: -// https://github.com/BPI-SINOVOIP/RPi.GPIO/blob/master/source/c_gpio.c -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -// of the Software, and to permit persons to whom the Software is furnished to do -// so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// -//--------------------------------------------------------------------------- - -#include "hal/sunxi_utils.h" -#include -#include - -using namespace std; - -static const string BLACK = "\033[30m"; /* Black */ -static const string RED = "\033[31m"; /* Red */ -static const string GREEN = "\033[32m"; /* Green */ -static const string YELLOW = "\033[33m"; /* Yellow */ -static const string BLUE = "\033[34m"; /* Blue */ -static const string MAGENTA = "\033[35m"; /* Magenta */ -static const string CYAN = "\033[36m"; /* Cyan */ -static const string WHITE = "\033[37m"; /* White */ - -// TODO: this is only a debug function that will be removed at a later date..... -void dump_gpio_registers(const SunXI::sunxi_gpio_reg_t *regs) -{ - printf("%s--- GPIO BANK 0 CFG: %08X %08X %08X %08X\n", CYAN.c_str(), regs->gpio_bank[0].CFG[0], - regs->gpio_bank[0].CFG[1], regs->gpio_bank[0].CFG[2], regs->gpio_bank[0].CFG[3]); - - printf("--- Dat: (%08X) DRV: %08X %08X\n", regs->gpio_bank[0].DAT, regs->gpio_bank[0].DRV[0], - regs->gpio_bank[0].DRV[1]); - printf("--- Pull: %08X %08x\n", regs->gpio_bank[0].PULL[0], regs->gpio_bank[0].PULL[1]); - - printf("--- GPIO INT CFG: %08X %08X %08X\n", regs->gpio_int.CFG[0], regs->gpio_int.CFG[1], regs->gpio_int.CFG[2]); - printf("--- CTL: (%08X) STA: %08X DEB: %08X\n %s", regs->gpio_int.CTL, regs->gpio_int.STA, regs->gpio_int.DEB, - WHITE.c_str()); -} \ No newline at end of file diff --git a/cpp/hal/sunxi_utils.h b/cpp/hal/sunxi_utils.h deleted file mode 100644 index 14e7d8f7..00000000 --- a/cpp/hal/sunxi_utils.h +++ /dev/null @@ -1,189 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Copyright (C) 2022 akuker -// -// [ Utility functions for working with Allwinner CPUs ] -// -// This should include generic functions that can be applicable to -// different variants of the SunXI (Allwinner) SoCs -// -// Large portions of this functionality were derived from c_gpio.c, which -// is part of the RPI.GPIO library available here: -// https://github.com/BPI-SINOVOIP/RPi.GPIO/blob/master/source/c_gpio.c -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -// of the Software, and to permit persons to whom the Software is furnished to do -// so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// -//--------------------------------------------------------------------------- - -#pragma once - -#include -#ifndef __arm__ -#include -#endif - -class SunXI -{ - public: - static inline int GPIO_BANK(int pin) - { - return (pin >> 5); - } - static inline int GPIO_NUM(int pin) - { - return (pin & 0x1F); - } - static inline int GPIO_CFG_INDEX(int pin) - { - return ((pin & 0x1F) >> 3); - } - static inline int GPIO_CFG_OFFSET(int pin) - { - return (((pin & 0x1F) & 0x7) << 2); - } - static inline int GPIO_PUL_INDEX(int pin) - { - return ((pin & 0x1F) >> 4); - } - static inline int GPIO_PUL_OFFSET(int pin) - { - return ((pin & 0x0F) << 1); - } - static inline int GPIO_DRV_INDEX(int pin) - { - return ((pin & 0x1F) >> 4); - } - static inline int GPIO_DRV_OFFSET(int pin) - { - return ((pin & 0x0F) << 1); - } - - static inline void short_wait(void) - { - for (int i = 0; i < 150; i++) { -#ifndef __arm__ - // Timing doesn't really matter if we're not on ARM. - // The SunXI SoCs are all ARM-based. - const timespec ts = {.tv_sec = 0, .tv_nsec = 1}; - nanosleep(&ts, nullptr); -#else - // wait 150 cycles - asm volatile("nop"); -#endif - } - } - - enum class gpio_configure_values_e : uint8_t { - gpio_input = 0b000, - gpio_output = 0b001, - gpio_alt_func_1 = 0b010, - gpio_alt_func_2 = 0b011, - gpio_reserved_1 = 0b100, - gpio_reserved_2 = 0b101, - gpio_interupt = 0b110, - gpio_disable = 0b111 - }; - - struct sunxi_gpio { - unsigned int CFG[4]; // NOSONAR: Intentionally using C style arrays for low level register access - unsigned int DAT; - unsigned int DRV[2]; // NOSONAR: Intentionally using C style arrays for low level register access - unsigned int PULL[2]; // NOSONAR: Intentionally using C style arrays for low level register access - }; - using sunxi_gpio_t = struct sunxi_gpio; - - /* gpio interrupt control */ - struct sunxi_gpio_int { - unsigned int CFG[3]; // NOSONAR: Intentionally using C style arrays for low level register access - unsigned int CTL; - unsigned int STA; - unsigned int DEB; - }; - using sunxi_gpio_int_t = struct sunxi_gpio_int; - - struct sunxi_gpio_reg { - struct sunxi_gpio gpio_bank[9]; // NOSONAR: Intentionally using C style arrays for low level register access - unsigned char res[0xbc]; // NOSONAR: Intentionally using C style arrays for low level register access - struct sunxi_gpio_int gpio_int; - }; - using sunxi_gpio_reg_t = struct sunxi_gpio_reg; - - static const uint32_t PAGE_SIZE = (4 * 1024); - static const uint32_t BLOCK_SIZE = (4 * 1024); - - static const int SETUP_DEVMEM_FAIL = 1; - static const int SETUP_MALLOC_FAIL = 2; - static const int SETUP_MMAP_FAIL = 3; - static const int SETUP_CPUINFO_FAIL = 4; - static const int SETUP_NOT_RPI_FAIL = 5; - static const int INPUT = 1; - static const int OUTPUT = 0; - static const int ALT0 = 4; - static const int HIGH = 1; - static const int LOW = 0; - static const int PUD_OFF = 0; - static const int PUD_DOWN = 1; - static const int PUD_UP = 2; - - static const uint32_t SUNXI_R_GPIO_BASE = 0x01F02000; - static const uint32_t SUNXI_R_GPIO_REG_OFFSET = 0xC00; - static const uint32_t SUNXI_GPIO_BASE = 0x01C20000; - static const uint32_t SUNXI_GPIO_REG_OFFSET = 0x800; - static const uint32_t SUNXI_CFG_OFFSET = 0x00; - static const uint32_t SUNXI_DATA_OFFSET = 0x10; - static const uint32_t SUNXI_PUD_OFFSET = 0x1C; - static const uint32_t SUNXI_BANK_SIZE = 0x24; - - static const uint32_t MAP_SIZE = (4096 * 2); - static const uint32_t MAP_MASK = (MAP_SIZE - 1); - - static const int FSEL_OFFSET = 0; // 0x0000 - static const int SET_OFFSET = 7; // 0x001c / 4 - static const int CLR_OFFSET = 10; // 0x0028 / 4 - static const int PINLEVEL_OFFSET = 13; // 0x0034 / 4 - static const int EVENT_DETECT_OFFSET = 16; // 0x0040 / 4 - static const int RISING_ED_OFFSET = 19; // 0x004c / 4 - static const int FALLING_ED_OFFSET = 22; // 0x0058 / 4 - static const int HIGH_DETECT_OFFSET = 25; // 0x0064 / 4 - static const int LOW_DETECT_OFFSET = 28; // 0x0070 / 4 - static const int PULLUPDN_OFFSET = 37; // 0x0094 / 4 - static const int PULLUPDNCLK_OFFSET = 38; // 0x0098 / 4 - - static const uint32_t TMR_REGISTER_BASE = 0x01C20C00; - static const uint32_t TMR_IRQ_EN_REG = 0x0; // T imer IRQ Enable Register - static const uint32_t TMR_IRQ_STA_REG = 0x4; // Timer Status Register - static const uint32_t TMR0_CTRL_REG = 0x10; // Timer 0 Control Register - static const uint32_t TMR0_INTV_VALUE_REG = 0x14; // Timer 0 Interval Value Register - static const uint32_t TMR0_CUR_VALUE_REG = 0x18; // Timer 0 Current Value Register - static const uint32_t TMR1_CTRL_REG = 0x20; // Timer 1 Control Register - static const uint32_t TMR1_INTV_VALUE_REG = 0x24; // Timer 1 Interval Value Register - static const uint32_t TMR1_CUR_VALUE_REG = 0x28; // Timer 1 Current Value Register - static const uint32_t AVS_CNT_CTL_REG = 0x80; // AVS Control Register - static const uint32_t AVS_CNT0_REG = 0x84; // AVS Counter 0 Register - static const uint32_t AVS_CNT1_REG = 0x88; // AVS Counter 1 Register - static const uint32_t AVS_CNT_DIV_REG = 0x8C; // AVS Divisor Register - static const uint32_t WDOG0_IRQ_EN_REG = 0xA0; // Watchdog 0 IRQ Enable Register - static const uint32_t WDOG0_IRQ_STA_REG = 0xA4; // Watchdog 0 Status Register - static const uint32_t WDOG0_CTRL_REG = 0xB0; // Watchdog 0 Control Register - static const uint32_t WDOG0_CFG_REG = 0xB4; // Watchdog 0 Configuration Register - static const uint32_t WDOG0_MODE_REG = 0xB8; // Watchdog 0 Mode Register -}; \ No newline at end of file diff --git a/cpp/hal/systimer.cpp b/cpp/hal/systimer.cpp index 8ae8635f..c2b4e7b8 100644 --- a/cpp/hal/systimer.cpp +++ b/cpp/hal/systimer.cpp @@ -12,32 +12,25 @@ //--------------------------------------------------------------------------- #include "hal/systimer.h" -#include "hal/systimer_allwinner.h" #include "hal/systimer_raspberry.h" -#include +#include #include "hal/gpiobus.h" #include "hal/sbc_version.h" -#include "shared/log.h" +bool SysTimer::initialized = false; +bool SysTimer::is_raspberry = false; -bool SysTimer::initialized = false; -bool SysTimer::is_allwinnner = false; -bool SysTimer::is_raspberry = false; +using namespace std; -std::unique_ptr SysTimer::systimer_ptr; +unique_ptr SysTimer::systimer_ptr; void SysTimer::Init() { - LOGTRACE("%s", __PRETTY_FUNCTION__) - if (!initialized) { if (SBC_Version::IsRaspberryPi()) { systimer_ptr = make_unique(); is_raspberry = true; - } else if (SBC_Version::IsBananaPi()) { - systimer_ptr = make_unique(); - is_allwinnner = true; } systimer_ptr->Init(); initialized = true; @@ -49,16 +42,19 @@ uint32_t SysTimer::GetTimerLow() { return systimer_ptr->GetTimerLow(); } + // Get system timer high byte uint32_t SysTimer::GetTimerHigh() { return systimer_ptr->GetTimerHigh(); } + // Sleep for N nanoseconds void SysTimer::SleepNsec(uint32_t nsec) { systimer_ptr->SleepNsec(nsec); } + // Sleep for N microseconds void SysTimer::SleepUsec(uint32_t usec) { diff --git a/cpp/hal/systimer_allwinner.cpp b/cpp/hal/systimer_allwinner.cpp deleted file mode 100644 index b698c405..00000000 --- a/cpp/hal/systimer_allwinner.cpp +++ /dev/null @@ -1,155 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Copyright (C) 2022 akuker -// -// [ High resolution timer for the Allwinner series of SoC's] -// -//--------------------------------------------------------------------------- - -#include "hal/systimer_allwinner.h" -#include - -#include "hal/gpiobus.h" - -#include "shared/log.h" - -const std::string SysTimer_AllWinner::dev_mem_filename = "/dev/mem"; - -//--------------------------------------------------------------------------- -// -// Initialize the system timer -// -//--------------------------------------------------------------------------- -void SysTimer_AllWinner::Init() -{ - LOGTRACE("%s", __PRETTY_FUNCTION__) - - int fd; - - if ((fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) { - LOGERROR("I can't open /dev/mem. Are you running as root?") - exit(-1); - } - - hsitimer_regs = - (sun8i_hsitimer_registers *)mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, hs_timer_base_address); - - if (hsitimer_regs == MAP_FAILED) { - LOGERROR("Unable to map high speed timer registers. Are you running as root?") - } - - sysbus_regs = (struct sun8i_sysbus_registers *)mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, - system_bus_base_address); - - if (sysbus_regs == MAP_FAILED) { - LOGERROR("Unable to map system bus registers. Are you running as root?") - } - - enable_hs_timer(); -} - -void SysTimer_AllWinner::enable_hs_timer() -{ - // By default, the HSTimer clock gating is masked. When it is necessary to use - // the HSTimer, its clock gating should be opened in BUS Clock Gating Register 0 - // and then de-assert the software reset in BUS Software Reset Register 0 on the - // CCU module. If it is not needed to use the HSTimer, both the gating bit and - // the software reset bit should be set 0. - - LOGTRACE("%s [Before Enable] CLK GATE: %08X SOFT RST: %08X", __PRETTY_FUNCTION__, sysbus_regs->bus_clk_gating_reg0, - sysbus_regs->bus_soft_rst_reg0) - - sysbus_regs->bus_clk_gating_reg0 = sysbus_regs->bus_clk_gating_reg0 | (1 << BUS_CLK_GATING_REG0_HSTMR); - sysbus_regs->bus_soft_rst_reg0 = sysbus_regs->bus_soft_rst_reg0 | (1 << BUS_SOFT_RST_REG0_HSTMR); - LOGTRACE("%s [After Enable] CLK GATE: %08X SOFT RST: %08X", __PRETTY_FUNCTION__, sysbus_regs->bus_clk_gating_reg0, - sysbus_regs->bus_soft_rst_reg0) - - // Set interval value to the maximum value. (its a 52 bit register) - hsitimer_regs->hs_tmr_intv_hi_reg = (1 << 20) - 1; //(0xFFFFF) - hsitimer_regs->hs_tmr_intv_lo_reg = UINT32_MAX; - - // Select prescale value of 1, continuouse mode - hsitimer_regs->hs_tmr_ctrl_reg = HS_TMR_CLK_PRE_SCALE_1; - - // Set reload bit - hsitimer_regs->hs_tmr_ctrl_reg |= HS_TMR_RELOAD; - - // Enable HSTimer - hsitimer_regs->hs_tmr_ctrl_reg |= HS_TMR_EN; -} - -// TODO: According to the data sheet, we should turn off the HS timer when we're done with it. But, its just going to -// eat up a little extra power if we leave it running. -void SysTimer_AllWinner::disable_hs_timer() -{ - LOGTRACE("%s", __PRETTY_FUNCTION__) - - LOGINFO("[Before Disable] CLK GATE: %08X SOFT RST: %08X", sysbus_regs->bus_clk_gating_reg0, - sysbus_regs->bus_soft_rst_reg0) - - sysbus_regs->bus_clk_gating_reg0 = sysbus_regs->bus_clk_gating_reg0 & ~(1 << BUS_CLK_GATING_REG0_HSTMR); - sysbus_regs->bus_soft_rst_reg0 = sysbus_regs->bus_soft_rst_reg0 & ~(1 << BUS_SOFT_RST_REG0_HSTMR); - - LOGINFO("[After Disable] CLK GATE: %08X SOFT RST: %08X", sysbus_regs->bus_clk_gating_reg0, - sysbus_regs->bus_soft_rst_reg0) -} - -uint32_t SysTimer_AllWinner::GetTimerLow() -{ - // PiSCSI expects the timer to count UP, but the Allwinner HS timer counts - // down. So, we subtract the current timer value from UINT32_MAX - return UINT32_MAX - (hsitimer_regs->hs_tmr_curnt_lo_reg / 200); -} - -uint32_t SysTimer_AllWinner::GetTimerHigh() -{ - return (uint32_t)0; -} - -//--------------------------------------------------------------------------- -// -// Sleep in nanoseconds -// -//--------------------------------------------------------------------------- -void SysTimer_AllWinner::SleepNsec(uint32_t nsec) -{ - // If time is less than one HS timer clock tick, don't do anything - if (nsec < 20) { - return; - } - - // The HS timer receives a 200MHz clock input, which equates to - // one clock tick every 5 ns. - auto clockticks = (uint32_t)std::ceil(nsec / 5); - - uint32_t enter_time = hsitimer_regs->hs_tmr_curnt_lo_reg; - - // TODO: NEED TO HANDLE COUNTER OVERFLOW - LOGTRACE("%s entertime: %08X ns: %d clockticks: %d", __PRETTY_FUNCTION__, enter_time, nsec, clockticks) - while ((enter_time - hsitimer_regs->hs_tmr_curnt_lo_reg) < clockticks) - ; - - return; -} - -//--------------------------------------------------------------------------- -// -// Sleep in microseconds -// -//--------------------------------------------------------------------------- -void SysTimer_AllWinner::SleepUsec(uint32_t usec) -{ - LOGTRACE("%s", __PRETTY_FUNCTION__) - - // If time is 0, don't do anything - if (usec == 0) { - return; - } - - uint32_t enter_time = GetTimerLow(); - while ((GetTimerLow() - enter_time) < usec) - ; -} diff --git a/cpp/hal/systimer_allwinner.h b/cpp/hal/systimer_allwinner.h deleted file mode 100644 index 8378e1ac..00000000 --- a/cpp/hal/systimer_allwinner.h +++ /dev/null @@ -1,106 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Copyright (C) 2022 akuker -// -// [ High resolution timer ] -// -//--------------------------------------------------------------------------- -#pragma once - -#include "systimer.h" -#include -#include - -//=========================================================================== -// -// System timer -// -//=========================================================================== -class SysTimer_AllWinner : public PlatformSpecificTimer -{ - public: - // Default constructor - SysTimer_AllWinner() = default; - // Default destructor - ~SysTimer_AllWinner() override = default; - // Initialization - void Init() override; - // Get system timer low byte - uint32_t GetTimerLow() override; - // Get system timer high byte - uint32_t GetTimerHigh() override; - // Sleep for N nanoseconds - void SleepNsec(uint32_t nsec) override; - // Sleep for N microseconds - void SleepUsec(uint32_t usec) override; - - private: - void enable_hs_timer(); - void disable_hs_timer(); - - static const std::string dev_mem_filename; - - /* Reference: Allwinner H3 Datasheet, section 4.9.3 */ - static const uint32_t hs_timer_base_address = 0x01C60000; - /* Note: Currently the high speed timer is NOT in the armbian - * device tree. If it is ever added, this should be pulled - * from there */ - - using sun8i_hsitimer_registers = struct { - /* 0x00 HS Timer IRQ Enabled Register */ - uint32_t hs_tmr_irq_en_reg; - /* 0x04 HS Timer Status Register */ - uint32_t hs_tmr_irq_stat_reg; - /* 0x08 Unused */ - uint32_t unused_08; - /* 0x0C Unused */ - uint32_t unused_0C; - /* 0x10 HS Timer Control Register */ - uint32_t hs_tmr_ctrl_reg; - /* 0x14 HS Timer Interval Value Low Reg */ - uint32_t hs_tmr_intv_lo_reg; - /* 0x18 HS Timer Interval Value High Register */ - uint32_t hs_tmr_intv_hi_reg; - /* 0x1C HS Timer Current Value Low Register */ - uint32_t hs_tmr_curnt_lo_reg; - /* 0x20 HS Timer Current Value High Register */ - uint32_t hs_tmr_curnt_hi_reg; - }; - - /* Constants for the HS Timer IRQ enable Register (section 4.9.4.1) */ - static const uint32_t HS_TMR_INTERUPT_ENB = (1 << 0); - - /* Constants for the HS Timer Control Register (section 4.9.4.3) */ - static const uint32_t HS_TMR_EN = (1 << 0); - static const uint32_t HS_TMR_RELOAD = (1 << 1); - static const uint32_t HS_TMR_CLK_PRE_SCALE_1 = (0 << 4); - static const uint32_t HS_TMR_CLK_PRE_SCALE_2 = (1 << 4); - static const uint32_t HS_TMR_CLK_PRE_SCALE_4 = (2 << 4); - static const uint32_t HS_TMR_CLK_PRE_SCALE_8 = (3 << 4); - static const uint32_t HS_TMR_CLK_PRE_SCALE_16 = (4 << 4); // NOSONAR This matches the datasheet - static const uint32_t HS_TMR_MODE_SINGLE = (1 << 7); - static const uint32_t HS_TMR_TEST_MODE = (1 << 31); - - volatile sun8i_hsitimer_registers *hsitimer_regs; - - /* Reference: Allwinner H3 Datasheet, section 4.3.4 */ - static const uint32_t system_bus_base_address = 0x01C20000; - - struct sun8i_sysbus_registers { - uint32_t pad_00_5C[(0x60 / sizeof(uint32_t))]; // NOSONAR c-style array used for padding - /* 0x0060 Bus Clock Gating Register 0 */ - uint32_t bus_clk_gating_reg0; - uint32_t pad_64_2C0[((0x2C0 - 0x64) / sizeof(uint32_t))]; // NOSONAR c-style array used for padding - /* 0x2C0 Bus Software Reset Register 0 */ - uint32_t bus_soft_rst_reg0; - }; - - /* Bit associated with the HS Timer */ - static const uint32_t BUS_CLK_GATING_REG0_HSTMR = 19; - static const uint32_t BUS_SOFT_RST_REG0_HSTMR = 19; - - struct sun8i_sysbus_registers *sysbus_regs; -}; diff --git a/cpp/hal/systimer_raspberry.cpp b/cpp/hal/systimer_raspberry.cpp index 6ae647a4..8814ea02 100644 --- a/cpp/hal/systimer_raspberry.cpp +++ b/cpp/hal/systimer_raspberry.cpp @@ -12,6 +12,7 @@ //--------------------------------------------------------------------------- #include "hal/systimer_raspberry.h" +#include #include #include #include @@ -19,14 +20,14 @@ #include "hal/gpiobus.h" #include "hal/sbc_version.h" -#include "shared/log.h" - // System timer address volatile uint32_t *SysTimer_Raspberry::systaddr = nullptr; // ARM timer address volatile uint32_t *SysTimer_Raspberry::armtaddr = nullptr; volatile uint32_t SysTimer_Raspberry::corefreq = 0; +using namespace std; + //--------------------------------------------------------------------------- // // Initialize the system timer @@ -35,19 +36,19 @@ volatile uint32_t SysTimer_Raspberry::corefreq = 0; void SysTimer_Raspberry::Init() { // Get the base address - auto baseaddr = SBC_Version::GetPeripheralAddress(); + const auto baseaddr = SBC_Version::GetPeripheralAddress(); // Open /dev/mem int mem_fd = open("/dev/mem", O_RDWR | O_SYNC); if (mem_fd == -1) { - LOGERROR("Error: Unable to open /dev/mem. Are you running as root?") + spdlog::error("Error: Unable to open /dev/mem. Are you running as root?"); return; } // Map peripheral region memory void *map = mmap(nullptr, 0x1000100, PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, baseaddr); if (map == MAP_FAILED) { - LOGERROR("Error: Unable to map memory") + spdlog::error("Error: Unable to map memory"); close(mem_fd); return; } @@ -64,7 +65,7 @@ void SysTimer_Raspberry::Init() // // Clock id // 0x000000004: CORE - std::array maxclock = {32, 0, 0x00030004, 8, 0, 4, 0, 0}; + const array maxclock = {32, 0, 0x00030004, 8, 0, 4, 0, 0}; // Save the base address systaddr = (uint32_t *)map + SYST_OFFSET / sizeof(uint32_t); @@ -114,7 +115,7 @@ void SysTimer_Raspberry::SleepNsec(uint32_t nsec) } // Calculate the timer difference - uint32_t diff = corefreq * nsec / 1000; + const uint32_t diff = corefreq * nsec / 1000; // Return if the difference in time is too small if (diff == 0) { @@ -122,7 +123,7 @@ void SysTimer_Raspberry::SleepNsec(uint32_t nsec) } // Start - uint32_t start = armtaddr[ARMT_FREERUN]; + const uint32_t start = armtaddr[ARMT_FREERUN]; // Loop until timer has elapsed while ((armtaddr[ARMT_FREERUN] - start) < diff) @@ -141,7 +142,7 @@ void SysTimer_Raspberry::SleepUsec(uint32_t usec) return; } - uint32_t now = GetTimerLow(); + const uint32_t now = GetTimerLow(); while ((GetTimerLow() - now) < usec) ; } diff --git a/cpp/piscsi/command_context.cpp b/cpp/piscsi/command_context.cpp index ce11e6ef..ab4eeac4 100644 --- a/cpp/piscsi/command_context.cpp +++ b/cpp/piscsi/command_context.cpp @@ -3,24 +3,51 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "shared/log.h" -#include "generated/piscsi_interface.pb.h" +#include "shared/piscsi_exceptions.h" +#include "shared/protobuf_util.h" #include "command_context.h" +#include #include using namespace std; using namespace piscsi_interface; +using namespace protobuf_util; -void CommandContext::Cleanup() +bool CommandContext::ReadCommand() { - if (fd != -1) { - close(fd); - fd = -1; + // Read magic string + array magic; + if (const size_t bytes_read = ReadBytes(fd, magic); bytes_read) { + if (bytes_read != magic.size() || memcmp(magic.data(), "RASCSI", magic.size())) { + throw io_exception("Invalid magic"); + } + + // Fetch the command + DeserializeMessage(fd, command); + + return true; } + + return false; +} + +void CommandContext::WriteResult(const PbResult& result) const +{ + // The descriptor is -1 when devices are not attached via the remote interface but by the piscsi tool + if (fd != -1) { + SerializeMessage(fd, result); + } +} + +bool CommandContext::WriteSuccessResult(PbResult& result) const +{ + result.set_status(true); + WriteResult(result); + return true; } bool CommandContext::ReturnLocalizedError(LocalizationKey key, const string& arg1, const string& arg2, @@ -33,7 +60,13 @@ bool CommandContext::ReturnLocalizedError(LocalizationKey key, PbErrorCode error const string& arg2, const string& arg3) const { // For the logfile always use English - LOGERROR("%s", localizer.Localize(key, "en", arg1, arg2, arg3).c_str()) + // Do not log unknown operations as an error for backward/foward compatibility with old/new clients + if (error_code == PbErrorCode::UNKNOWN_OPERATION) { + spdlog::trace(localizer.Localize(key, "en", arg1, arg2, arg3)); + } + else { + spdlog::error(localizer.Localize(key, "en", arg1, arg2, arg3)); + } return ReturnStatus(false, localizer.Localize(key, locale, arg1, arg2, arg3), error_code, false); } @@ -42,17 +75,12 @@ bool CommandContext::ReturnStatus(bool status, const string& msg, PbErrorCode er { // Do not log twice if logging has already been done in the localized error handling above if (log && !status && !msg.empty()) { - LOGERROR("%s", msg.c_str()) + spdlog::error(msg); } if (fd == -1) { if (!msg.empty()) { - if (status) { - cerr << "Error: " << msg << endl; - } - else { - cout << msg << endl; - } + cerr << "Error: " << msg << endl; } } else { @@ -60,8 +88,18 @@ bool CommandContext::ReturnStatus(bool status, const string& msg, PbErrorCode er result.set_status(status); result.set_error_code(error_code); result.set_msg(msg); - serializer.SerializeMessage(fd, result); + WriteResult(result); } return status; } + +bool CommandContext::ReturnSuccessStatus() const +{ + return ReturnStatus(true, "", PbErrorCode::NO_ERROR_CODE, true); +} + +bool CommandContext::ReturnErrorStatus(const string& msg) const +{ + return ReturnStatus(false, msg, PbErrorCode::NO_ERROR_CODE, true); +} diff --git a/cpp/piscsi/command_context.h b/cpp/piscsi/command_context.h index 3fcc4e20..25c4dbb8 100644 --- a/cpp/piscsi/command_context.h +++ b/cpp/piscsi/command_context.h @@ -3,15 +3,14 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once -#include "generated/piscsi_interface.pb.h" #include "localizer.h" -#include "shared/protobuf_serializer.h" +#include "generated/piscsi_interface.pb.h" #include using namespace std; @@ -19,27 +18,36 @@ using namespace piscsi_interface; class CommandContext { - const ProtobufSerializer serializer; - - const Localizer localizer; - - string locale; - - int fd; public: - CommandContext(const std::string& s = "", int f = -1) : locale(s), fd(f) {} + CommandContext(const PbCommand& cmd, string_view f, string_view l) : command(cmd), default_folder(f), locale(l) {} + explicit CommandContext(int f) : fd(f) {} ~CommandContext() = default; - void Cleanup(); - - const ProtobufSerializer& GetSerializer() const { return serializer; } - int GetFd() const { return fd; } - void SetFd(int f) { fd = f; } - bool IsValid() const { return fd != -1; } + string GetDefaultFolder() const { return default_folder; } + void SetDefaultFolder(string_view f) { default_folder = f; } + bool ReadCommand(); + void WriteResult(const PbResult&) const; + bool WriteSuccessResult(PbResult&) const; + const PbCommand& GetCommand() const { return command; } bool ReturnLocalizedError(LocalizationKey, const string& = "", const string& = "", const string& = "") const; bool ReturnLocalizedError(LocalizationKey, PbErrorCode, const string& = "", const string& = "", const string& = "") const; - bool ReturnStatus(bool = true, const string& = "", PbErrorCode = PbErrorCode::NO_ERROR_CODE, bool = true) const; + bool ReturnSuccessStatus() const; + bool ReturnErrorStatus(const string&) const; + +private: + + bool ReturnStatus(bool, const string&, PbErrorCode, bool) const; + + const Localizer localizer; + + PbCommand command; + + string default_folder; + + string locale; + + int fd = -1; }; diff --git a/cpp/piscsi/localizer.cpp b/cpp/piscsi/localizer.cpp index bcb17297..f18a2c36 100644 --- a/cpp/piscsi/localizer.cpp +++ b/cpp/piscsi/localizer.cpp @@ -3,15 +3,13 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "localizer.h" #include -#include #include -#include using namespace std; @@ -24,19 +22,19 @@ Localizer::Localizer() Add(LocalizationKey::ERROR_AUTHENTICATION, "es", "Fallo de autentificación"); Add(LocalizationKey::ERROR_AUTHENTICATION, "zh", "认证失败"); - Add(LocalizationKey::ERROR_OPERATION, "en", "Unknown operation"); - Add(LocalizationKey::ERROR_OPERATION, "de", "Unbekannte Operation"); - Add(LocalizationKey::ERROR_OPERATION, "sv", "Okänd operation"); - Add(LocalizationKey::ERROR_OPERATION, "fr", "Opération inconnue"); - Add(LocalizationKey::ERROR_OPERATION, "es", "Operación desconocida"); - Add(LocalizationKey::ERROR_OPERATION, "zh", "未知操作"); + Add(LocalizationKey::ERROR_OPERATION, "en", "Unknown operation: %1"); + Add(LocalizationKey::ERROR_OPERATION, "de", "Unbekannte Operation: %1"); + Add(LocalizationKey::ERROR_OPERATION, "sv", "Okänd operation: %1"); + Add(LocalizationKey::ERROR_OPERATION, "fr", "Opération inconnue: %1"); + Add(LocalizationKey::ERROR_OPERATION, "es", "Operación desconocida: %1"); + Add(LocalizationKey::ERROR_OPERATION, "zh", "未知操作: %1"); - Add(LocalizationKey::ERROR_LOG_LEVEL, "en", "Invalid log level %1"); - Add(LocalizationKey::ERROR_LOG_LEVEL, "de", "Ungültiger Log-Level %1"); - Add(LocalizationKey::ERROR_LOG_LEVEL, "sv", "Ogiltig loggnivå %1"); - Add(LocalizationKey::ERROR_LOG_LEVEL, "fr", "Niveau de journalisation invalide %1"); - Add(LocalizationKey::ERROR_LOG_LEVEL, "es", "Nivel de registro %1 no válido"); - Add(LocalizationKey::ERROR_LOG_LEVEL, "zh", "无效的日志级别 %1"); + Add(LocalizationKey::ERROR_LOG_LEVEL, "en", "Invalid log level '%1'"); + Add(LocalizationKey::ERROR_LOG_LEVEL, "de", "Ungültiger Log-Level '%1'"); + Add(LocalizationKey::ERROR_LOG_LEVEL, "sv", "Ogiltig loggnivå '%1'"); + Add(LocalizationKey::ERROR_LOG_LEVEL, "fr", "Niveau de journalisation invalide '%1'"); + Add(LocalizationKey::ERROR_LOG_LEVEL, "es", "Nivel de registro '%1' no válido"); + Add(LocalizationKey::ERROR_LOG_LEVEL, "zh", "无效的日志级别 '%1'"); Add(LocalizationKey::ERROR_MISSING_DEVICE_ID, "en", "Missing device ID"); Add(LocalizationKey::ERROR_MISSING_DEVICE_ID, "de", "Fehlende Geräte-ID"); @@ -51,26 +49,28 @@ Localizer::Localizer() Add(LocalizationKey::ERROR_MISSING_FILENAME, "fr", "Nom de fichier manquant"); Add(LocalizationKey::ERROR_MISSING_FILENAME, "es", "Falta el nombre del archivo"); Add(LocalizationKey::ERROR_MISSING_FILENAME, "zh", "缺少文件名"); - + Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "en", "Device type %1 requires a filename"); Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "de", "Gerätetyp %1 erfordert einen Dateinamen"); Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "sv", "Enhetstypen %1 kräver ett filnamn"); + Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "fr", "Périphérique de type %1 à besoin d'un nom de fichier"); Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "es", "El tipo de dispositivo %1 requiere un nombre de archivo"); Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "zh", "设备类型 %1 需要一个文件名"); - - Add(LocalizationKey::ERROR_IMAGE_IN_USE, "en", "Image file '%1' is already being used by ID %2, unit %3"); - Add(LocalizationKey::ERROR_IMAGE_IN_USE, "de", "Image-Datei '%1' wird bereits von ID %2, Einheit %3 benutzt"); - Add(LocalizationKey::ERROR_IMAGE_IN_USE, "sv", "Skivbildsfilen '%1' används redan av id %2, enhetsnummer %3"); - Add(LocalizationKey::ERROR_IMAGE_IN_USE, "fr", "Le fichier d'image '%1' est déjà utilisé par l'ID %2, unité %3"); - Add(LocalizationKey::ERROR_IMAGE_IN_USE, "es", "El archivo de imagen '%1' ya está siendo utilizado por el ID %2, unidad %3"); - Add(LocalizationKey::ERROR_IMAGE_IN_USE, "zh", "图像文件%1已被 ID %2、单元 %3 使用"); - + + Add(LocalizationKey::ERROR_IMAGE_IN_USE, "en", "Image file '%1' is already being used by device %2"); + Add(LocalizationKey::ERROR_IMAGE_IN_USE, "de", "Image-Datei '%1' wird bereits von Gerät %2 benutzt"); + Add(LocalizationKey::ERROR_IMAGE_IN_USE, "sv", "Skivbildsfilen '%1' används redan av nhet %2"); + Add(LocalizationKey::ERROR_IMAGE_IN_USE, "fr", "Le fichier d'image '%1' est déjà utilisé par périphérique %2"); + Add(LocalizationKey::ERROR_IMAGE_IN_USE, "es", "El archivo de imagen '%1' ya está siendo utilizado por dispositivo %2"); + Add(LocalizationKey::ERROR_IMAGE_IN_USE, "zh", "图像文件%1已被 ID %2"); + Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "en", "Can't create image file info for '%1'"); Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "de", "Image-Datei-Information für '%1' kann nicht erzeugt werden"); Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "sv", "Kunde ej skapa skivbildsfilsinfo för '%1'"); + Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "fr", "Ne peux pas créer les informations du fichier image '%1'"); Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "es", "No se puede crear información de archivo de imagen para '%1'"); Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "zh", "无法为'%1'创建图像文件信息"); - + Add(LocalizationKey::ERROR_RESERVED_ID, "en", "Device ID %1 is reserved"); Add(LocalizationKey::ERROR_RESERVED_ID, "de", "Geräte-ID %1 ist reserviert"); Add(LocalizationKey::ERROR_RESERVED_ID, "sv", "Enhets-id %1 är reserverat"); @@ -84,21 +84,21 @@ Localizer::Localizer() Add(LocalizationKey::ERROR_NON_EXISTING_DEVICE, "fr", "Commande pour ID %1 non-existant"); Add(LocalizationKey::ERROR_NON_EXISTING_DEVICE, "es", "Comando para ID %1 no existente"); Add(LocalizationKey::ERROR_NON_EXISTING_DEVICE, "zh", "不存在的 ID %1 的指令"); - + Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "en", "Command for non-existing ID %1, unit %2"); Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "de", "Kommando für nicht existente ID %1, Einheit %2"); Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "sv", "Kommando för id %1, enhetsnummer %2 som ej existerar"); Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "fr", "Command pour ID %1, unité %2 non-existant"); Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "es", "Comando para ID %1 inexistente, unidad %2"); Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "zh", "不存在的 ID %1, 单元 %2 的指令"); - + Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "en", "Unknown device type %1"); Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "de", "Unbekannter Gerätetyp %1"); Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "sv", "Obekant enhetstyp: %1"); Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "fr", "Type de périphérique inconnu %1"); Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "es", "Tipo de dispositivo desconocido %1"); Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "zh", "未知设备类型 %1"); - + Add(LocalizationKey::ERROR_MISSING_DEVICE_TYPE, "en", "Device type required for unknown extension of file '%1'"); Add(LocalizationKey::ERROR_MISSING_DEVICE_TYPE, "de", "Gerätetyp erforderlich für unbekannte Extension der Datei '%1'"); Add(LocalizationKey::ERROR_MISSING_DEVICE_TYPE, "sv", "Man måste ange enhetstyp för obekant filändelse '%1'"); @@ -116,6 +116,7 @@ Localizer::Localizer() Add(LocalizationKey::ERROR_DETACH, "en", "Couldn't detach device"); Add(LocalizationKey::ERROR_DETACH, "de", "Geräte konnte nicht entfernt werden"); Add(LocalizationKey::ERROR_DETACH, "sv", "Kunde ej koppla ifrån enheten"); + Add(LocalizationKey::ERROR_DETACH, "fr", "Impossible de détacher le périphérique"); Add(LocalizationKey::ERROR_DETACH, "es", "No se ha podido desconectar el dispositivo"); Add(LocalizationKey::ERROR_DETACH, "zh", "无法卸载设备"); @@ -178,54 +179,63 @@ Localizer::Localizer() Add(LocalizationKey::ERROR_SCSI_CONTROLLER, "en", "Couldn't create SCSI controller"); Add(LocalizationKey::ERROR_SCSI_CONTROLLER, "de", "SCSI-Controller konnte nicht erzeugt werden"); Add(LocalizationKey::ERROR_SCSI_CONTROLLER, "sv", "Kunde ej skapa SCSI-gränssnitt"); + Add(LocalizationKey::ERROR_SCSI_CONTROLLER, "fr", "Impossible de créer le contrôleur SCSI"); Add(LocalizationKey::ERROR_SCSI_CONTROLLER, "es", "No se ha podido crear el controlador SCSI"); Add(LocalizationKey::ERROR_SCSI_CONTROLLER, "zh", "无法创建 SCSI 控制器"); Add(LocalizationKey::ERROR_INVALID_ID, "en", "Invalid device ID %1 (0-%2)"); Add(LocalizationKey::ERROR_INVALID_ID, "de", "Ungültige Geräte-ID %1 (0-%2)"); Add(LocalizationKey::ERROR_INVALID_ID, "sv", "Ogiltigt enhets-id %1 (0-%2)"); + Add(LocalizationKey::ERROR_INVALID_ID, "fr", "ID de périphérique invalide %1 (0-%2)"); Add(LocalizationKey::ERROR_INVALID_ID, "es", "ID de dispositivo inválido %1 (0-%2)"); Add(LocalizationKey::ERROR_INVALID_ID, "zh", "无效的设备 ID %1 (0-%2)"); Add(LocalizationKey::ERROR_INVALID_LUN, "en", "Invalid LUN %1 (0-%2)"); Add(LocalizationKey::ERROR_INVALID_LUN, "de", "Ungültige LUN %1 (0-%2)"); Add(LocalizationKey::ERROR_INVALID_LUN, "sv", "Ogiltigt enhetsnummer %1 (0-%2)"); + Add(LocalizationKey::ERROR_INVALID_LUN, "fr", "LUN invalide %1 (0-%2)"); Add(LocalizationKey::ERROR_INVALID_LUN, "es", "LUN invalido %1 (0-%2)"); Add(LocalizationKey::ERROR_INVALID_LUN, "zh", "无效的 LUN %1 (0-%2)"); Add(LocalizationKey::ERROR_LUN0, "en", "LUN 0 cannot be detached as long as there is still another LUN"); Add(LocalizationKey::ERROR_LUN0, "de", "LUN 0 kann nicht entfernt werden, solange noch eine andere LUN existiert"); Add(LocalizationKey::ERROR_LUN0, "sv", "Enhetsnummer 0 kan ej bli frånkopplat så länge som andra enhetsnummer är anslutna"); + Add(LocalizationKey::ERROR_LUN0, "fr", "LUN 0 ne peux pas être détaché tant qu'il y'a un autre LUN"); Add(LocalizationKey::ERROR_LUN0, "es", "El LUN 0 no se puede desconectar mientras haya otro LUN"); Add(LocalizationKey::ERROR_LUN0, "zh", "LUN 0 无法卸载,因为当前仍有另一个 LUN。"); - Add(LocalizationKey::ERROR_INITIALIZATION, "en", "Initialization of %1 device, ID %2, LUN %3 failed"); - Add(LocalizationKey::ERROR_INITIALIZATION, "de", "Initialisierung von %1-Gerät, ID %2, LUN %3 fehlgeschlagen"); - Add(LocalizationKey::ERROR_INITIALIZATION, "sv", "Kunde ej initialisera enheten %1 med id %2 och enhetsnummer %3"); - Add(LocalizationKey::ERROR_INITIALIZATION, "es", "La inicialización del dispositivo %1, ID %2, LUN %3 falló"); - Add(LocalizationKey::ERROR_INITIALIZATION, "zh", "%1 设备、ID %2、LUN %3 的初始化失败"); + Add(LocalizationKey::ERROR_INITIALIZATION, "en", "Initialization of %1 failed"); + Add(LocalizationKey::ERROR_INITIALIZATION, "de", "Initialisierung von %1 fehlgeschlagen"); + Add(LocalizationKey::ERROR_INITIALIZATION, "sv", "Kunde ej initialisera %1 "); + Add(LocalizationKey::ERROR_INITIALIZATION, "fr", "Echec de l'initialisation de %1"); + Add(LocalizationKey::ERROR_INITIALIZATION, "es", "La inicialización del %1 falló"); + Add(LocalizationKey::ERROR_INITIALIZATION, "zh", "%1 的初始化失败"); Add(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, "en", "%1 operation denied, %2 isn't stoppable"); Add(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, "de", "%1-Operation verweigert, %2 ist nicht stopbar"); Add(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, "sv", "Operationen %1 nekades för att %2 inte kan stoppas"); + Add(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, "fr", "Opération %1 refusée, %2 ne peut être stoppé"); Add(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, "es", "%1 operación denegada, %2 no se puede parar"); Add(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, "zh", "%1 操作被拒绝,%2 不可停止"); Add(LocalizationKey::ERROR_OPERATION_DENIED_REMOVABLE, "en", "%1 operation denied, %2 isn't removable"); Add(LocalizationKey::ERROR_OPERATION_DENIED_REMOVABLE, "de", "%1-Operation verweigert, %2 ist nicht wechselbar"); Add(LocalizationKey::ERROR_OPERATION_DENIED_REMOVABLE, "sv", "Operationen %1 nekades för att %2 inte är uttagbar(t)"); + Add(LocalizationKey::ERROR_OPERATION_DENIED_REMOVABLE, "fr", "Opération %1 refusée, %2 n'est pas détachable"); Add(LocalizationKey::ERROR_OPERATION_DENIED_REMOVABLE, "es", "%1 operación denegada, %2 no es removible"); Add(LocalizationKey::ERROR_OPERATION_DENIED_REMOVABLE, "zh", "%1 操作被拒绝,%2 不可移除"); Add(LocalizationKey::ERROR_OPERATION_DENIED_PROTECTABLE, "en", "%1 operation denied, %2 isn't protectable"); Add(LocalizationKey::ERROR_OPERATION_DENIED_PROTECTABLE, "de", "%1-Operation verweigert, %2 ist nicht schützbar"); Add(LocalizationKey::ERROR_OPERATION_DENIED_PROTECTABLE, "sv", "Operationen %1 nekades för att %2 inte är skyddbar(t)"); + Add(LocalizationKey::ERROR_OPERATION_DENIED_PROTECTABLE, "fr", "Opération %1 refusée, %2 n'est pas protégeable"); Add(LocalizationKey::ERROR_OPERATION_DENIED_PROTECTABLE, "es", "%1 operación denegada, %2 no es protegible"); Add(LocalizationKey::ERROR_OPERATION_DENIED_PROTECTABLE, "zh", "%1 操作被拒绝,%2 不可保护"); Add(LocalizationKey::ERROR_OPERATION_DENIED_READY, "en", "%1 operation denied, %2 isn't ready"); Add(LocalizationKey::ERROR_OPERATION_DENIED_READY, "de", "%1-Operation verweigert, %2 ist nicht bereit"); Add(LocalizationKey::ERROR_OPERATION_DENIED_READY, "sv", "Operationen %1 nekades för att %2 inte är redo"); + Add(LocalizationKey::ERROR_OPERATION_DENIED_READY, "fr", "Opération %1 refusée, %2 n'est pas prêt"); Add(LocalizationKey::ERROR_OPERATION_DENIED_READY, "es", "%1 operación denegada, %2 no está listo"); Add(LocalizationKey::ERROR_OPERATION_DENIED_READY, "zh", "%1 操作被拒绝,%2 还没有准备好"); } @@ -235,17 +245,16 @@ void Localizer::Add(LocalizationKey key, const string& locale, string_view value // Safeguards against empty messages, duplicate entries and unsupported locales assert(locale.size()); assert(value.size()); - assert(supported_languages.find(locale) != supported_languages.end()); + assert(supported_languages.contains(locale)); assert(localized_messages[locale][key].empty()); - localized_messages[locale][key] = value; } string Localizer::Localize(LocalizationKey key, const string& locale, const string& arg1, const string& arg2, const string &arg3) const { - string locale_lower = locale; - transform(locale_lower.begin(), locale_lower.end(), locale_lower.begin(), ::tolower); + string locale_lower; + ranges::transform(locale, back_inserter(locale_lower), ::tolower); auto it = localized_messages.find(locale_lower); if (it == localized_messages.end()) { @@ -268,9 +277,9 @@ string Localizer::Localize(LocalizationKey key, const string& locale, const stri } string message = m->second; - message = regex_replace(message, regex("%1"), arg1); - message = regex_replace(message, regex("%2"), arg2); - message = regex_replace(message, regex("%3"), arg3); + message = regex_replace(message, regex1, arg1); + message = regex_replace(message, regex2, arg2); + message = regex_replace(message, regex3, arg3); return message; } diff --git a/cpp/piscsi/localizer.h b/cpp/piscsi/localizer.h index c39f4fdb..e1153ec8 100644 --- a/cpp/piscsi/localizer.h +++ b/cpp/piscsi/localizer.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // // Message localization support. Currently only for messages with up to 3 string parameters. // @@ -11,9 +11,11 @@ #pragma once +#include "shared/piscsi_util.h" #include #include #include +#include using namespace std; @@ -64,8 +66,12 @@ public: private: void Add(LocalizationKey, const string&, string_view); - unordered_map> localized_messages; + unordered_map, piscsi_util::StringHash, equal_to<>> localized_messages; // Supported locales, always lower case - unordered_set supported_languages = { "en", "de", "sv", "fr", "es", "zh" }; + unordered_set> supported_languages = { "en", "de", "sv", "fr", "es", "zh" }; + + const regex regex1 = regex("%1"); + const regex regex2 = regex("%2"); + const regex regex3 = regex("%3"); }; diff --git a/cpp/piscsi.cpp b/cpp/piscsi/piscsi.cpp similarity index 80% rename from cpp/piscsi.cpp rename to cpp/piscsi/piscsi.cpp index ab8a674c..9b632aec 100644 --- a/cpp/piscsi.cpp +++ b/cpp/piscsi/piscsi.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -13,7 +13,7 @@ using namespace std; int main(int argc, char *argv[]) { - const vector args(argv, argv + argc); + vector args(argv, argv + argc); return Piscsi().run(args); } diff --git a/cpp/piscsi/piscsi_core.cpp b/cpp/piscsi/piscsi_core.cpp index ff6ecc18..553e2297 100644 --- a/cpp/piscsi/piscsi_core.cpp +++ b/cpp/piscsi/piscsi_core.cpp @@ -1,39 +1,35 @@ //--------------------------------------------------------------------------- // -// SCSI Target Emulator PiSCSI -// for Raspberry Pi +// SCSI Target Emulator PiSCSI +// for Raspberry Pi // -// Powered by XM6 TypeG Technology. -// Copyright (C) 2016-2020 GIMONS -// Copyright (C) 2020-2023 Contributors to the PiSCSI project +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2020 GIMONS +// Copyright (C) 2020-2023 Contributors to the PiSCSI project +// Copyright (C) 2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "shared/config.h" -#include "shared/log.h" #include "shared/piscsi_util.h" -#include "shared/protobuf_serializer.h" #include "shared/protobuf_util.h" #include "shared/piscsi_exceptions.h" #include "shared/piscsi_version.h" -#include "controllers/controller_manager.h" #include "controllers/scsi_controller.h" +#include "devices/device_logger.h" #include "devices/device_factory.h" #include "devices/storage_device.h" #include "hal/gpiobus_factory.h" #include "hal/gpiobus.h" #include "hal/systimer.h" -#include "piscsi/piscsi_executor.h" #include "piscsi/piscsi_core.h" -#include "spdlog/sinks/stdout_color_sinks.h" +#include #include #include -#include #include #include #include -#include -#include +#include using namespace std; using namespace filesystem; @@ -43,15 +39,15 @@ using namespace piscsi_util; using namespace protobuf_util; using namespace scsi_defs; -void Piscsi::Banner(const vector& args) const +void Piscsi::Banner(span args) const { cout << piscsi_util::Banner("(Backend Service)"); cout << "Connection type: " << CONNECT_DESC << '\n' << flush; if ((args.size() > 1 && strcmp(args[1], "-h") == 0) || (args.size() > 1 && strcmp(args[1], "--help") == 0)){ cout << "\nUsage: " << args[0] << " [-idID[:LUN] FILE] ...\n\n"; - cout << " ID is SCSI device ID (0-7).\n"; - cout << " LUN is the optional logical unit (0-31).\n"; + cout << " ID is SCSI device ID (0-" << (ControllerManager::GetScsiIdMax() - 1) << ").\n"; + cout << " LUN is the optional logical unit (0-" << (ControllerManager::GetScsiLunMax() - 1) <<").\n"; cout << " FILE is a disk image file, \"daynaport\", \"bridge\", \"printer\" or \"services\".\n\n"; cout << " Image type is detected based on file extension if no explicit type is specified.\n"; cout << " hd1 : SCSI-1 HD image (Non-removable generic SCSI-1 HD image)\n"; @@ -69,59 +65,62 @@ void Piscsi::Banner(const vector& args) const } } -bool Piscsi::InitBus() const +bool Piscsi::InitBus() { bus = GPIOBUS_Factory::Create(BUS::mode_e::TARGET); if (bus == nullptr) { return false; } - auto b = bus; - controller_manager = make_shared(*b); - auto c = controller_manager; - executor = make_shared(piscsi_image, *c); + executor = make_unique(*bus, controller_manager); return true; } -void Piscsi::Cleanup() +void Piscsi::CleanUp() { - executor->DetachAll(); - - service.Cleanup(); - - bus->Cleanup(); -} - -void Piscsi::ReadAccessToken(const string& filename) const -{ - struct stat st; - if (stat(filename.c_str(), &st) || !S_ISREG(st.st_mode)) { - throw parser_exception("Can't access token file '" + filename + "'"); + if (service.IsRunning()) { + service.Stop(); } - if (st.st_uid || st.st_gid) { - throw parser_exception("Access token file '" + filename + "' must be owned by root"); + executor->DetachAll(); + + // TODO Check why there are rare cases where bus is NULL on a remote interface shutdown + // even though it is never set to NULL anywhere + assert(bus); + if (bus) { + bus->Cleanup(); + } +} + +void Piscsi::ReadAccessToken(const path& filename) +{ + if (error_code error; !is_regular_file(filename, error)) { + throw parser_exception("Access token file '" + filename.string() + "' must be a regular file"); + } + + if (struct stat st; stat(filename.c_str(), &st) || st.st_uid || st.st_gid) { + throw parser_exception("Access token file '" + filename.string() + "' must be owned by root"); } if (const auto perms = filesystem::status(filename).permissions(); (perms & perms::group_read) != perms::none || (perms & perms::others_read) != perms::none || (perms & perms::group_write) != perms::none || (perms & perms::others_write) != perms::none) { - throw parser_exception("Access token file '" + filename + "' must be readable by root only"); + throw parser_exception("Access token file '" + filename.string() + "' must be readable by root only"); } ifstream token_file(filename); if (token_file.fail()) { - throw parser_exception("Can't open access token file '" + filename + "'"); + throw parser_exception("Can't open access token file '" + filename.string() + "'"); } getline(token_file, access_token); if (token_file.fail()) { - throw parser_exception("Can't read access token file '" + filename + "'"); + throw parser_exception("Can't read access token file '" + filename.string() + "'"); } if (access_token.empty()) { - throw parser_exception("Access token file '" + filename + "' must not be empty"); + throw parser_exception("Access token file '" + filename.string() + "' must not be empty"); } } @@ -131,66 +130,74 @@ void Piscsi::LogDevices(string_view devices) const string line; while (getline(ss, line, '\n')) { - LOGINFO("%s", line.c_str()) + spdlog::info(line); } } -PbDeviceType Piscsi::ParseDeviceType(const string& value) const -{ - string t = value; - PbDeviceType type; - transform(t.begin(), t.end(), t.begin(), ::toupper); - if (!PbDeviceType_Parse(t, &type)) { - throw parser_exception("Illegal device type '" + value + "'"); - } - - return type; -} - void Piscsi::TerminationHandler(int) { - Cleanup(); + instance->CleanUp(); // Process will terminate automatically } -Piscsi::optargs_type Piscsi::ParseArguments(const vector& args, int& port) const +string Piscsi::ParseArguments(span args, PbCommand& command, int& port, string& reserved_ids) { - optargs_type optargs; + string log_level = "info"; + PbDeviceType type = UNDEFINED; int block_size = 0; string name; + string id_and_lun; + + string locale = GetLocale(); + + // Avoid duplicate messages while parsing + set_level(level::off); opterr = 1; int opt; while ((opt = getopt(static_cast(args.size()), args.data(), "-Iib:d:n:p:r:t:z:D:F:L:P:R:C:v")) != -1) { switch (opt) { - // The following options can not be processed until AFTER - // the 'bus' object is created and configured + // The two options below are kind of a compound option with two letters case 'i': case 'I': + continue; + case 'd': case 'D': - case 'R': - case 'n': - case 'r': - case 't': - case 'F': - case 'z': - { - const string optarg_str = optarg == nullptr ? "" : optarg; - optargs.emplace_back(opt, optarg_str); + id_and_lun = optarg; continue; - } - case 'b': { + case 'b': if (!GetAsUnsignedInt(optarg, block_size)) { throw parser_exception("Invalid block size " + string(optarg)); } continue; - } + + case 'z': + locale = optarg; + continue; + + case 'F': + if (const string error = piscsi_image.SetDefaultFolder(optarg); !error.empty()) { + throw parser_exception(error); + } + continue; case 'L': - current_log_level = optarg; + log_level = optarg; + continue; + + case 'R': + int depth; + if (!GetAsUnsignedInt(optarg, depth)) { + throw parser_exception("Invalid image file scan depth " + string(optarg)); + } + piscsi_image.SetDepth(depth); + continue; + + case 'n': + name = optarg; continue; case 'p': @@ -203,86 +210,12 @@ Piscsi::optargs_type Piscsi::ParseArguments(const vector& args, int& por ReadAccessToken(optarg); continue; - case 'v': - cout << piscsi_get_version_string() << endl; - exit(0); - - case 1: - { - // Encountered filename - const string optarg_str = (optarg == nullptr) ? "" : string(optarg); - optargs.emplace_back(opt, optarg_str); - continue; - } - - default: - throw parser_exception("Parser error"); - } - - if (optopt) { - throw parser_exception("Praser error"); - } - } - - return optargs; -} - -void Piscsi::CreateInitialDevices(const optargs_type& optargs) const -{ - PbCommand command; - PbDeviceType type = UNDEFINED; - int block_size = 0; - string name; - string log_level; - string id_and_lun; - - const char *locale = setlocale(LC_MESSAGES, ""); - if (locale == nullptr || !strcmp(locale, "C")) { - locale = "en"; - } - - opterr = 1; - for (const auto& [option, value] : optargs) { - switch (option) { - case 'i': - case 'I': - continue; - - case 'd': - case 'D': - id_and_lun = value; - continue; - - case 'z': - locale = value.c_str(); - continue; - - case 'F': - if (const string error = piscsi_image.SetDefaultFolder(value); !error.empty()) { - throw parser_exception(error); - } - continue; - - case 'R': - int depth; - if (!GetAsUnsignedInt(value, depth)) { - throw parser_exception("Invalid image file scan depth " + value); - } - piscsi_image.SetDepth(depth); - continue; - - case 'n': - name = value; - continue; - case 'r': - if (const string error = executor->SetReservedIds(value); !error.empty()) { - throw parser_exception(error); - } + reserved_ids = optarg; continue; case 't': - type = ParseDeviceType(value); + type = ParseDeviceType(optarg); continue; case 1: @@ -293,10 +226,16 @@ void Piscsi::CreateInitialDevices(const optargs_type& optargs) const throw parser_exception("Parser error"); } - PbDeviceDefinition *device = command.add_devices(); + if (optopt) { + throw parser_exception("Parser error"); + } + + // Set up the device data + + auto device = command.add_devices(); if (!id_and_lun.empty()) { - if (const string error = SetIdAndLun(*device, id_and_lun, ScsiController::LUN_MAX); !error.empty()) { + if (const string error = SetIdAndLun(*device, id_and_lun); !error.empty()) { throw parser_exception(error); } } @@ -304,7 +243,7 @@ void Piscsi::CreateInitialDevices(const optargs_type& optargs) const device->set_type(type); device->set_block_size(block_size); - ParseParameters(*device, value); + ParseParameters(*device, optarg); SetProductData(*device, name); @@ -314,182 +253,248 @@ void Piscsi::CreateInitialDevices(const optargs_type& optargs) const id_and_lun = ""; } - // Attach all specified devices - command.set_operation(ATTACH); - - if (CommandContext context(locale); !executor->ProcessCmd(context, command)) { - throw parser_exception("Can't execute " + PbOperation_Name(command.operation())); + if (!SetLogLevel(log_level)) { + throw parser_exception("Invalid log level '" + log_level + "'"); } - // Display and log the device list - PbServerInfo server_info; - piscsi_response.GetDevices(controller_manager->GetAllDevices(), server_info, piscsi_image.GetDefaultFolder()); - const list& devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() }; - const string device_list = ListDevices(devices); - LogDevices(device_list); - cout << device_list << flush; + return locale; } -bool Piscsi::ExecuteCommand(const CommandContext& context, const PbCommand& command) +PbDeviceType Piscsi::ParseDeviceType(const string& value) { + string t; + ranges::transform(value, back_inserter(t), ::toupper); + if (PbDeviceType type; PbDeviceType_Parse(t, &type)) { + return type; + } + + throw parser_exception("Illegal device type '" + value + "'"); +} + +bool Piscsi::SetLogLevel(const string& log_level) const +{ + int id = -1; + int lun = -1; + string level = log_level; + + if (const auto& components = Split(log_level, COMPONENT_SEPARATOR, 2); !components.empty()) { + level = components[0]; + + if (components.size() > 1) { + if (const string error = ProcessId(components[1], id, lun); !error.empty()) { + spdlog::warn("Error setting log level: " + error); + return false; + } + } + } + + const level::level_enum l = level::from_str(level); + // Compensate for spdlog using 'off' for unknown levels + if (to_string_view(l) != level) { + spdlog::warn("Invalid log level '" + level + "'"); + return false; + } + + set_level(l); + DeviceLogger::SetLogIdAndLun(id, lun); + + if (id != -1) { + if (lun == -1) { + spdlog::info("Set log level for device " + to_string(id) + " to '" + level + "'"); + } + else { + spdlog::info("Set log level for device " + to_string(id) + ":" + to_string(lun) + " to '" + level + "'"); + } + } + else { + spdlog::info("Set log level to '" + level + "'"); + } + + return true; +} + +bool Piscsi::ExecuteCommand(const CommandContext& context) +{ + const PbCommand& command = context.GetCommand(); + const PbOperation operation = command.operation(); + if (!access_token.empty() && access_token != GetParam(command, "token")) { return context.ReturnLocalizedError(LocalizationKey::ERROR_AUTHENTICATION, UNAUTHORIZED); } - if (!PbOperation_IsValid(command.operation())) { - LOGERROR("Received unknown command with operation opcode %d", command.operation()) + if (!PbOperation_IsValid(operation)) { + spdlog::trace("Ignored unknown command with operation opcode " + to_string(operation)); - return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION, UNKNOWN_OPERATION); + return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION, UNKNOWN_OPERATION, to_string(operation)); } - LOGTRACE("Received %s command", PbOperation_Name(command.operation()).c_str()) + spdlog::trace("Received " + PbOperation_Name(operation) + " command"); PbResult result; - ProtobufSerializer serializer; - switch(command.operation()) { - case LOG_LEVEL: { - const string log_level = GetParam(command, "level"); - if (const bool status = executor->SetLogLevel(log_level); !status) { + switch(operation) { + case LOG_LEVEL: + if (const string log_level = GetParam(command, "level"); !SetLogLevel(log_level)) { context.ReturnLocalizedError(LocalizationKey::ERROR_LOG_LEVEL, log_level); } else { - current_log_level = log_level; - - context.ReturnStatus(); + context.ReturnSuccessStatus(); } break; - } - case DEFAULT_FOLDER: { - if (const string status = piscsi_image.SetDefaultFolder(GetParam(command, "folder")); !status.empty()) { - context.ReturnStatus(false, status); + case DEFAULT_FOLDER: + if (const string error = piscsi_image.SetDefaultFolder(GetParam(command, "folder")); !error.empty()) { + context.ReturnErrorStatus(error); } else { - context.ReturnStatus(); + context.ReturnSuccessStatus(); } break; - } - case DEVICES_INFO: { - piscsi_response.GetDevicesInfo(controller_manager->GetAllDevices(), result, command, - piscsi_image.GetDefaultFolder()); - serializer.SerializeMessage(context.GetFd(), result); + case DEVICES_INFO: + response.GetDevicesInfo(controller_manager.GetAllDevices(), result, command, piscsi_image.GetDefaultFolder()); + context.WriteResult(result); break; - } - case DEVICE_TYPES_INFO: { - result.set_allocated_device_types_info(piscsi_response.GetDeviceTypesInfo(result).release()); - serializer.SerializeMessage(context.GetFd(), result); + case DEVICE_TYPES_INFO: + response.GetDeviceTypesInfo(*result.mutable_device_types_info()); + return context.WriteSuccessResult(result); + + case SERVER_INFO: + response.GetServerInfo(*result.mutable_server_info(), command, controller_manager.GetAllDevices(), + executor->GetReservedIds(), piscsi_image.GetDefaultFolder(), piscsi_image.GetDepth()); + context.WriteSuccessResult(result); break; - } - case SERVER_INFO: { - result.set_allocated_server_info(piscsi_response.GetServerInfo(controller_manager->GetAllDevices(), - result, executor->GetReservedIds(), current_log_level, piscsi_image.GetDefaultFolder(), - GetParam(command, "folder_pattern"), GetParam(command, "file_pattern"), - piscsi_image.GetDepth()).release()); - serializer.SerializeMessage(context.GetFd(), result); - break; - } + case VERSION_INFO: + response.GetVersionInfo(*result.mutable_version_info()); + return context.WriteSuccessResult(result); - case VERSION_INFO: { - result.set_allocated_version_info(piscsi_response.GetVersionInfo(result).release()); - serializer.SerializeMessage(context.GetFd(), result); - break; - } + case LOG_LEVEL_INFO: + response.GetLogLevelInfo(*result.mutable_log_level_info()); + return context.WriteSuccessResult(result); - case LOG_LEVEL_INFO: { - result.set_allocated_log_level_info(piscsi_response.GetLogLevelInfo(result, current_log_level).release()); - serializer.SerializeMessage(context.GetFd(), result); - break; - } + case DEFAULT_IMAGE_FILES_INFO: + response.GetImageFilesInfo(*result.mutable_image_files_info(), piscsi_image.GetDefaultFolder(), + GetParam(command, "folder_pattern"), GetParam(command, "file_pattern"), piscsi_image.GetDepth()); + return context.WriteSuccessResult(result); - case DEFAULT_IMAGE_FILES_INFO: { - result.set_allocated_image_files_info(piscsi_response.GetAvailableImages(result, - piscsi_image.GetDefaultFolder(), GetParam(command, "folder_pattern"), - GetParam(command, "file_pattern"), piscsi_image.GetDepth()).release()); - serializer.SerializeMessage(context.GetFd(), result); - break; - } - - case IMAGE_FILE_INFO: { + case IMAGE_FILE_INFO: if (string filename = GetParam(command, "file"); filename.empty()) { context.ReturnLocalizedError( LocalizationKey::ERROR_MISSING_FILENAME); } else { auto image_file = make_unique(); - const bool status = piscsi_response.GetImageFile(*image_file.get(), piscsi_image.GetDefaultFolder(), filename); + const bool status = response.GetImageFile(*image_file.get(), piscsi_image.GetDefaultFolder(), + filename); if (status) { - result.set_status(true); result.set_allocated_image_file_info(image_file.get()); - serializer.SerializeMessage(context.GetFd(), result); + result.set_status(true); + context.WriteResult(result); } else { context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_FILE_INFO); } } break; - } - case NETWORK_INTERFACES_INFO: { - result.set_allocated_network_interfaces_info(piscsi_response.GetNetworkInterfacesInfo(result).release()); - serializer.SerializeMessage(context.GetFd(), result); + case NETWORK_INTERFACES_INFO: + response.GetNetworkInterfacesInfo(*result.mutable_network_interfaces_info()); + return context.WriteSuccessResult(result); + + case MAPPING_INFO: + response.GetMappingInfo(*result.mutable_mapping_info()); + return context.WriteSuccessResult(result); + + case STATISTICS_INFO: + response.GetStatisticsInfo(*result.mutable_statistics_info(), controller_manager.GetAllDevices()); + context.WriteSuccessResult(result); break; - } - case MAPPING_INFO: { - result.set_allocated_mapping_info(piscsi_response.GetMappingInfo(result).release()); - serializer.SerializeMessage(context.GetFd(), result); - break; - } + case OPERATION_INFO: + response.GetOperationInfo(*result.mutable_operation_info(), piscsi_image.GetDepth()); + return context.WriteSuccessResult(result); - case OPERATION_INFO: { - result.set_allocated_operation_info(piscsi_response.GetOperationInfo(result, - piscsi_image.GetDepth()).release()); - serializer.SerializeMessage(context.GetFd(), result); - break; - } + case RESERVED_IDS_INFO: + response.GetReservedIds(*result.mutable_reserved_ids_info(), executor->GetReservedIds()); + return context.WriteSuccessResult(result); - case RESERVED_IDS_INFO: { - result.set_allocated_reserved_ids_info(piscsi_response.GetReservedIds(result, - executor->GetReservedIds()).release()); - serializer.SerializeMessage(context.GetFd(), result); - break; - } + case SHUT_DOWN: + return ShutDown(context, GetParam(command, "mode")); - case SHUT_DOWN: { - if (executor->ShutDown(context, GetParam(command, "mode"))) { - TerminationHandler(0); - } - break; - } + case NO_OPERATION: + return context.ReturnSuccessStatus(); - default: { - // Wait until we become idle - const timespec ts = { .tv_sec = 0, .tv_nsec = 500'000'000}; - while (active) { - nanosleep(&ts, nullptr); + case CREATE_IMAGE: + return piscsi_image.CreateImage(context); + + case DELETE_IMAGE: + return piscsi_image.DeleteImage(context); + + case RENAME_IMAGE: + return piscsi_image.RenameImage(context); + + case COPY_IMAGE: + return piscsi_image.CopyImage(context); + + case PROTECT_IMAGE: + case UNPROTECT_IMAGE: + return piscsi_image.SetImagePermissions(context); + + case RESERVE_IDS: + return executor->ProcessCmd(context); + + default: + // The remaining commands may only be executed when the target is idle + if (!ExecuteWithLock(context)) { + return false; } - executor->ProcessCmd(context, command); - break; - } + return HandleDeviceListChange(context, operation); } return true; } -int Piscsi::run(const vector& args) +bool Piscsi::ExecuteWithLock(const CommandContext& context) +{ + scoped_lock lock(execution_locker); + return executor->ProcessCmd(context); +} + +bool Piscsi::HandleDeviceListChange(const CommandContext& context, PbOperation operation) const +{ + // ATTACH and DETACH return the resulting device list + if (operation == ATTACH || operation == DETACH) { + // A command with an empty device list is required here in order to return data for all devices + PbCommand command; + PbResult result; + response.GetDevicesInfo(controller_manager.GetAllDevices(), result, command, piscsi_image.GetDefaultFolder()); + context.WriteResult(result); + return result.status(); + } + + return true; +} + +int Piscsi::run(span args) { GOOGLE_PROTOBUF_VERIFY_VERSION; Banner(args); + // The -v option shall result in no other action except displaying the version + if (ranges::find_if(args, [] (const char *arg) { return !strcasecmp(arg, "-v"); } ) != args.end()) { + cout << piscsi_get_version_string() << '\n'; + return EXIT_SUCCESS; + } + + PbCommand command; + string locale; + string reserved_ids; int port = DEFAULT_PORT; - optargs_type optargs; try { - optargs = ParseArguments(args, port); + locale = ParseArguments(args, command, port, reserved_ids); } catch(const parser_exception& e) { cerr << "Error: " << e.what() << endl; @@ -497,35 +502,53 @@ int Piscsi::run(const vector& args) return EXIT_FAILURE; } - // current_log_level may have been updated by ParseArguments() - executor->SetLogLevel(current_log_level); - - // Create a thread-safe stdout logger to process the log messages - const auto logger = stdout_color_mt("piscsi stdout logger"); - if (!InitBus()) { cerr << "Error: Can't initialize bus" << endl; return EXIT_FAILURE; } - // We need to wait to create the devices until after the bus/controller/etc objects have been created - // TODO Try to resolve dependencies so that this work-around can be removed - try { - CreateInitialDevices(optargs); - } - catch(const parser_exception& e) { - cerr << "Error: " << e.what() << endl; + if (const string error = service.Init([this] (CommandContext& context) { + context.SetDefaultFolder(piscsi_image.GetDefaultFolder()); + return ExecuteCommand(context); + }, port); !error.empty()) { + cerr << "Error: " << error << endl; - Cleanup(); + CleanUp(); return EXIT_FAILURE; } - if (!service.Init(&ExecuteCommand, port)) { + if (const string error = executor->SetReservedIds(reserved_ids); !error.empty()) { + cerr << "Error: " << error << endl; + + CleanUp(); + return EXIT_FAILURE; } + if (command.devices_size()) { + // Attach all specified devices + command.set_operation(ATTACH); + + if (const CommandContext context(command, piscsi_image.GetDefaultFolder(), locale); !executor->ProcessCmd(context)) { + cerr << "Error: Can't attach devices" << endl; + + CleanUp(); + + return EXIT_FAILURE; + } + } + + // Display and log the device list + PbServerInfo server_info; + response.GetDevices(controller_manager.GetAllDevices(), server_info, piscsi_image.GetDefaultFolder()); + const vector& devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() }; + const string device_list = ListDevices(devices); + LogDevices(device_list); + cout << device_list << flush; + + instance = this; // Signal handler to detach all devices on a KILL or TERM signal struct sigaction termination_handler; termination_handler.sa_handler = TerminationHandler; @@ -533,22 +556,30 @@ int Piscsi::run(const vector& args) termination_handler.sa_flags = 0; sigaction(SIGINT, &termination_handler, nullptr); sigaction(SIGTERM, &termination_handler, nullptr); + signal(SIGPIPE, SIG_IGN); // Set the affinity to a specific processor core FixCpu(3); - sched_param schparam; + service.Start(); + + Process(); + + return EXIT_SUCCESS; +} + +void Piscsi::Process() +{ #ifdef USE_SEL_EVENT_ENABLE // Scheduling policy setting (highest priority) + // TODO Check whether this results in any performance gain + sched_param schparam; schparam.sched_priority = sched_get_priority_max(SCHED_FIFO); sched_setscheduler(0, SCHED_FIFO, &schparam); #else cout << "Note: No PiSCSI hardware support, only client interface calls are supported" << endl; #endif - // Start execution - service.SetRunning(true); - // Main Loop while (service.IsRunning()) { #ifdef USE_SEL_EVENT_ENABLE @@ -572,81 +603,89 @@ int Piscsi::run(const vector& args) } #endif - // Wait until BSY is released as there is a possibility for the - // initiator to assert it while setting the ID (for up to 3 seconds) - WaitForNotBusy(); + // Only process the SCSI command if the bus is not busy and no other device responded + if (IsNotBusy() && bus->GetSEL()) { + scoped_lock lock(execution_locker); - // Stop because the bus is busy or another device responded - if (bus->GetBSY() || !bus->GetSEL()) { - continue; - } - - int initiator_id = AbstractController::UNKNOWN_INITIATOR_ID; - - // The initiator and target ID - const uint8_t id_data = bus->GetDAT(); - - phase_t phase = phase_t::busfree; - - // Identify the responsible controller - auto controller = controller_manager->IdentifyController(id_data); - if (controller != nullptr) { - device_logger.SetIdAndLun(controller->GetTargetId(), -1); - - initiator_id = controller->ExtractInitiatorId(id_data); - - if (initiator_id != AbstractController::UNKNOWN_INITIATOR_ID) { - device_logger.Trace("++++ Starting processing for initiator ID " + to_string(initiator_id)); - } - else { - device_logger.Trace("++++ Starting processing for unknown initiator ID"); - } - - if (controller->Process(initiator_id) == phase_t::selection) { - phase = phase_t::selection; + // Process command on the responsible controller based on the current initiator and target ID + if (const auto shutdown_mode = controller_manager.ProcessOnController(bus->GetDAT()); + shutdown_mode != AbstractController::piscsi_shutdown_mode::NONE) { + // When the bus is free PiSCSI or the Pi may be shut down. + ShutDown(shutdown_mode); } } - - // Return to bus monitoring if the selection phase has not started - if (phase != phase_t::selection) { - continue; - } - - // Start target device - active = true; - -#if !defined(USE_SEL_EVENT_ENABLE) && defined(__linux__) - // Scheduling policy setting (highest priority) - schparam.sched_priority = sched_get_priority_max(SCHED_FIFO); - sched_setscheduler(0, SCHED_FIFO, &schparam); -#endif - - // Loop until the bus is free - while (service.IsRunning()) { - // Target drive - phase = controller->Process(initiator_id); - - // End when the bus is free - if (phase == phase_t::busfree) { - break; - } - } - -#if !defined(USE_SEL_EVENT_ENABLE) && defined(__linux__) - // Set the scheduling priority back to normal - schparam.sched_priority = 0; - sched_setscheduler(0, SCHED_OTHER, &schparam); -#endif - - // End the target travel - active = false; } - - return EXIT_SUCCESS; } -void Piscsi::WaitForNotBusy() const +// Shutdown on a remote interface command +bool Piscsi::ShutDown(const CommandContext& context, const string& m) { + if (m.empty()) { + return context.ReturnLocalizedError(LocalizationKey::ERROR_SHUTDOWN_MODE_MISSING); + } + + AbstractController::piscsi_shutdown_mode mode = AbstractController::piscsi_shutdown_mode::NONE; + if (m == "rascsi") { + mode = AbstractController::piscsi_shutdown_mode::STOP_PISCSI; + } + else if (m == "system") { + mode = AbstractController::piscsi_shutdown_mode::STOP_PI; + } + else if (m == "reboot") { + mode = AbstractController::piscsi_shutdown_mode::RESTART_PI; + } + else { + return context.ReturnLocalizedError(LocalizationKey::ERROR_SHUTDOWN_MODE_INVALID, m); + } + + // Shutdown modes other than rascsi require root permissions + if (mode != AbstractController::piscsi_shutdown_mode::STOP_PISCSI && getuid()) { + return context.ReturnLocalizedError(LocalizationKey::ERROR_SHUTDOWN_PERMISSION); + } + + // Report success now because after a shutdown nothing can be reported anymore + PbResult result; + context.WriteSuccessResult(result); + + return ShutDown(mode); +} + +// Shutdown on a SCSI command +bool Piscsi::ShutDown(AbstractController::piscsi_shutdown_mode shutdown_mode) { + switch(shutdown_mode) { + case AbstractController::piscsi_shutdown_mode::STOP_PISCSI: + spdlog::info("PiSCSI shutdown requested"); + CleanUp(); + return true; + + case AbstractController::piscsi_shutdown_mode::STOP_PI: + spdlog::info("Raspberry Pi shutdown requested"); + CleanUp(); + if (system("init 0") == -1) { + spdlog::error("Raspberry Pi shutdown failed"); + } + break; + + case AbstractController::piscsi_shutdown_mode::RESTART_PI: + spdlog::info("Raspberry Pi restart requested"); + CleanUp(); + if (system("init 6") == -1) { + spdlog::error("Raspberry Pi restart failed"); + } + break; + + case AbstractController::piscsi_shutdown_mode::NONE: + assert(false); + break; + } + + return false; +} + +bool Piscsi::IsNotBusy() const +{ + // Wait until BSY is released as there is a possibility for the + // initiator to assert it while setting the ID (for up to 3 seconds) if (bus->GetBSY()) { const uint32_t now = SysTimer::GetTimerLow(); @@ -655,8 +694,12 @@ void Piscsi::WaitForNotBusy() const bus->Acquire(); if (!bus->GetBSY()) { - break; + return true; } } + + return false; } + + return true; } diff --git a/cpp/piscsi/piscsi_core.h b/cpp/piscsi/piscsi_core.h index 22cb158b..1560851d 100644 --- a/cpp/piscsi/piscsi_core.h +++ b/cpp/piscsi/piscsi_core.h @@ -3,31 +3,30 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once -#include "devices/device_logger.h" +#include "controllers/controller_manager.h" #include "piscsi/command_context.h" #include "piscsi/piscsi_service.h" #include "piscsi/piscsi_image.h" #include "piscsi/piscsi_response.h" +#include "piscsi/piscsi_executor.h" #include "generated/piscsi_interface.pb.h" -#include +#include "spdlog/sinks/stdout_color_sinks.h" +#include #include +#include using namespace std; class BUS; -class ControllerManager; -class PiscsiExecutor; class Piscsi { - using optargs_type = vector>; - static const int DEFAULT_PORT = 6868; public: @@ -35,46 +34,49 @@ public: Piscsi() = default; ~Piscsi() = default; - int run(const vector&); + int run(span); private: - void Banner(const vector&) const; - bool InitBus() const; - static void Cleanup(); - void ReadAccessToken(const string&) const; + void Banner(span) const; + bool InitBus(); + void CleanUp(); + void ReadAccessToken(const path&); void LogDevices(string_view) const; - PbDeviceType ParseDeviceType(const string&) const; static void TerminationHandler(int); - optargs_type ParseArguments(const vector&, int&) const; - void CreateInitialDevices(const optargs_type&) const; - void WaitForNotBusy() const; + string ParseArguments(span, PbCommand&, int&, string&); + void Process(); + bool IsNotBusy() const; - // TODO Should not be static and should be moved to PiscsiService - static bool ExecuteCommand(const CommandContext&, const PbCommand&); + bool ShutDown(AbstractController::piscsi_shutdown_mode); + bool ShutDown(const CommandContext&, const string&); - DeviceLogger device_logger; + bool ExecuteCommand(const CommandContext&); + bool ExecuteWithLock(const CommandContext&); + bool HandleDeviceListChange(const CommandContext&, PbOperation) const; - // A static instance is needed because of the signal handler - static inline shared_ptr bus; + bool SetLogLevel(const string&) const; - // TODO These fields should not be static + const shared_ptr logger = spdlog::stdout_color_mt("piscsi stdout logger"); - static inline PiscsiService service; + static PbDeviceType ParseDeviceType(const string&); - static inline PiscsiImage piscsi_image; + mutex execution_locker; - const static inline PiscsiResponse piscsi_response; + string access_token; - static inline shared_ptr controller_manager; + PiscsiImage piscsi_image; - static inline shared_ptr executor; + PiscsiResponse response; - // Processing flag - static inline volatile bool active; + PiscsiService service; - // Some versions of spdlog do not support get_log_level(), so we have to remember the level - static inline string current_log_level = "info"; + unique_ptr executor; - static inline string access_token; + ControllerManager controller_manager; + + unique_ptr bus; + + // Required for the termination handler + static inline Piscsi *instance; }; diff --git a/cpp/piscsi/piscsi_executor.cpp b/cpp/piscsi/piscsi_executor.cpp index 2f984992..d930184e 100644 --- a/cpp/piscsi/piscsi_executor.cpp +++ b/cpp/piscsi/piscsi_executor.cpp @@ -3,35 +3,28 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "shared/log.h" #include "shared/piscsi_util.h" #include "shared/protobuf_util.h" #include "shared/piscsi_exceptions.h" -#include "controllers/controller_manager.h" -#include "controllers/scsi_controller.h" -#include "devices/device_logger.h" #include "devices/device_factory.h" -#include "devices/primary_device.h" #include "devices/disk.h" -#include "piscsi_service.h" -#include "piscsi_image.h" #include "localizer.h" #include "command_context.h" #include "piscsi_executor.h" +#include #include using namespace spdlog; using namespace protobuf_util; using namespace piscsi_util; -bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDeviceDefinition& pb_device, - const PbCommand& command, bool dryRun) +bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDeviceDefinition& pb_device, bool dryRun) { - PrintCommand(command, pb_device, dryRun); + spdlog::info((dryRun ? "Validating: " : "Executing: ") + PrintCommand(context.GetCommand(), pb_device)); const int id = pb_device.id(); const int lun = pb_device.unit(); @@ -40,14 +33,14 @@ bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDev return false; } - const PbOperation operation = command.operation(); + const PbOperation operation = context.GetCommand().operation(); // For all commands except ATTACH the device and LUN must exist if (operation != ATTACH && !VerifyExistingIdAndLun(context, id, lun)) { return false; } - auto device = controller_manager.GetDeviceByIdAndLun(id, lun); + auto device = controller_manager.GetDeviceForIdAndLun(id, lun); if (!ValidateOperationAgainstDevice(context, *device, operation)) { return false; @@ -64,7 +57,7 @@ bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDev return Attach(context, pb_device, dryRun); case DETACH: - return Detach(context, device, dryRun); + return Detach(context, *device, dryRun); case INSERT: return Insert(context, pb_device, device, dryRun); @@ -82,146 +75,68 @@ bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDev case CHECK_AUTHENTICATION: case NO_OPERATION: // Do nothing, just log - LOGTRACE("Received %s command", PbOperation_Name(operation).c_str()) + spdlog::trace("Received " + PbOperation_Name(operation) + " command"); break; default: - return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION); + return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION, to_string(operation)); } return true; } -bool PiscsiExecutor::ProcessCmd(const CommandContext& context, const PbCommand& command) +bool PiscsiExecutor::ProcessCmd(const CommandContext& context) { + const PbCommand& command = context.GetCommand(); + + // Handle commands that are not device-specific switch (command.operation()) { case DETACH_ALL: DetachAll(); - return context.ReturnStatus(); + return context.ReturnSuccessStatus(); case RESERVE_IDS: { - const string ids = GetParam(command, "ids"); - if (const string error = SetReservedIds(ids); !error.empty()) { - return context.ReturnStatus(false, error); + if (const string error = SetReservedIds(GetParam(command, "ids")); !error.empty()) { + return context.ReturnErrorStatus(error); } - return context.ReturnStatus(); + return context.ReturnSuccessStatus(); } - case CREATE_IMAGE: - return piscsi_image.CreateImage(context, command); - - case DELETE_IMAGE: - return piscsi_image.DeleteImage(context, command); - - case RENAME_IMAGE: - return piscsi_image.RenameImage(context, command); - - case COPY_IMAGE: - return piscsi_image.CopyImage(context, command); - - case PROTECT_IMAGE: - case UNPROTECT_IMAGE: - return piscsi_image.SetImagePermissions(context, command); - default: // This is a device-specific command handled below break; } - // Remember the list of reserved files, than run the dry run + // Remember the list of reserved files during the dry run const auto& reserved_files = StorageDevice::GetReservedFiles(); - for (const auto& device : command.devices()) { - if (!ProcessDeviceCmd(context, device, command, true)) { - // Dry run failed, restore the file list - StorageDevice::SetReservedFiles(reserved_files); - return false; - } - } - - // Restore the list of reserved files before proceeding + const bool isDryRunError = ranges::find_if_not(command.devices(), [&] (const auto& device) + { return ProcessDeviceCmd(context, device, true); }) != command.devices().end(); StorageDevice::SetReservedFiles(reserved_files); - if (const string result = ValidateLunSetup(command); !result.empty()) { - return context.ReturnStatus(false, result); - } - - for (const auto& device : command.devices()) { - if (!ProcessDeviceCmd(context, device, command, false)) { - return false; - } - } - - // ATTACH and DETACH return the device list - if (context.IsValid() && (command.operation() == ATTACH || command.operation() == DETACH)) { - // A new command with an empty device list is required here in order to return data for all devices - PbCommand cmd; - PbResult result; - piscsi_response.GetDevicesInfo(controller_manager.GetAllDevices(), result, cmd, - piscsi_image.GetDefaultFolder()); - serializer.SerializeMessage(context.GetFd(), result); - return true; - } - - return context.ReturnStatus(); -} - -bool PiscsiExecutor::SetLogLevel(const string& log_level) const -{ - int id = -1; - int lun = -1; - string level = log_level; - - if (size_t separator_pos = log_level.find(COMPONENT_SEPARATOR); separator_pos != string::npos) { - level = log_level.substr(0, separator_pos); - - const string l = log_level.substr(separator_pos + 1); - separator_pos = l.find(COMPONENT_SEPARATOR); - if (separator_pos != string::npos) { - const string error = ProcessId(l, ScsiController::LUN_MAX, id, lun); - if (!error.empty()) { - LOGWARN("Invalid device ID/LUN specifier '%s'", l.c_str()) - return false; - } - } - else if (!GetAsUnsignedInt(l, id)) { - LOGWARN("Invalid device ID specifier '%s'", l.c_str()) - return false; - } - } - - if (const auto& it = log_level_mapping.find(level); it != log_level_mapping.end()) { - set_level(it->second); - } - else { - LOGWARN("Invalid log level '%s'", log_level.c_str()) + if (isDryRunError) { return false; } - DeviceLogger::SetLogIdAndLun(id, lun); - - if (id != -1) { - if (lun == -1) { - LOGINFO("Set log level for device ID %d to '%s'", id, level.c_str()) - } - else { - LOGINFO("Set log level for device ID %d, LUN %d to '%s'", id, lun, level.c_str()) - } - } - else { - LOGINFO("Set log level to '%s'", level.c_str()) + if (const string error = EnsureLun0(command); !error.empty()) { + return context.ReturnErrorStatus(error); } - return true; + if (ranges::find_if_not(command.devices(), [&] (const auto& device) + { return ProcessDeviceCmd(context, device, false); } ) != command.devices().end()) { + return false; + } + + return context.ReturnSuccessStatus(); } bool PiscsiExecutor::Start(PrimaryDevice& device, bool dryRun) const { if (!dryRun) { - LOGINFO("Start requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(), device.GetLun()) + spdlog::info("Start requested for " + device.GetIdentifier()); if (!device.Start()) { - LOGWARN("Starting %s ID %d, unit %d failed", device.GetTypeString(), device.GetId(), device.GetLun()) + spdlog::warn("Starting " + device.GetIdentifier() + " failed"); } } @@ -231,7 +146,7 @@ bool PiscsiExecutor::Start(PrimaryDevice& device, bool dryRun) const bool PiscsiExecutor::Stop(PrimaryDevice& device, bool dryRun) const { if (!dryRun) { - LOGINFO("Stop requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(), device.GetLun()) + spdlog::info("Stop requested for " + device.GetIdentifier()); device.Stop(); } @@ -242,10 +157,10 @@ bool PiscsiExecutor::Stop(PrimaryDevice& device, bool dryRun) const bool PiscsiExecutor::Eject(PrimaryDevice& device, bool dryRun) const { if (!dryRun) { - LOGINFO("Eject requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(), device.GetLun()) + spdlog::info("Eject requested for " + device.GetIdentifier()); if (!device.Eject(true)) { - LOGWARN("Ejecting %s ID %d, unit %d failed", device.GetTypeString(), device.GetId(), device.GetLun()) + spdlog::warn("Ejecting " + device.GetIdentifier() + " failed"); } } @@ -255,8 +170,7 @@ bool PiscsiExecutor::Eject(PrimaryDevice& device, bool dryRun) const bool PiscsiExecutor::Protect(PrimaryDevice& device, bool dryRun) const { if (!dryRun) { - LOGINFO("Write protection requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(), - device.GetLun()) + spdlog::info("Write protection requested for " + device.GetIdentifier()); device.SetProtected(true); } @@ -267,8 +181,7 @@ bool PiscsiExecutor::Protect(PrimaryDevice& device, bool dryRun) const bool PiscsiExecutor::Unprotect(PrimaryDevice& device, bool dryRun) const { if (!dryRun) { - LOGINFO("Write unprotection requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(), - device.GetLun()) + spdlog::info("Write unprotection requested for " + device.GetIdentifier()); device.SetProtected(false); } @@ -282,15 +195,16 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit const int lun = pb_device.unit(); const PbDeviceType type = pb_device.type(); - if (lun >= ScsiController::LUN_MAX) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun), to_string(ScsiController::LUN_MAX)); + if (lun >= ControllerManager::GetScsiLunMax()) { + return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun), + to_string(ControllerManager::GetScsiLunMax())); } - if (controller_manager.GetDeviceByIdAndLun(id, lun) != nullptr) { + if (controller_manager.HasDeviceForIdAndLun(id, lun)) { return context.ReturnLocalizedError(LocalizationKey::ERROR_DUPLICATE_ID, to_string(id), to_string(lun)); } - if (reserved_ids.find(id) != reserved_ids.end()) { + if (reserved_ids.contains(id)) { return context.ReturnLocalizedError(LocalizationKey::ERROR_RESERVED_ID, to_string(id)); } @@ -302,8 +216,7 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit } // If no filename was provided the medium is considered not inserted - auto storage_device = dynamic_pointer_cast(device); - device->SetRemoved(storage_device != nullptr ? filename.empty() : false); + device->SetRemoved(device->SupportsFile() ? filename.empty() : false); if (!SetProductData(context, pb_device, *device)) { return false; @@ -313,14 +226,14 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit return false; } - string full_path; + const auto storage_device = dynamic_pointer_cast(device); if (device->SupportsFile()) { // Only with removable media drives, CD and MO the medium (=file) may be inserted later if (!device->IsRemovable() && filename.empty()) { return context.ReturnLocalizedError(LocalizationKey::ERROR_MISSING_FILENAME, PbDeviceType_Name(type)); } - if (!ValidateImageFile(context, *storage_device, filename, full_path)) { + if (!ValidateImageFile(context, *storage_device, filename)) { return false; } } @@ -336,25 +249,24 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit return true; } - unordered_map params = { pb_device.params().begin(), pb_device.params().end() }; + param_map params = { pb_device.params().begin(), pb_device.params().end() }; if (!device->SupportsFile()) { // Clients like scsictl might have sent both "file" and "interfaces" params.erase("file"); } if (!device->Init(params)) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_INITIALIZATION, PbDeviceType_Name(device->GetType()), - to_string(id), to_string(lun)); + return context.ReturnLocalizedError(LocalizationKey::ERROR_INITIALIZATION, device->GetIdentifier()); } - if (storage_device != nullptr) { - storage_device->ReserveFile(full_path, id, lun); - } - - if (!controller_manager.AttachToScsiController(id, device)) { + if (!controller_manager.AttachToController(bus, id, device)) { return context.ReturnLocalizedError(LocalizationKey::ERROR_SCSI_CONTROLLER); } + if (storage_device != nullptr && !storage_device->IsRemoved()) { + storage_device->ReserveFile(); + } + string msg = "Attached "; if (device->IsReadOnly()) { msg += "read-only "; @@ -362,8 +274,8 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit else if (device->IsProtectable() && device->IsProtected()) { msg += "protected "; } - msg += string(device->GetTypeString()) + " device, ID " + to_string(id) + ", unit " + to_string(lun); - LOGINFO("%s", msg.c_str()) + msg += device->GetIdentifier(); + spdlog::info(msg); return true; } @@ -371,12 +283,11 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit bool PiscsiExecutor::Insert(const CommandContext& context, const PbDeviceDefinition& pb_device, const shared_ptr& device, bool dryRun) const { - auto storage_device = dynamic_pointer_cast(device); - if (storage_device == nullptr) { + if (!device->SupportsFile()) { return false; } - if (!storage_device->IsRemoved()) { + if (!device->IsRemoved()) { return context.ReturnLocalizedError(LocalizationKey::ERROR_EJECT_REQUIRED); } @@ -394,56 +305,51 @@ bool PiscsiExecutor::Insert(const CommandContext& context, const PbDeviceDefinit return true; } - LOGINFO("Insert %sfile '%s' requested into %s ID %d, unit %d", pb_device.protected_() ? "protected " : "", - filename.c_str(), storage_device->GetTypeString(), pb_device.id(), pb_device.unit()) + spdlog::info("Insert " + string(pb_device.protected_() ? "protected " : "") + "file '" + filename + + "' requested into " + device->GetIdentifier()); - if (!SetSectorSize(context, storage_device, pb_device.block_size())) { + if (!SetSectorSize(context, device, pb_device.block_size())) { return false; } - string full_path; - if (!ValidateImageFile(context, *storage_device, filename, full_path)) { + auto storage_device = dynamic_pointer_cast(device); + if (!ValidateImageFile(context, *storage_device, filename)) { return false; } storage_device->SetProtected(pb_device.protected_()); - storage_device->ReserveFile(full_path, storage_device->GetId(), storage_device->GetLun()); + storage_device->ReserveFile(); storage_device->SetMediumChanged(true); return true; } -bool PiscsiExecutor::Detach(const CommandContext& context, const shared_ptr& device, bool dryRun) const +bool PiscsiExecutor::Detach(const CommandContext& context, PrimaryDevice& device, bool dryRun) { - auto controller = controller_manager.FindController(device->GetId()); + auto controller = controller_manager.FindController(device.GetId()); if (controller == nullptr) { return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH); } // LUN 0 can only be detached if there is no other LUN anymore - if (!device->GetLun() && controller->GetLunCount() > 1) { + if (!device.GetLun() && controller->GetLunCount() > 1) { return context.ReturnLocalizedError(LocalizationKey::ERROR_LUN0); } if (!dryRun) { - // Remember the ID before it gets invalid when removing the device - const int id = device->GetId(); + // Remember the device identifier for the log message before the device data become invalid on removal + const string identifier = device.GetIdentifier(); if (!controller->RemoveDevice(device)) { return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH); } // If no LUN is left also delete the controller - if (!controller->GetLunCount() && !controller_manager.DeleteController(controller)) { + if (!controller->GetLunCount() && !controller_manager.DeleteController(*controller)) { return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH); } - if (auto storage_device = dynamic_pointer_cast(device); storage_device != nullptr) { - storage_device->UnreserveFile(); - } - - LOGINFO("%s", ("Detached " + string(device->GetTypeString()) + " device with ID " + to_string(id) - + ", unit " + to_string(device->GetLun())).c_str()) + spdlog::info("Detached " + identifier); } return true; @@ -452,177 +358,96 @@ bool PiscsiExecutor::Detach(const CommandContext& context, const shared_ptr ids_to_reserve; + set ids_to_reserve; stringstream ss(ids.data()); string id; while (getline(ss, id, ',')) { - if (!id.empty()) { - ids_to_reserve.push_back(id); - } - } - - set reserved; - for (const string& id_to_reserve : ids_to_reserve) { int res_id; - if (!GetAsUnsignedInt(id_to_reserve, res_id) || res_id > 7) { - return "Invalid ID " + id_to_reserve; - } - - if (controller_manager.FindController(res_id) != nullptr) { - return "ID " + id_to_reserve + " is currently in use"; - } - - reserved.insert(res_id); - } - - reserved_ids = { reserved.begin(), reserved.end() }; - - if (!reserved_ids.empty()) { - string s; - bool isFirst = true; - for (const auto& reserved_id : reserved) { - if (!isFirst) { - s += ", "; - } - isFirst = false; - s += to_string(reserved_id); + if (!GetAsUnsignedInt(id, res_id) || res_id > 7) { + return "Invalid ID " + id; } - LOGINFO("Reserved ID(s) set to %s", s.c_str()) + if (controller_manager.HasController(res_id)) { + return "ID " + id + " is currently in use"; + } + + ids_to_reserve.insert(res_id); + } + + reserved_ids = { ids_to_reserve.begin(), ids_to_reserve.end() }; + + if (!ids_to_reserve.empty()) { + spdlog::info("Reserved ID(s) set to " + Join(ids_to_reserve)); } else { - LOGINFO("Cleared reserved ID(s)") + spdlog::info("Cleared reserved ID(s)"); } return ""; } bool PiscsiExecutor::ValidateImageFile(const CommandContext& context, StorageDevice& storage_device, - const string& filename, string& full_path) const + const string& filename) const { if (filename.empty()) { return true; } - if (const auto [id1, lun1] = StorageDevice::GetIdsForReservedFile(filename); id1 != -1 || lun1 != -1) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_IN_USE, filename, - to_string(id1), to_string(lun1)); + if (!CheckForReservedFile(context, filename)) { + return false; } - string effective_filename = filename; + storage_device.SetFilename(filename); if (!StorageDevice::FileExists(filename)) { // If the file does not exist search for it in the default image folder - effective_filename = piscsi_image.GetDefaultFolder() + "/" + filename; + const string effective_filename = context.GetDefaultFolder() + "/" + filename; - if (const auto [id2, lun2] = StorageDevice::GetIdsForReservedFile(effective_filename); id2 != -1 || lun2 != -1) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_IN_USE, filename, - to_string(id2), to_string(lun2)); + if (!CheckForReservedFile(context, effective_filename)) { + return false; } - if (!StorageDevice::FileExists(effective_filename)) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_FILE_OPEN, effective_filename); - } - } - - storage_device.SetFilename(effective_filename); - - if (storage_device.IsReadOnlyFile()) { - // Permanently write-protected - storage_device.SetReadOnly(true); - storage_device.SetProtectable(false); - } - else { - storage_device.SetReadOnly(false); - storage_device.SetProtectable(true); + storage_device.SetFilename(effective_filename); } try { storage_device.Open(); } catch(const io_exception&) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_FILE_OPEN, effective_filename); + return context.ReturnLocalizedError(LocalizationKey::ERROR_FILE_OPEN, storage_device.GetFilename()); } - full_path = effective_filename; - return true; } -void PiscsiExecutor::PrintCommand(const PbCommand& command, const PbDeviceDefinition& pb_device, bool dryRun) const +bool PiscsiExecutor::CheckForReservedFile(const CommandContext& context, const string& filename) +{ + if (const auto [id, lun] = StorageDevice::GetIdsForReservedFile(filename); id != -1) { + return context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_IN_USE, filename, + to_string(id) + ":" + to_string(lun)); + } + + return true; +} + +string PiscsiExecutor::PrintCommand(const PbCommand& command, const PbDeviceDefinition& pb_device) const { const map> params = { command.params().begin(), command.params().end() }; ostringstream s; - s << (dryRun ? "Validating" : "Executing"); - s << ": operation=" << PbOperation_Name(command.operation()); + s << "operation=" << PbOperation_Name(command.operation()); if (!params.empty()) { s << ", command params="; bool isFirst = true; - for (const auto& [key, value]: params) { + for (const auto& [key, value] : params) { if (!isFirst) { s << ", "; } @@ -632,8 +457,7 @@ void PiscsiExecutor::PrintCommand(const PbCommand& command, const PbDeviceDefini } } - s << ", device id=" << pb_device.id() << ", lun=" << pb_device.unit() << ", type=" - << PbDeviceType_Name(pb_device.type()); + s << ", device=" << pb_device.id() << ":" << pb_device.unit() << ", type=" << PbDeviceType_Name(pb_device.type()); if (pb_device.params_size()) { s << ", device params="; @@ -649,13 +473,14 @@ void PiscsiExecutor::PrintCommand(const PbCommand& command, const PbDeviceDefini s << ", vendor='" << pb_device.vendor() << "', product='" << pb_device.product() << "', revision='" << pb_device.revision() << "', block size=" << pb_device.block_size(); - LOGINFO("%s", s.str().c_str()) + + return s.str(); } -string PiscsiExecutor::ValidateLunSetup(const PbCommand& command) const +string PiscsiExecutor::EnsureLun0(const PbCommand& command) const { // Mapping of available LUNs (bit vector) to devices - unordered_map luns; + unordered_map luns; // Collect LUN bit vectors of new devices for (const auto& device : command.devices()) { @@ -667,23 +492,17 @@ string PiscsiExecutor::ValidateLunSetup(const PbCommand& command) const luns[device->GetId()] |= 1 << device->GetLun(); } - // LUN 0 must exist for all devices - for (const auto& [id, lun]: luns) { - if (!(lun & 0x01)) { - return "LUN 0 is missing for device ID " + to_string(id); - } - } - - return ""; + const auto& it = ranges::find_if_not(luns, [] (const auto& l) { return l.second & 0x01; } ); + return it == luns.end() ? "" : "LUN 0 is missing for device ID " + to_string((*it).first); } bool PiscsiExecutor::VerifyExistingIdAndLun(const CommandContext& context, int id, int lun) const { - if (controller_manager.FindController(id) == nullptr) { + if (!controller_manager.HasController(id)) { return context.ReturnLocalizedError(LocalizationKey::ERROR_NON_EXISTING_DEVICE, to_string(id)); } - if (controller_manager.GetDeviceByIdAndLun(id, lun) == nullptr) { + if (!controller_manager.HasDeviceForIdAndLun(id, lun)) { return context.ReturnLocalizedError(LocalizationKey::ERROR_NON_EXISTING_UNIT, to_string(id), to_string(lun)); } @@ -706,13 +525,13 @@ shared_ptr PiscsiExecutor::CreateDevice(const CommandContext& con return device; } -bool PiscsiExecutor::SetSectorSize(const CommandContext& context, shared_ptr device, int block_size) const +bool PiscsiExecutor::SetSectorSize(const CommandContext& context, shared_ptr device, int size) const { - if (block_size) { - auto disk = dynamic_pointer_cast(device); + if (size) { + const auto disk = dynamic_pointer_cast(device); if (disk != nullptr && disk->IsSectorSizeConfigurable()) { - if (!disk->SetConfiguredSectorSize(device_factory, block_size)) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_BLOCK_SIZE, to_string(block_size)); + if (!disk->SetConfiguredSectorSize(device_factory, size)) { + return context.ReturnLocalizedError(LocalizationKey::ERROR_BLOCK_SIZE, to_string(size)); } } else { @@ -725,22 +544,26 @@ bool PiscsiExecutor::SetSectorSize(const CommandContext& context, shared_ptr= ControllerManager::DEVICE_MAX) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_ID, to_string(id), to_string(ControllerManager::DEVICE_MAX - 1)); + if (id >= ControllerManager::GetScsiIdMax()) { + return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_ID, to_string(id), + to_string(ControllerManager::GetScsiIdMax() - 1)); } - if (lun < 0 || lun >= ScsiController::LUN_MAX) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun), to_string(ScsiController::LUN_MAX - 1)); + if (lun < 0 || lun >= ControllerManager::GetScsiLunMax()) { + return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun), + to_string(ControllerManager::GetScsiLunMax() - 1)); } return true; @@ -777,7 +601,7 @@ bool PiscsiExecutor::SetProductData(const CommandContext& context, const PbDevic } } catch(const invalid_argument& e) { - return context.ReturnStatus(false, e.what()); + return context.ReturnErrorStatus(e.what()); } return true; diff --git a/cpp/piscsi/piscsi_executor.h b/cpp/piscsi/piscsi_executor.h index cf2739a3..bae4fe60 100644 --- a/cpp/piscsi/piscsi_executor.h +++ b/cpp/piscsi/piscsi_executor.h @@ -3,39 +3,34 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once -#include "spdlog/spdlog.h" -#include "shared/protobuf_serializer.h" -#include "piscsi/piscsi_response.h" +#include "hal/bus.h" +#include "controllers/controller_manager.h" #include -#include -class PiscsiImage; class DeviceFactory; -class ControllerManager; class PrimaryDevice; +class StorageDevice; class CommandContext; -using namespace spdlog; - class PiscsiExecutor { public: - PiscsiExecutor(PiscsiImage& piscsi_image, ControllerManager& controller_manager) - : piscsi_image(piscsi_image), controller_manager(controller_manager) {} + PiscsiExecutor(BUS& bus, ControllerManager& controller_manager) : bus(bus), controller_manager(controller_manager) {} ~PiscsiExecutor() = default; - unordered_set GetReservedIds() const { return reserved_ids; } + // TODO At least some of these methods should be private, currently they are directly called by the unit tests - bool ProcessDeviceCmd(const CommandContext&, const PbDeviceDefinition&, const PbCommand&, bool); - bool ProcessCmd(const CommandContext&, const PbCommand&); - bool SetLogLevel(const string&) const; + auto GetReservedIds() const { return reserved_ids; } + + bool ProcessDeviceCmd(const CommandContext&, const PbDeviceDefinition&, bool); + bool ProcessCmd(const CommandContext&); bool Start(PrimaryDevice&, bool) const; bool Stop(PrimaryDevice&, bool) const; bool Eject(PrimaryDevice&, bool) const; @@ -43,41 +38,29 @@ public: bool Unprotect(PrimaryDevice&, bool) const; bool Attach(const CommandContext&, const PbDeviceDefinition&, bool); bool Insert(const CommandContext&, const PbDeviceDefinition&, const shared_ptr&, bool) const; - bool Detach(const CommandContext&, const shared_ptr&, bool) const; + bool Detach(const CommandContext&, PrimaryDevice&, bool); void DetachAll(); - bool ShutDown(const CommandContext&, const string&); string SetReservedIds(string_view); - bool ValidateImageFile(const CommandContext&, StorageDevice&, const string&, string&) const; - void PrintCommand(const PbCommand&, const PbDeviceDefinition&, bool) const; - string ValidateLunSetup(const PbCommand&) const; + bool ValidateImageFile(const CommandContext&, StorageDevice&, const string&) const; + string PrintCommand(const PbCommand&, const PbDeviceDefinition&) const; + string EnsureLun0(const PbCommand&) const; bool VerifyExistingIdAndLun(const CommandContext&, int, int) const; shared_ptr CreateDevice(const CommandContext&, const PbDeviceType, int, const string&) const; bool SetSectorSize(const CommandContext&, shared_ptr, int) const; - static bool ValidateOperationAgainstDevice(const CommandContext&, const PrimaryDevice&, const PbOperation&); + static bool ValidateOperationAgainstDevice(const CommandContext&, const PrimaryDevice&, PbOperation); static bool ValidateIdAndLun(const CommandContext&, int, int); static bool SetProductData(const CommandContext&, const PbDeviceDefinition&, PrimaryDevice&); private: - const PiscsiResponse piscsi_response; + static bool CheckForReservedFile(const CommandContext&, const string&); - PiscsiImage& piscsi_image; + BUS& bus; ControllerManager& controller_manager; const DeviceFactory device_factory; - const ProtobufSerializer serializer; - unordered_set reserved_ids; - - static inline const unordered_map log_level_mapping = { - { "trace", level::trace }, - { "debug", level::debug }, - { "info", level::info }, - { "warn", level::warn }, - { "err", level::err }, - { "off", level::off } - }; }; diff --git a/cpp/piscsi/piscsi_image.cpp b/cpp/piscsi/piscsi_image.cpp index 6f32c78d..8e569e04 100644 --- a/cpp/piscsi/piscsi_image.cpp +++ b/cpp/piscsi/piscsi_image.cpp @@ -3,21 +3,18 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "shared/log.h" -#include "shared/protobuf_util.h" #include "devices/disk.h" -#include "command_context.h" #include "piscsi_image.h" +#include "shared/protobuf_util.h" +#include #include #include #include -#include #include -#include using namespace std; using namespace filesystem; @@ -32,14 +29,12 @@ PiscsiImage::PiscsiImage() bool PiscsiImage::CheckDepth(string_view filename) const { - return count(filename.begin(), filename.end(), '/') <= depth; + return ranges::count(filename, '/') <= depth; } -bool PiscsiImage::CreateImageFolder(const CommandContext& context, const string& filename) const +bool PiscsiImage::CreateImageFolder(const CommandContext& context, string_view filename) const { - if (const size_t filename_start = filename.rfind('/'); filename_start != string::npos) { - const auto folder = path(filename.substr(0, filename_start)); - + if (const auto folder = path(filename).parent_path(); !folder.string().empty()) { // Checking for existence first prevents an error if the top-level folder is a softlink if (error_code error; exists(folder, error)) { return true; @@ -51,67 +46,64 @@ bool PiscsiImage::CreateImageFolder(const CommandContext& context, const string& return ChangeOwner(context, folder, false); } catch(const filesystem_error& e) { - return context.ReturnStatus(false, "Can't create image folder '" + string(folder) + "': " + e.what()); + return context.ReturnErrorStatus("Can't create image folder '" + folder.string() + "': " + e.what()); } } return true; } -string PiscsiImage::SetDefaultFolder(const string& f) +string PiscsiImage::SetDefaultFolder(string_view f) { if (f.empty()) { return "Can't set default image folder: Missing folder name"; } - string folder = f; - // If a relative path is specified, the path is assumed to be relative to the user's home directory - if (folder[0] != '/') { - folder = GetHomeDir() + "/" + folder; + path folder(f); + if (folder.is_relative()) { + folder = path(GetHomeDir() + "/" + folder.string()); } - else { - if (folder.find("/home/") != 0) { - return "Default image folder must be located in '/home/'"; - } + + if (path home_root = path(GetHomeDir()).parent_path(); !folder.string().starts_with(home_root.string())) { + return "Default image folder must be located in '" + home_root.string() + "'"; } // Resolve a potential symlink - auto p = path(folder); - if (error_code error; is_symlink(p, error)) { - p = read_symlink(p); + if (error_code error; is_symlink(folder, error)) { + folder = read_symlink(folder); } - if (error_code error; !is_directory(p, error)) { - return "'" + string(p) + "' is not a valid folder"; + if (error_code error; !is_directory(folder)) { + return string("'") + folder.string() + "' is not a valid image folder"; } - default_folder = string(p); + default_folder = folder.string(); - LOGINFO("Default image folder set to '%s'", default_folder.c_str()) + spdlog::info("Default image folder set to '" + default_folder + "'"); return ""; } -bool PiscsiImage::CreateImage(const CommandContext& context, const PbCommand& command) const +bool PiscsiImage::CreateImage(const CommandContext& context) const { - const string filename = GetParam(command, "file"); + const string filename = GetParam(context.GetCommand(), "file"); if (filename.empty()) { - return context.ReturnStatus(false, "Can't create image file: Missing image filename"); + return context.ReturnErrorStatus("Missing image filename"); } if (!CheckDepth(filename)) { - return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + filename + "'").c_str()); + return context.ReturnErrorStatus(("Invalid folder hierarchy depth '" + filename + "'").c_str()); } const string full_filename = GetFullName(filename); if (!IsValidDstFilename(full_filename)) { - return context.ReturnStatus(false, "Can't create image file: '" + full_filename + "': File already exists"); + return context.ReturnErrorStatus("Can't create image file: '" + full_filename + "': File already exists"); } - const string size = GetParam(command, "size"); + const string size = GetParam(context.GetCommand(), "size"); if (size.empty()) { - return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': Missing file size"); + return context.ReturnErrorStatus("Can't create image file '" + full_filename + "': Missing file size"); } off_t len; @@ -119,20 +111,20 @@ bool PiscsiImage::CreateImage(const CommandContext& context, const PbCommand& co len = stoull(size); } catch(const invalid_argument&) { - return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': Invalid file size " + size); + return context.ReturnErrorStatus("Can't create image file '" + full_filename + "': Invalid file size " + size); } catch(const out_of_range&) { - return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': Invalid file size " + size); + return context.ReturnErrorStatus("Can't create image file '" + full_filename + "': Invalid file size " + size); } if (len < 512 || (len & 0x1ff)) { - return context.ReturnStatus(false, "Invalid image file size " + to_string(len) + " (not a multiple of 512)"); + return context.ReturnErrorStatus("Invalid image file size " + to_string(len) + " (not a multiple of 512)"); } if (!CreateImageFolder(context, full_filename)) { return false; } - const bool read_only = GetParam(command, "read_only") == "true"; + const bool read_only = GetParam(context.GetCommand(), "read_only") == "true"; error_code error; path file(full_filename); @@ -149,40 +141,37 @@ bool PiscsiImage::CreateImage(const CommandContext& context, const PbCommand& co catch(const filesystem_error& e) { remove(file, error); - return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': " + e.what()); + return context.ReturnErrorStatus("Can't create image file '" + full_filename + "': " + e.what()); } - LOGINFO("%s", string("Created " + string(read_only ? "read-only " : "") + "image file '" + full_filename + - "' with a size of " + to_string(len) + " bytes").c_str()) + spdlog::info("Created " + string(read_only ? "read-only " : "") + "image file '" + full_filename + + "' with a size of " + to_string(len) + " bytes"); - return context.ReturnStatus(); + return context.ReturnSuccessStatus(); } -bool PiscsiImage::DeleteImage(const CommandContext& context, const PbCommand& command) const +bool PiscsiImage::DeleteImage(const CommandContext& context) const { - const string filename = GetParam(command, "file"); + const string filename = GetParam(context.GetCommand(), "file"); if (filename.empty()) { - return context.ReturnStatus(false, "Missing image filename"); + return context.ReturnErrorStatus("Missing image filename"); } if (!CheckDepth(filename)) { - return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + filename + "'").c_str()); + return context.ReturnErrorStatus("Invalid folder hierarchy depth '" + filename + "'"); } const auto full_filename = path(GetFullName(filename)); - if (!exists(full_filename)) { - return context.ReturnStatus(false, "Image file '" + string(full_filename) + "' does not exist"); + return context.ReturnErrorStatus("Image file '" + full_filename.string() + "' does not exist"); } - const auto [id, lun] = StorageDevice::GetIdsForReservedFile(full_filename); - if (id != -1 || lun != -1) { - return context.ReturnStatus(false, "Can't delete image file '" + string(full_filename) + - "', it is currently being used by device ID " + to_string(id) + ", LUN " + to_string(lun)); + if (!IsReservedFile(context, full_filename, "delete")) { + return false; } if (error_code error; !remove(full_filename, error)) { - return context.ReturnStatus(false, "Can't delete image file '" + string(full_filename) + "'"); + return context.ReturnErrorStatus("Can't delete image file '" + full_filename.string() + "'"); } // Delete empty subfolders @@ -196,32 +185,22 @@ bool PiscsiImage::DeleteImage(const CommandContext& context, const PbCommand& co } if (error_code error; !remove(full_folder)) { - return context.ReturnStatus(false, "Can't delete empty image folder '" + string(full_folder) + "'"); + return context.ReturnErrorStatus("Can't delete empty image folder '" + full_folder.string() + "'"); } last_slash = folder.rfind('/'); } - LOGINFO("Deleted image file '%s'", full_filename.c_str()) + spdlog::info("Deleted image file '" + full_filename.string() + "'"); - return context.ReturnStatus(); + return context.ReturnSuccessStatus(); } -bool PiscsiImage::RenameImage(const CommandContext& context, const PbCommand& command) const +bool PiscsiImage::RenameImage(const CommandContext& context) const { string from; string to; - if (!ValidateParams(context, command, "rename/move", from, to)) { - return false; - } - - const auto [id, lun] = StorageDevice::GetIdsForReservedFile(from); - if (id != -1 || lun != -1) { - return context.ReturnStatus(false, "Can't rename/move image file '" + from + - "', it is currently being used by device ID " + to_string(id) + ", LUN " + to_string(lun)); - } - - if (!CreateImageFolder(context, to)) { + if (!ValidateParams(context, "rename/move", from, to)) { return false; } @@ -229,33 +208,19 @@ bool PiscsiImage::RenameImage(const CommandContext& context, const PbCommand& co rename(path(from), path(to)); } catch(const filesystem_error& e) { - return context.ReturnStatus(false, "Can't rename/move image file '" + from + "' to '" + to + "': " + e.what()); + return context.ReturnErrorStatus("Can't rename/move image file '" + from + "': " + e.what()); } - LOGINFO("Renamed/Moved image file '%s' to '%s'", from.c_str(), to.c_str()) + spdlog::info("Renamed/Moved image file '" + from + "' to '" + to + "'"); - return context.ReturnStatus(); + return context.ReturnSuccessStatus(); } -bool PiscsiImage::CopyImage(const CommandContext& context, const PbCommand& command) const +bool PiscsiImage::CopyImage(const CommandContext& context) const { string from; string to; - if (!ValidateParams(context, command, "copy", from, to)) { - return false; - } - - if (access(from.c_str(), R_OK)) { - return context.ReturnStatus(false, "Can't read source image file '" + from + "'"); - } - - const auto [id, lun] = StorageDevice::GetIdsForReservedFile(from); - if (id != -1 || lun != -1) { - return context.ReturnStatus(false, "Can't copy image file '" + from + - "', it is currently being used by device ID " + to_string(id) + ", LUN " + to_string(lun)); - } - - if (!CreateImageFolder(context, to)) { + if (!ValidateParams(context, "copy", from, to)) { return false; } @@ -268,119 +233,138 @@ bool PiscsiImage::CopyImage(const CommandContext& context, const PbCommand& comm copy_symlink(f, t); } catch(const filesystem_error& e) { - return context.ReturnStatus(false, "Can't copy image file symlink '" + from + "': " + e.what()); + return context.ReturnErrorStatus("Can't copy image file symlink '" + from + "': " + e.what()); } - LOGINFO("Copied image file symlink '%s' to '%s'", from.c_str(), to.c_str()) + spdlog::info("Copied image file symlink '" + from + "' to '" + to + "'"); - return context.ReturnStatus(); + return context.ReturnSuccessStatus(); } try { copy_file(f, t); - permissions(t, GetParam(command, "read_only") == "true" ? + permissions(t, GetParam(context.GetCommand(), "read_only") == "true" ? perms::owner_read | perms::group_read | perms::others_read : perms::owner_read | perms::group_read | perms::others_read | perms::owner_write | perms::group_write); } catch(const filesystem_error& e) { - return context.ReturnStatus(false, "Can't copy image file '" + from + "' to '" + to + "': " + e.what()); + return context.ReturnErrorStatus("Can't copy image file '" + from + "': " + e.what()); } - LOGINFO("Copied image file '%s' to '%s'", from.c_str(), to.c_str()) + spdlog::info("Copied image file '" + from + "' to '" + to + "'"); - return context.ReturnStatus(); + return context.ReturnSuccessStatus(); } -bool PiscsiImage::SetImagePermissions(const CommandContext& context, const PbCommand& command) const +bool PiscsiImage::SetImagePermissions(const CommandContext& context) const { - string filename = GetParam(command, "file"); + const string filename = GetParam(context.GetCommand(), "file"); if (filename.empty()) { - return context.ReturnStatus(false, "Missing image filename"); + return context.ReturnErrorStatus("Missing image filename"); } if (!CheckDepth(filename)) { - return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + filename + "'").c_str()); + return context.ReturnErrorStatus("Invalid folder hierarchy depth '" + filename + "'"); } - filename = GetFullName(filename); - if (!IsValidSrcFilename(filename)) { - return context.ReturnStatus(false, "Can't modify image file '" + filename + "': Invalid name or type"); + const string full_filename = GetFullName(filename); + if (!IsValidSrcFilename(full_filename)) { + return context.ReturnErrorStatus("Can't modify image file '" + full_filename + "': Invalid name or type"); } - const bool protect = command.operation() == PROTECT_IMAGE; + const bool protect = context.GetCommand().operation() == PROTECT_IMAGE; + + if (protect && !IsReservedFile(context, full_filename, "protect")) { + return false; + } try { - permissions(path(filename), protect ? + permissions(path(full_filename), protect ? perms::owner_read | perms::group_read | perms::others_read : perms::owner_read | perms::group_read | perms::others_read | perms::owner_write | perms::group_write); } catch(const filesystem_error& e) { - return context.ReturnStatus(false, "Can't " + string(protect ? "protect" : "unprotect") + " image file '" + - filename + "': " + e.what()); + return context.ReturnErrorStatus("Can't " + string(protect ? "protect" : "unprotect") + " image file '" + + full_filename + "': " + e.what()); } - if (protect) { - LOGINFO("Protected image file '%s'", filename.c_str()) - } - else { - LOGINFO("Unprotected image file '%s'", filename.c_str()) - } + spdlog::info((protect ? "Protected" : "Unprotected") + string(" image file '") + full_filename + "'"); - return context.ReturnStatus(); + return context.ReturnSuccessStatus(); } -bool PiscsiImage::ValidateParams(const CommandContext& context, const PbCommand& command, const string& operation, - string& from, string& to) const +bool PiscsiImage::IsReservedFile(const CommandContext& context, const string& file, const string& op) { - from = GetParam(command, "from"); - if (from.empty()) { - return context.ReturnStatus(false, "Can't " + operation + " image file: Missing source filename"); - } - - if (!CheckDepth(from)) { - return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + from + "'").c_str()); - } - - from = GetFullName(from); - if (!IsValidSrcFilename(from)) { - return context.ReturnStatus(false, "Can't " + operation + " image file: '" + from + "': Invalid name or type"); - } - - to = GetParam(command, "to"); - if (to.empty()) { - return context.ReturnStatus(false, "Can't " + operation + " image file '" + from + "': Missing destination filename"); - } - - if (!CheckDepth(to)) { - return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + to + "'").c_str()); - } - - to = GetFullName(to); - if (!IsValidDstFilename(to)) { - return context.ReturnStatus(false, "Can't " + operation + " image file '" + from + "' to '" + to + "': File already exists"); + const auto [id, lun] = StorageDevice::GetIdsForReservedFile(file); + if (id != -1) { + return context.ReturnErrorStatus("Can't " + op + " image file '" + file + + "', it is currently being used by device " + to_string(id) + ":" + to_string(lun)); } return true; } -bool PiscsiImage::IsValidSrcFilename(const string& filename) +bool PiscsiImage::ValidateParams(const CommandContext& context, const string& op, string& from, string& to) const +{ + from = GetParam(context.GetCommand(), "from"); + if (from.empty()) { + return context.ReturnErrorStatus("Can't " + op + " image file: Missing source filename"); + } + + if (!CheckDepth(from)) { + return context.ReturnErrorStatus("Invalid folder hierarchy depth '" + from + "'"); + } + + to = GetParam(context.GetCommand(), "to"); + if (to.empty()) { + return context.ReturnErrorStatus("Can't " + op + " image file '" + from + "': Missing destination filename"); + } + + if (!CheckDepth(to)) { + return context.ReturnErrorStatus("Invalid folder hierarchy depth '" + to + "'"); + } + + from = GetFullName(from); + if (!IsValidSrcFilename(from)) { + return context.ReturnErrorStatus("Can't " + op + " image file: '" + from + "': Invalid name or type"); + } + + to = GetFullName(to); + if (!IsValidDstFilename(to)) { + return context.ReturnErrorStatus("Can't " + op + " image file '" + from + "' to '" + to + "': File already exists"); + } + + if (!IsReservedFile(context, from, op)) { + return false; + } + + if (!CreateImageFolder(context, to)) { + return false; + } + + return true; +} + +bool PiscsiImage::IsValidSrcFilename(string_view filename) { // Source file must exist and must be a regular file or a symlink path file(filename); - return is_regular_file(file) || is_symlink(file); + + error_code error; + return is_regular_file(file, error) || is_symlink(file, error); } -bool PiscsiImage::IsValidDstFilename(const string& filename) +bool PiscsiImage::IsValidDstFilename(string_view filename) { // Destination file must not yet exist try { return !exists(path(filename)); } catch(const filesystem_error&) { - return true; + return false; } } @@ -394,7 +378,7 @@ bool PiscsiImage::ChangeOwner(const CommandContext& context, const path& filenam error_code error; remove(filename, error); - return context.ReturnStatus(false, "Can't change ownership of '" + string(filename) + "': " + strerror(e)); + return context.ReturnErrorStatus("Can't change ownership of '" + filename.string() + "': " + strerror(e)); } permissions(filename, read_only ? @@ -437,5 +421,5 @@ pair PiscsiImage::GetUidAndGid() gid = pwd.pw_gid; } - return make_pair(uid, gid); + return { uid, gid }; } diff --git a/cpp/piscsi/piscsi_image.h b/cpp/piscsi/piscsi_image.h index 4a820835..f53e894d 100644 --- a/cpp/piscsi/piscsi_image.h +++ b/cpp/piscsi/piscsi_image.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -28,22 +28,23 @@ public: void SetDepth(int d) { depth = d; } int GetDepth() const { return depth; } string GetDefaultFolder() const { return default_folder; } - string SetDefaultFolder(const string&); - bool CreateImage(const CommandContext&, const PbCommand&) const; - bool DeleteImage(const CommandContext&, const PbCommand&) const; - bool RenameImage(const CommandContext&, const PbCommand&) const; - bool CopyImage(const CommandContext&, const PbCommand&) const; - bool SetImagePermissions(const CommandContext&, const PbCommand&) const; + string SetDefaultFolder(string_view); + bool CreateImage(const CommandContext&) const; + bool DeleteImage(const CommandContext&) const; + bool RenameImage(const CommandContext&) const; + bool CopyImage(const CommandContext&) const; + bool SetImagePermissions(const CommandContext&) const; private: bool CheckDepth(string_view) const; string GetFullName(const string& filename) const { return default_folder + "/" + filename; } - bool CreateImageFolder(const CommandContext&, const string&) const; - bool ValidateParams(const CommandContext&, const PbCommand&, const string&, string&, string&) const; + bool CreateImageFolder(const CommandContext&, string_view) const; + static bool IsReservedFile(const CommandContext&, const string&, const string&); + bool ValidateParams(const CommandContext&, const string&, string&, string&) const; - static bool IsValidSrcFilename(const string&); - static bool IsValidDstFilename(const string&); + static bool IsValidSrcFilename(string_view); + static bool IsValidDstFilename(string_view); static bool ChangeOwner(const CommandContext&, const path&, bool); static string GetHomeDir(); static pair GetUidAndGid(); diff --git a/cpp/piscsi/piscsi_response.cpp b/cpp/piscsi/piscsi_response.cpp index 35f3ec51..28e7e1a2 100644 --- a/cpp/piscsi/piscsi_response.cpp +++ b/cpp/piscsi/piscsi_response.cpp @@ -3,50 +3,48 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "shared/log.h" +#include "controllers/controller_manager.h" #include "shared/protobuf_util.h" +#include "shared/network_util.h" +#include "shared/piscsi_util.h" #include "shared/piscsi_version.h" #include "devices/disk.h" -#include "devices/device_factory.h" -#include "generated/piscsi_interface.pb.h" #include "piscsi_response.h" +#include #include using namespace std; using namespace filesystem; using namespace piscsi_interface; +using namespace piscsi_util; +using namespace network_util; using namespace protobuf_util; -unique_ptr PiscsiResponse::GetDeviceProperties(const Device& device) const +void PiscsiResponse::GetDeviceProperties(const Device& device, PbDeviceProperties& properties) const { - auto properties = make_unique(); - - // Currently there is only a SCSI controller, i.e. there can always be 32 LUNs - properties->set_luns(32); - properties->set_read_only(device.IsReadOnly()); - properties->set_protectable(device.IsProtectable()); - properties->set_stoppable(device.IsStoppable()); - properties->set_removable(device.IsRemovable()); - properties->set_lockable(device.IsLockable()); - properties->set_supports_file(device.SupportsFile()); - properties->set_supports_params(device.SupportsParams()); + properties.set_luns(ControllerManager::GetScsiLunMax()); + properties.set_read_only(device.IsReadOnly()); + properties.set_protectable(device.IsProtectable()); + properties.set_stoppable(device.IsStoppable()); + properties.set_removable(device.IsRemovable()); + properties.set_lockable(device.IsLockable()); + properties.set_supports_file(device.SupportsFile()); + properties.set_supports_params(device.SupportsParams()); if (device.SupportsParams()) { - for (const auto& [key, value] : device_factory.GetDefaultParams(device.GetType())) { - auto& map = *properties->mutable_default_params(); + for (const auto& [key, value] : device.GetDefaultParams()) { + auto& map = *properties.mutable_default_params(); map[key] = value; } } for (const auto& block_size : device_factory.GetSectorSizes(device.GetType())) { - properties->add_block_sizes(block_size); + properties.add_block_sizes(block_size); } - - return properties; } void PiscsiResponse::GetDeviceTypeProperties(PbDeviceTypesInfo& device_types_info, PbDeviceType type) const @@ -54,10 +52,10 @@ void PiscsiResponse::GetDeviceTypeProperties(PbDeviceTypesInfo& device_types_inf auto type_properties = device_types_info.add_properties(); type_properties->set_type(type); const auto device = device_factory.CreateDevice(type, 0, ""); - type_properties->set_allocated_properties(GetDeviceProperties(*device).release()); -} //NOSONAR The allocated memory is managed by protobuf + GetDeviceProperties(*device, *type_properties->mutable_properties()); +} -void PiscsiResponse::GetAllDeviceTypeProperties(PbDeviceTypesInfo& device_types_info) const +void PiscsiResponse::GetDeviceTypesInfo(PbDeviceTypesInfo& device_types_info) const { // Start with 2 instead of 1. 1 was the removed SASI drive type. int ordinal = 2; @@ -78,16 +76,15 @@ void PiscsiResponse::GetDevice(const Device& device, PbDevice& pb_device, const pb_device.set_revision(device.GetRevision()); pb_device.set_type(device.GetType()); - pb_device.set_allocated_properties(GetDeviceProperties(device).release()); + GetDeviceProperties(device, *pb_device.mutable_properties()); - auto status = make_unique().release(); //NOSONAR The allocated memory is managed by protobuf - pb_device.set_allocated_status(status); + auto status = pb_device.mutable_status(); status->set_protected_(device.IsProtected()); status->set_stopped(device.IsStopped()); status->set_removed(device.IsRemoved()); status->set_locked(device.IsLocked()); - if (device.SupportsParams()) { //NOSONAR The allocated memory is managed by protobuf + if (device.SupportsParams()) { for (const auto& [key, value] : device.GetParams()) { SetParam(pb_device, key, value); } @@ -100,11 +97,9 @@ void PiscsiResponse::GetDevice(const Device& device, PbDevice& pb_device, const const auto storage_device = dynamic_cast(&device); if (storage_device != nullptr) { - auto image_file = make_unique().release(); - GetImageFile(*image_file, default_folder, device.IsReady() ? storage_device->GetFilename() : ""); - pb_device.set_allocated_file(image_file); + GetImageFile(*pb_device.mutable_file(), default_folder, device.IsReady() ? storage_device->GetFilename() : ""); } -} //NOSONAR The allocated memory is managed by protobuf +} bool PiscsiResponse::GetImageFile(PbImageFile& image_file, const string& default_folder, const string& filename) const { @@ -112,13 +107,13 @@ bool PiscsiResponse::GetImageFile(PbImageFile& image_file, const string& default image_file.set_name(filename); image_file.set_type(device_factory.GetTypeForFile(filename)); - const string f = filename[0] == '/' ? filename : default_folder + "/" + filename; + const path p(filename[0] == '/' ? filename : default_folder + "/" + filename); - image_file.set_read_only(access(f.c_str(), W_OK)); + image_file.set_read_only(access(p.c_str(), W_OK)); - // filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle files of more than 2 GiB - if (struct stat st; !stat(f.c_str(), &st) && !S_ISDIR(st.st_mode)) { - image_file.set_size(st.st_size); + error_code error; + if (is_regular_file(p, error) || (is_symlink(p, error) && !is_block_file(p, error))) { + image_file.set_size(file_size(p)); return true; } } @@ -127,92 +122,68 @@ bool PiscsiResponse::GetImageFile(PbImageFile& image_file, const string& default } void PiscsiResponse::GetAvailableImages(PbImageFilesInfo& image_files_info, const string& default_folder, - const string& folder, const string& folder_pattern, const string& file_pattern, int scan_depth) const + const string& folder_pattern, const string& file_pattern, int scan_depth) const { - if (scan_depth-- < 0) { + const path default_path(default_folder); + if (!is_directory(default_path)) { return; } - string folder_pattern_lower = folder_pattern; - transform(folder_pattern_lower.begin(), folder_pattern_lower.end(), folder_pattern_lower.begin(), ::tolower); + string folder_pattern_lower; + ranges::transform(folder_pattern, back_inserter(folder_pattern_lower), ::tolower); - string file_pattern_lower = file_pattern; - transform(file_pattern_lower.begin(), file_pattern_lower.end(), file_pattern_lower.begin(), ::tolower); + string file_pattern_lower; + ranges::transform(file_pattern, back_inserter(file_pattern_lower), ::tolower); - DIR *d = opendir(folder.c_str()); - if (d == nullptr) { - return; - } - - // C++ filesystem cannot be used here because gcc < 10.3.0 cannot handle files of more than 2 GiB - const dirent *dir; - while ((dir = readdir(d))) { - string filename = GetNextImageFile(dir, folder); - if (filename.empty()) { + for (auto it = recursive_directory_iterator(default_path, directory_options::follow_directory_symlink); + it != recursive_directory_iterator(); it++) { + if (it.depth() > scan_depth) { + it.disable_recursion_pending(); continue; } - string name_lower = dir->d_name; - if (!file_pattern.empty()) { - transform(name_lower.begin(), name_lower.end(), name_lower.begin(), ::tolower); - } + const string parent = it->path().parent_path().string(); - if (dir->d_type == DT_DIR) { - if (folder_pattern_lower.empty() || name_lower.find(folder_pattern_lower) != string::npos) { - GetAvailableImages(image_files_info, default_folder, filename, folder_pattern, - file_pattern, scan_depth); - } + const string folder = parent.size() > default_folder.size() ? parent.substr(default_folder.size() + 1) : ""; + if (!FilterMatches(folder, folder_pattern_lower) || !FilterMatches(it->path().filename().string(), file_pattern_lower)) { continue; } - if (file_pattern_lower.empty() || name_lower.find(file_pattern_lower) != string::npos) { - if (auto image_file = make_unique(); GetImageFile(*image_file.get(), default_folder, filename)) { - GetImageFile(*image_files_info.add_image_files(), default_folder, - filename.substr(default_folder.length() + 1)); - } + if (!ValidateImageFile(it->path())) { + continue; + } + + const string filename = folder.empty() ? + it->path().filename().string() : folder + "/" + it->path().filename().string(); + if (PbImageFile image_file; GetImageFile(image_file, default_folder, filename)) { + GetImageFile(*image_files_info.add_image_files(), default_folder, filename); } } - - closedir(d); } -unique_ptr PiscsiResponse::GetAvailableImages(PbResult& result, const string& default_folder, +void PiscsiResponse::GetImageFilesInfo(PbImageFilesInfo& image_files_info, const string& default_folder, const string& folder_pattern, const string& file_pattern, int scan_depth) const { - auto image_files_info = make_unique(); + image_files_info.set_default_image_folder(default_folder); + image_files_info.set_depth(scan_depth); - image_files_info->set_default_image_folder(default_folder); - image_files_info->set_depth(scan_depth); - - GetAvailableImages(*image_files_info, default_folder, default_folder, folder_pattern, - file_pattern, scan_depth); - - result.set_status(true); - - return image_files_info; + GetAvailableImages(image_files_info, default_folder, folder_pattern, file_pattern, scan_depth); } -void PiscsiResponse::GetAvailableImages(PbResult& result, PbServerInfo& server_info, const string& default_folder, +void PiscsiResponse::GetAvailableImages(PbServerInfo& server_info, const string& default_folder, const string& folder_pattern, const string& file_pattern, int scan_depth) const { - auto image_files_info = GetAvailableImages(result, default_folder, folder_pattern, file_pattern, scan_depth); - image_files_info->set_default_image_folder(default_folder); - server_info.set_allocated_image_files_info(image_files_info.release()); + server_info.mutable_image_files_info()->set_default_image_folder(default_folder); - result.set_status(true); //NOSONAR The allocated memory is managed by protobuf + GetImageFilesInfo(*server_info.mutable_image_files_info(), default_folder, folder_pattern, file_pattern, scan_depth); } -unique_ptr PiscsiResponse::GetReservedIds(PbResult& result, const unordered_set& ids) const +void PiscsiResponse::GetReservedIds(PbReservedIdsInfo& reserved_ids_info, const unordered_set& ids) const { - auto reserved_ids_info = make_unique(); for (const int id : ids) { - reserved_ids_info->add_ids(id); + reserved_ids_info.add_ids(id); } - - result.set_status(true); - - return reserved_ids_info; } void PiscsiResponse::GetDevices(const unordered_set>& devices, PbServerInfo& server_info, @@ -232,7 +203,7 @@ void PiscsiResponse::GetDevicesInfo(const unordered_setGetId(), device->GetLun())); + id_sets.insert({ device->GetId(), device->GetLun() }); } } // Otherwise get information on the devices provided in the command @@ -243,8 +214,7 @@ void PiscsiResponse::GetDevicesInfo(const unordered_set(); - + auto devices_info = result.mutable_devices_info(); for (const auto& [id, lun] : id_sets) { for (const auto& d : devices) { if (d->GetId() == id && d->GetLun() == lun) { @@ -254,244 +224,239 @@ void PiscsiResponse::GetDevicesInfo(const unordered_set PiscsiResponse::GetDeviceTypesInfo(PbResult& result) const +void PiscsiResponse::GetServerInfo(PbServerInfo& server_info, const PbCommand& command, + const unordered_set>& devices, const unordered_set& reserved_ids, + const string& default_folder, int scan_depth) const { - auto device_types_info = make_unique(); - - GetAllDeviceTypeProperties(*device_types_info); - - result.set_status(true); - - return device_types_info; -} - -unique_ptr PiscsiResponse::GetServerInfo(const unordered_set>& devices, - PbResult& result, const unordered_set& reserved_ids, const string& current_log_level, - const string& default_folder, const string& folder_pattern, const string& file_pattern, int scan_depth) const -{ - auto server_info = make_unique(); - - server_info->set_allocated_version_info(GetVersionInfo(result).release()); - server_info->set_allocated_log_level_info(GetLogLevelInfo(result, current_log_level).release()); //NOSONAR The allocated memory is managed by protobuf - GetAllDeviceTypeProperties(*server_info->mutable_device_types_info()); //NOSONAR The allocated memory is managed by protobuf - GetAvailableImages(result, *server_info, default_folder, folder_pattern, file_pattern, scan_depth); - server_info->set_allocated_network_interfaces_info(GetNetworkInterfacesInfo(result).release()); - server_info->set_allocated_mapping_info(GetMappingInfo(result).release()); //NOSONAR The allocated memory is managed by protobuf - GetDevices(devices, *server_info, default_folder); //NOSONAR The allocated memory is managed by protobuf - server_info->set_allocated_reserved_ids_info(GetReservedIds(result, reserved_ids).release()); - server_info->set_allocated_operation_info(GetOperationInfo(result, scan_depth).release()); //NOSONAR The allocated memory is managed by protobuf - - result.set_status(true); - - return server_info; -} - -unique_ptr PiscsiResponse::GetVersionInfo(PbResult& result) const -{ - auto version_info = make_unique(); - - version_info->set_major_version(piscsi_major_version); - version_info->set_minor_version(piscsi_minor_version); - version_info->set_patch_version(piscsi_patch_version); - - result.set_status(true); - - return version_info; -} - -unique_ptr PiscsiResponse::GetLogLevelInfo(PbResult& result, const string& current_log_level) const -{ - auto log_level_info = make_unique(); - - for (const auto& log_level : log_levels) { - log_level_info->add_log_levels(log_level); + const vector command_operations = Split(GetParam(command, "operations"), ','); + set> operations; + for (const string& operation : command_operations) { + string op; + ranges::transform(operation, back_inserter(op), ::toupper); + operations.insert(op); } - log_level_info->set_current_log_level(current_log_level); - - result.set_status(true); - - return log_level_info; -} - -unique_ptr PiscsiResponse::GetNetworkInterfacesInfo(PbResult& result) const -{ - auto network_interfaces_info = make_unique(); - - for (const auto& network_interface : device_factory.GetNetworkInterfaces()) { - network_interfaces_info->add_name(network_interface); + if (!operations.empty()) { + spdlog::trace("Requested operation(s): " + Join(operations, ",")); } - result.set_status(true); + if (HasOperation(operations, PbOperation::VERSION_INFO)) { + GetVersionInfo(*server_info.mutable_version_info()); + } - return network_interfaces_info; + if (HasOperation(operations, PbOperation::LOG_LEVEL_INFO)) { + GetLogLevelInfo(*server_info.mutable_log_level_info()); + } + + if (HasOperation(operations, PbOperation::DEVICE_TYPES_INFO)) { + GetDeviceTypesInfo(*server_info.mutable_device_types_info()); + } + + if (HasOperation(operations, PbOperation::DEFAULT_IMAGE_FILES_INFO)) { + GetAvailableImages(server_info, default_folder, GetParam(command, "folder_pattern"), + GetParam(command, "file_pattern"), scan_depth); + } + + if (HasOperation(operations, PbOperation::NETWORK_INTERFACES_INFO)) { + GetNetworkInterfacesInfo(*server_info.mutable_network_interfaces_info()); + } + + if (HasOperation(operations, PbOperation::MAPPING_INFO)) { + GetMappingInfo(*server_info.mutable_mapping_info()); + } + + if (HasOperation(operations, PbOperation::STATISTICS_INFO)) { + GetStatisticsInfo(*server_info.mutable_statistics_info(), devices); + } + + if (HasOperation(operations, PbOperation::DEVICES_INFO)) { + GetDevices(devices, server_info, default_folder); + } + + if (HasOperation(operations, PbOperation::RESERVED_IDS_INFO)) { + GetReservedIds(*server_info.mutable_reserved_ids_info(), reserved_ids); + } + + if (HasOperation(operations, PbOperation::OPERATION_INFO)) { + GetOperationInfo(*server_info.mutable_operation_info(), scan_depth); + } } -unique_ptr PiscsiResponse::GetMappingInfo(PbResult& result) const +void PiscsiResponse::GetVersionInfo(PbVersionInfo& version_info) const { - auto mapping_info = make_unique(); + version_info.set_major_version(piscsi_major_version); + version_info.set_minor_version(piscsi_minor_version); + version_info.set_patch_version(piscsi_patch_version); +} +void PiscsiResponse::GetLogLevelInfo(PbLogLevelInfo& log_level_info) const +{ + for (const auto& log_level : spdlog::level::level_string_views) { + log_level_info.add_log_levels(log_level.data()); + } + + log_level_info.set_current_log_level(spdlog::level::level_string_views[spdlog::get_level()].data()); +} + +void PiscsiResponse::GetNetworkInterfacesInfo(PbNetworkInterfacesInfo& network_interfaces_info) const +{ + for (const auto& network_interface : GetNetworkInterfaces()) { + network_interfaces_info.add_name(network_interface); + } +} + +void PiscsiResponse::GetMappingInfo(PbMappingInfo& mapping_info) const +{ for (const auto& [name, type] : device_factory.GetExtensionMapping()) { - (*mapping_info->mutable_mapping())[name] = type; + (*mapping_info.mutable_mapping())[name] = type; } - - result.set_status(true); - - return mapping_info; } -unique_ptr PiscsiResponse::GetOperationInfo(PbResult& result, int depth) const +void PiscsiResponse::GetStatisticsInfo(PbStatisticsInfo& statistics_info, + const unordered_set>& devices) const { - auto operation_info = make_unique(); - - auto operation = CreateOperation(*operation_info, ATTACH, "Attach device, device-specific parameters are required"); - AddOperationParameter(*operation, "name", "Image file name in case of a mass storage device").release(); - AddOperationParameter(*operation, "interface", "Comma-separated prioritized network interface list").release(); - AddOperationParameter(*operation, "inet", "IP address and netmask of the network bridge").release(); - AddOperationParameter(*operation, "cmd", "Print command for the printer device").release(); - operation.release(); - - CreateOperation(*operation_info, DETACH, "Detach device, device-specific parameters are required").release(); - - CreateOperation(*operation_info, DETACH_ALL, "Detach all devices").release(); - - CreateOperation(*operation_info, START, "Start device, device-specific parameters are required").release(); - - CreateOperation(*operation_info, STOP, "Stop device, device-specific parameters are required").release(); - - operation = CreateOperation(*operation_info, INSERT, "Insert medium, device-specific parameters are required"); - AddOperationParameter(*operation, "file", "Image file name", "", true).release(); - operation.release(); - - CreateOperation(*operation_info, EJECT, "Eject medium, device-specific parameters are required").release(); - - CreateOperation(*operation_info, PROTECT, "Protect medium, device-specific parameters are required").release(); - - CreateOperation(*operation_info, UNPROTECT, "Unprotect medium, device-specific parameters are required").release(); - - operation = CreateOperation(*operation_info, SERVER_INFO, "Get piscsi server information"); - if (depth) { - AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names").release(); + for (const auto& device : devices) { + for (const auto& statistics : device->GetStatistics()) { + auto s = statistics_info.add_statistics(); + s->set_id(statistics.id()); + s->set_unit(statistics.unit()); + s->set_category(statistics.category()); + s->set_key(statistics.key()); + s->set_value(statistics.value()); + } } - AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names").release(); - operation.release(); - - CreateOperation(*operation_info, VERSION_INFO, "Get piscsi server version").release(); - - CreateOperation(*operation_info, DEVICES_INFO, "Get information on attached devices").release(); - - CreateOperation(*operation_info, DEVICE_TYPES_INFO, "Get device properties by device type").release(); - - operation = CreateOperation(*operation_info, DEFAULT_IMAGE_FILES_INFO, "Get information on available image files"); - if (depth) { - AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names").release(); - } - AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names").release(); - operation.release(); - - operation = CreateOperation(*operation_info, IMAGE_FILE_INFO, "Get information on image file"); - AddOperationParameter(*operation, "file", "Image file name", "", true).release(); - operation.release(); - - CreateOperation(*operation_info, LOG_LEVEL_INFO, "Get log level information").release(); - - CreateOperation(*operation_info, NETWORK_INTERFACES_INFO, "Get the available network interfaces").release(); - - CreateOperation(*operation_info, MAPPING_INFO, "Get mapping of extensions to device types").release(); - - CreateOperation(*operation_info, RESERVED_IDS_INFO, "Get list of reserved device IDs").release(); - - operation = CreateOperation(*operation_info, DEFAULT_FOLDER, "Set default image file folder"); - AddOperationParameter(*operation, "folder", "Default image file folder name", "", true).release(); - operation.release(); - - operation = CreateOperation(*operation_info, LOG_LEVEL, "Set log level"); - AddOperationParameter(*operation, "level", "New log level", "", true).release(); - operation.release(); - - operation = CreateOperation(*operation_info, RESERVE_IDS, "Reserve device IDs"); - AddOperationParameter(*operation, "ids", "Comma-separated device ID list", "", true).release(); - operation.release(); - - operation = CreateOperation(*operation_info, SHUT_DOWN, "Shut down or reboot"); - auto parameter = AddOperationParameter(*operation, "mode", "Shutdown mode", "", true).release(); - parameter->add_permitted_values("piscsi"); - // System shutdown/reboot requires root permissions - if (!getuid()) { - parameter->add_permitted_values("system"); - parameter->add_permitted_values("reboot"); - } - operation.release(); - - operation = CreateOperation(*operation_info, CREATE_IMAGE, "Create an image file"); - AddOperationParameter(*operation, "file", "Image file name", "", true).release(); - AddOperationParameter(*operation, "size", "Image file size in bytes", "", true).release(); - parameter = AddOperationParameter(*operation, "read_only", "Read-only flag", "false").release(); - parameter->add_permitted_values("true"); - parameter->add_permitted_values("false"); - operation.release(); - - operation = CreateOperation(*operation_info, DELETE_IMAGE, "Delete image file"); - AddOperationParameter(*operation, "file", "Image file name", "", true).release(); - operation.release(); - - operation = CreateOperation(*operation_info, RENAME_IMAGE, "Rename image file"); - AddOperationParameter(*operation, "from", "Source image file name", "", true).release(); - AddOperationParameter(*operation, "to", "Destination image file name", "", true).release(); - operation.release(); - - operation = CreateOperation(*operation_info, COPY_IMAGE, "Copy image file"); - AddOperationParameter(*operation, "from", "Source image file name", "", true).release(); - AddOperationParameter(*operation, "to", "Destination image file name", "", true).release(); - parameter = AddOperationParameter(*operation, "read_only", "Read-only flag", "false").release(); - parameter->add_permitted_values("true"); - parameter->add_permitted_values("false"); - operation.release(); - - operation = CreateOperation(*operation_info, PROTECT_IMAGE, "Write-protect image file"); - AddOperationParameter(*operation, "file", "Image file name", "", true).release(); - operation.release(); - - operation = CreateOperation(*operation_info, UNPROTECT_IMAGE, "Make image file writable"); - AddOperationParameter(*operation, "file", "Image file name", "", true).release(); - operation.release(); - - operation = CreateOperation(*operation_info, CHECK_AUTHENTICATION, "Check whether an authentication token is valid"); - AddOperationParameter(*operation, "token", "Authentication token to be checked", "", true).release(); - operation.release(); - - CreateOperation(*operation_info, OPERATION_INFO, "Get operation meta data").release(); - - result.set_status(true); - - return operation_info; } -unique_ptr PiscsiResponse::CreateOperation(PbOperationInfo& operation_info, const PbOperation& operation, +void PiscsiResponse::GetOperationInfo(PbOperationInfo& operation_info, int depth) const +{ + auto operation = CreateOperation(operation_info, ATTACH, "Attach device, device-specific parameters are required"); + AddOperationParameter(*operation, "name", "Image file name in case of a mass storage device"); + AddOperationParameter(*operation, "interface", "Comma-separated prioritized network interface list"); + AddOperationParameter(*operation, "inet", "IP address and netmask of the network bridge"); + AddOperationParameter(*operation, "cmd", "Print command for the printer device"); + + CreateOperation(operation_info, DETACH, "Detach device, device-specific parameters are required"); + + CreateOperation(operation_info, DETACH_ALL, "Detach all devices"); + + CreateOperation(operation_info, START, "Start device, device-specific parameters are required"); + + CreateOperation(operation_info, STOP, "Stop device, device-specific parameters are required"); + + operation = CreateOperation(operation_info, INSERT, "Insert medium, device-specific parameters are required"); + AddOperationParameter(*operation, "file", "Image file name", "", true); + + CreateOperation(operation_info, EJECT, "Eject medium, device-specific parameters are required"); + + CreateOperation(operation_info, PROTECT, "Protect medium, device-specific parameters are required"); + + CreateOperation(operation_info, UNPROTECT, "Unprotect medium, device-specific parameters are required"); + + operation = CreateOperation(operation_info, SERVER_INFO, "Get piscsi server information"); + if (depth) { + AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names"); + } + AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names"); + + CreateOperation(operation_info, VERSION_INFO, "Get piscsi server version"); + + CreateOperation(operation_info, DEVICES_INFO, "Get information on attached devices"); + + CreateOperation(operation_info, DEVICE_TYPES_INFO, "Get device properties by device type"); + + operation = CreateOperation(operation_info, DEFAULT_IMAGE_FILES_INFO, "Get information on available image files"); + if (depth) { + AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names"); + } + AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names"); + + operation = CreateOperation(operation_info, IMAGE_FILE_INFO, "Get information on image file"); + AddOperationParameter(*operation, "file", "Image file name", "", true); + + CreateOperation(operation_info, LOG_LEVEL_INFO, "Get log level information"); + + CreateOperation(operation_info, NETWORK_INTERFACES_INFO, "Get the available network interfaces"); + + CreateOperation(operation_info, MAPPING_INFO, "Get mapping of extensions to device types"); + + CreateOperation(operation_info, STATISTICS_INFO, "Get statistics"); + + CreateOperation(operation_info, RESERVED_IDS_INFO, "Get list of reserved device IDs"); + + operation = CreateOperation(operation_info, DEFAULT_FOLDER, "Set default image file folder"); + AddOperationParameter(*operation, "folder", "Default image file folder name", "", true); + + operation = CreateOperation(operation_info, LOG_LEVEL, "Set log level"); + AddOperationParameter(*operation, "level", "New log level", "", true); + + operation = CreateOperation(operation_info, RESERVE_IDS, "Reserve device IDs"); + AddOperationParameter(*operation, "ids", "Comma-separated device ID list", "", true); + + operation = CreateOperation(operation_info, SHUT_DOWN, "Shut down or reboot"); + if (getuid()) { + AddOperationParameter(*operation, "mode", "Shutdown mode", "", true, { "rascsi" } ); + } + else { + // System shutdown/reboot requires root permissions + AddOperationParameter(*operation, "mode", "Shutdown mode", "", true, { "rascsi", "system", "reboot" } ); + } + + operation = CreateOperation(operation_info, CREATE_IMAGE, "Create an image file"); + AddOperationParameter(*operation, "file", "Image file name", "", true); + AddOperationParameter(*operation, "size", "Image file size in bytes", "", true); + AddOperationParameter(*operation, "read_only", "Read-only flag", "false", false, { "true", "false" } ); + + operation = CreateOperation(operation_info, DELETE_IMAGE, "Delete image file"); + AddOperationParameter(*operation, "file", "Image file name", "", true); + + operation = CreateOperation(operation_info, RENAME_IMAGE, "Rename image file"); + AddOperationParameter(*operation, "from", "Source image file name", "", true); + AddOperationParameter(*operation, "to", "Destination image file name", "", true); + + operation = CreateOperation(operation_info, COPY_IMAGE, "Copy image file"); + AddOperationParameter(*operation, "from", "Source image file name", "", true); + AddOperationParameter(*operation, "to", "Destination image file name", "", true); + AddOperationParameter(*operation, "read_only", "Read-only flag", "false", false, { "true", "false" } ); + + operation = CreateOperation(operation_info, PROTECT_IMAGE, "Write-protect image file"); + AddOperationParameter(*operation, "file", "Image file name", "", true); + + operation = CreateOperation(operation_info, UNPROTECT_IMAGE, "Make image file writable"); + AddOperationParameter(*operation, "file", "Image file name", "", true); + + operation = CreateOperation(operation_info, CHECK_AUTHENTICATION, "Check whether an authentication token is valid"); + AddOperationParameter(*operation, "token", "Authentication token to be checked", "", true); + + CreateOperation(operation_info, OPERATION_INFO, "Get operation meta data"); +} + +// This method returns a raw pointer because protobuf does not have support for smart pointers +PbOperationMetaData *PiscsiResponse::CreateOperation(PbOperationInfo& operation_info, const PbOperation& operation, const string& description) const { - auto meta_data = make_unique(); - meta_data->set_server_side_name(PbOperation_Name(operation)); - meta_data->set_description(description); + PbOperationMetaData meta_data; + meta_data.set_server_side_name(PbOperation_Name(operation)); + meta_data.set_description(description); int ordinal = PbOperation_descriptor()->FindValueByName(PbOperation_Name(operation))->index(); - (*operation_info.mutable_operations())[ordinal] = *meta_data.release(); - return unique_ptr(&(*operation_info.mutable_operations())[ordinal]); + (*operation_info.mutable_operations())[ordinal] = meta_data; + return &(*operation_info.mutable_operations())[ordinal]; } -unique_ptr PiscsiResponse::AddOperationParameter(PbOperationMetaData& meta_data, - const string& name, const string& description, const string& default_value, bool is_mandatory) const +void PiscsiResponse::AddOperationParameter(PbOperationMetaData& meta_data, const string& name, + const string& description, const string& default_value, bool is_mandatory, + const vector& permitted_values) const { - auto parameter = unique_ptr(meta_data.add_parameters()); + auto parameter = meta_data.add_parameters(); parameter->set_name(name); parameter->set_description(description); parameter->set_default_value(default_value); parameter->set_is_mandatory(is_mandatory); - - return parameter; + for (const auto& permitted_value : permitted_values) { + parameter->add_permitted_values(permitted_value); + } } set PiscsiResponse::MatchDevices(const unordered_set>& devices, PbResult& result, @@ -503,7 +468,7 @@ set PiscsiResponse::MatchDevices(const unordered_setGetId() == device.id() && d->GetLun() == device.unit()) { - id_sets.insert(make_pair(device.id(), device.unit())); + id_sets.insert({ device.id(), device.unit() }); has_device = true; break; } @@ -513,7 +478,7 @@ set PiscsiResponse::MatchDevices(const unordered_set PiscsiResponse::MatchDevices(const unordered_setd_type != DT_REG && dir->d_type != DT_DIR && dir->d_type != DT_LNK && dir->d_type != DT_BLK) - || dir->d_name[0] == '.') { - return ""; + if (path.filename().string().starts_with(".")) { + return false; } - const string filename = folder + "/" + dir->d_name; + filesystem::path p(path); - const bool file_exists = exists(path(filename)); - - // filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle files of more than 2 GiB - struct stat st; - stat(filename.c_str(), &st); - if (dir->d_type == DT_REG && file_exists && !st.st_size) { - LOGWARN("File '%s' in image folder '%s' is empty", dir->d_name, folder.c_str()) - return ""; + // Follow symlink + if (is_symlink(p)) { + p = read_symlink(p); + if (!exists(p)) { + spdlog::warn("Image file symlink '" + path.string() + "' is broken"); + return false; + } } - if (dir->d_type == DT_LNK && !file_exists) { - LOGWARN("Symlink '%s' in image folder '%s' is broken", dir->d_name, folder.c_str()) - return ""; + if (is_directory(p) || (is_other(p) && !is_block_file(p))) { + return false; } - return filename; + if (!is_block_file(p) && file_size(p) < 256) { + spdlog::warn("Image file '" + p.string() + "' is invalid"); + return false; + } + + return true; +} + +bool PiscsiResponse::FilterMatches(const string& input, string_view pattern_lower) +{ + if (!pattern_lower.empty()) { + string name_lower; + ranges::transform(input, back_inserter(name_lower), ::tolower); + + if (name_lower.find(pattern_lower) == string::npos) { + return false; + } + } + + return true; +} + +bool PiscsiResponse::HasOperation(const set>& operations, PbOperation operation) +{ + return operations.empty() || operations.contains(PbOperation_Name(operation)); } diff --git a/cpp/piscsi/piscsi_response.h b/cpp/piscsi/piscsi_response.h index 16a9b95f..b095416f 100644 --- a/cpp/piscsi/piscsi_response.h +++ b/cpp/piscsi/piscsi_response.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -11,53 +11,58 @@ #include "devices/device_factory.h" #include "devices/primary_device.h" +#include "shared/piscsi_util.h" #include "generated/piscsi_interface.pb.h" -#include -#include #include +#include +#include using namespace std; +using namespace filesystem; using namespace piscsi_interface; class PiscsiResponse { - using id_set = pair; - public: PiscsiResponse() = default; ~PiscsiResponse() = default; bool GetImageFile(PbImageFile&, const string&, const string&) const; - unique_ptr GetAvailableImages(PbResult&, const string&, const string&, const string&, int) const; - unique_ptr GetReservedIds(PbResult&, const unordered_set&) const; + void GetImageFilesInfo(PbImageFilesInfo&, const string&, const string&, const string&, int) const; + void GetReservedIds(PbReservedIdsInfo&, const unordered_set&) const; void GetDevices(const unordered_set>&, PbServerInfo&, const string&) const; void GetDevicesInfo(const unordered_set>&, PbResult&, const PbCommand&, const string&) const; - unique_ptr GetDeviceTypesInfo(PbResult&) const; - unique_ptr GetVersionInfo(PbResult&) const; - unique_ptr GetServerInfo(const unordered_set>&, PbResult&, const unordered_set&, - const string&, const string&, const string&, const string&, int) const; - unique_ptr GetNetworkInterfacesInfo(PbResult&) const; - unique_ptr GetMappingInfo(PbResult&) const; - unique_ptr GetLogLevelInfo(PbResult&, const string&) const; - unique_ptr GetOperationInfo(PbResult&, int) const; + void GetDeviceTypesInfo(PbDeviceTypesInfo&) const; + void GetVersionInfo(PbVersionInfo&) const; + void GetServerInfo(PbServerInfo&, const PbCommand&, const unordered_set>&, + const unordered_set&, const string&, int) const; + void GetNetworkInterfacesInfo(PbNetworkInterfacesInfo&) const; + void GetMappingInfo(PbMappingInfo&) const; + void GetLogLevelInfo(PbLogLevelInfo&) const; + void GetStatisticsInfo(PbStatisticsInfo&, const unordered_set>&) const; + void GetOperationInfo(PbOperationInfo&, int) const; private: - DeviceFactory device_factory; + inline static const vector EMPTY_VECTOR; - const inline static array log_levels = { "trace", "debug", "info", "warn", "err", "off" }; + // TODO Try to get rid of this field by having the device instead of the factory providing the device data + const DeviceFactory device_factory; - unique_ptr GetDeviceProperties(const Device&) const; + void GetDeviceProperties(const Device&, PbDeviceProperties&) const; void GetDevice(const Device&, PbDevice&, const string&) const; - void GetAllDeviceTypeProperties(PbDeviceTypesInfo&) const; void GetDeviceTypeProperties(PbDeviceTypesInfo&, PbDeviceType) const; - void GetAvailableImages(PbImageFilesInfo&, const string&, const string&, const string&, const string&, int) const; - void GetAvailableImages(PbResult& result, PbServerInfo&, const string&, const string&, const string&, int) const; - unique_ptr CreateOperation(PbOperationInfo&, const PbOperation&, const string&) const; - unique_ptr AddOperationParameter(PbOperationMetaData&, const string&, const string&, - const string& = "", bool = false) const; + void GetAvailableImages(PbImageFilesInfo&, const string&, const string&, const string&, int) const; + void GetAvailableImages(PbServerInfo&, const string&, const string&, const string&, int) const; + PbOperationMetaData *CreateOperation(PbOperationInfo&, const PbOperation&, const string&) const; + void AddOperationParameter(PbOperationMetaData&, const string&, const string&, + const string& = "", bool = false, const vector& = EMPTY_VECTOR) const; set MatchDevices(const unordered_set>&, PbResult&, const PbCommand&) const; - static string GetNextImageFile(const dirent *, const string&); + static bool ValidateImageFile(const path&); + + static bool FilterMatches(const string&, string_view); + + static bool HasOperation(const set>&, PbOperation); }; diff --git a/cpp/piscsi/piscsi_service.cpp b/cpp/piscsi/piscsi_service.cpp index 661b5f92..4f842def 100644 --- a/cpp/piscsi/piscsi_service.cpp +++ b/cpp/piscsi/piscsi_service.cpp @@ -3,140 +3,114 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "shared/log.h" #include "shared/piscsi_util.h" -#include "shared/protobuf_serializer.h" #include "shared/piscsi_exceptions.h" #include "command_context.h" -#include "localizer.h" #include "piscsi_service.h" +#include +#include #include +#include #include +#include -using namespace piscsi_interface; using namespace piscsi_util; -void PiscsiService::Cleanup() const +string PiscsiService::Init(const callback& cb, int port) { - running = false; + assert(service_socket == -1); - if (service_socket != -1) { - close(service_socket); - } -} - -bool PiscsiService::Init(const callback& cb, int port) -{ if (port <= 0 || port > 65535) { - return false; + return "Invalid port number " + to_string(port); } - // Create socket for monitor - sockaddr_in server = {}; service_socket = socket(PF_INET, SOCK_STREAM, 0); if (service_socket == -1) { - LOGERROR("Unable to create socket") - return false; + return "Unable to create service socket: " + string(strerror(errno)); } + if (const int yes = 1; setsockopt(service_socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) { + Stop(); + return "Can't reuse address"; + } + + sockaddr_in server = {}; server.sin_family = PF_INET; server.sin_port = htons((uint16_t)port); - server.sin_addr.s_addr = htonl(INADDR_ANY); - - // Allow address reuse - if (int yes = 1; setsockopt(service_socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) { - return false; + server.sin_addr.s_addr = INADDR_ANY; + if (bind(service_socket, reinterpret_cast(&server), sizeof(sockaddr_in)) < 0) { //NOSONAR bit_cast is not supported by the bullseye compiler + Stop(); + return "Port " + to_string(port) + " is in use, is piscsi already running?"; } - signal(SIGPIPE, SIG_IGN); - - if (bind(service_socket, (sockaddr *)&server, sizeof(sockaddr_in)) < 0) { - cerr << "Error: Port " << port << " is in use, is piscsi or rascsi already running?" << endl; - return false; + if (listen(service_socket, 2) == -1) { + Stop(); + return "Can't listen to service socket: " + string(strerror(errno)); } execute = cb; - monthread = thread(&PiscsiService::Execute, this); - monthread.detach(); + return ""; +} - // Interrupt handler settings - return signal(SIGINT, KillHandler) != SIG_ERR && signal(SIGHUP, KillHandler) != SIG_ERR - && signal(SIGTERM, KillHandler) != SIG_ERR; +void PiscsiService::Start() +{ + assert(service_socket != -1); + + service_thread = jthread([this] () { Execute(); } ); +} + +void PiscsiService::Stop() +{ + assert(service_socket != -1); + + shutdown(service_socket, SHUT_RD); + close(service_socket); + + service_socket = -1; } void PiscsiService::Execute() const { #ifdef __linux__ - // Scheduler Settings - sched_param schedparam; - schedparam.sched_priority = 0; + // Run this thread with very low priority + sched_param schedparam = { .sched_priority = 0 }; sched_setscheduler(0, SCHED_IDLE, &schedparam); #endif - // Set the affinity to a specific processor core - FixCpu(2); - - // Wait for the execution to start - const timespec ts = { .tv_sec = 0, .tv_nsec = 1000}; - while (!running) { - nanosleep(&ts, nullptr); - } - - // Set up the monitor socket to receive commands - listen(service_socket, 1); - - while (true) { - CommandContext context; - - try { - PbCommand command = ReadCommand(context); - if (context.IsValid()) { - execute(context, command); - } + // TODO Accept more than one command instead of closing the socket after a single command + while (service_socket != -1) { + const int fd = accept(service_socket, nullptr, nullptr); + if (fd != -1) { + ExecuteCommand(fd); + close(fd); } - catch(const io_exception& e) { - LOGWARN("%s", e.what()) - - // Fall through - } - - context.Cleanup(); } } -PbCommand PiscsiService::ReadCommand(CommandContext& context) const +void PiscsiService::ExecuteCommand(int fd) const { - // Wait for connection - sockaddr client = {}; - socklen_t socklen = sizeof(client); - const int fd = accept(service_socket, &client, &socklen); - if (fd == -1) { - throw io_exception("accept() failed"); + CommandContext context(fd); + try { + if (context.ReadCommand()) { + execute(context); + } } + catch(const io_exception& e) { + spdlog::warn(e.what()); - PbCommand command; - - // Read magic string - vector magic(6); - const size_t bytes_read = context.GetSerializer().ReadBytes(fd, magic); - if (!bytes_read) { - return command; + // Try to return an error message (this may fail if the exception was caused when returning the actual result) + PbResult result; + result.set_msg(e.what()); + try { + context.WriteResult(result); + } + catch(const io_exception&) { //NOSONAR Not handled on purpose + // Ignore + } } - - if (bytes_read != magic.size() || memcmp(magic.data(), "RASCSI", magic.size())) { - throw io_exception("Invalid magic"); - } - - // Fetch the command - context.GetSerializer().DeserializeMessage(fd, command); - - context.SetFd(fd); - - return command; } - diff --git a/cpp/piscsi/piscsi_service.h b/cpp/piscsi/piscsi_service.h index a880c6c8..03a956d7 100644 --- a/cpp/piscsi/piscsi_service.h +++ b/cpp/piscsi/piscsi_service.h @@ -3,49 +3,42 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once -#include "generated/piscsi_interface.pb.h" #include #include +#include class CommandContext; using namespace std; -using namespace piscsi_interface; class PiscsiService { - using callback = function; - - callback execute; - - int service_socket = -1; - - thread monthread; - - static inline volatile bool running = false; + using callback = function; public: PiscsiService() = default; ~PiscsiService() = default; - bool Init(const callback&, int); - void Cleanup() const; - - bool IsRunning() const { return running; } - void SetRunning(bool b) const { running = b; } + string Init(const callback&, int); + void Start(); + void Stop(); + bool IsRunning() const { return service_socket != -1 && service_thread.joinable(); } private: void Execute() const; + void ExecuteCommand(int) const; - PbCommand ReadCommand(CommandContext&) const; + callback execute; - static void KillHandler(int) { running = false; } + jthread service_thread; + + int service_socket = -1; }; diff --git a/cpp/piscsi_interface.proto b/cpp/piscsi_interface.proto index d99ead12..77706644 100644 --- a/cpp/piscsi_interface.proto +++ b/cpp/piscsi_interface.proto @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -23,7 +23,7 @@ package piscsi_interface; // The available device types enum PbDeviceType { UNDEFINED = 0; - // Non-removable SASI drive, not supported anymore + // Non-removable SASI drive, not supported anymore but must not be removed because of backwards compatibility SAHD = 1 [deprecated = true]; // Non-removable SCSI drive SCHD = 2; @@ -82,9 +82,12 @@ enum PbOperation { // Make medium writable (not possible for read-only media) UNPROTECT = 9; - // Gets the server information (PbServerInfo). Calling this operation should be avoided because it - // may return a lot of data. More specific other operations should be used instead. + // Gets the server information (PbServerInfo). Calling this operation without a list of operations should + // be avoided because this may return a lot of data. More specific other operations should be used instead. // Parameters: + // "operations": Optional case insensitive comma-separated list of operation names to return data for, + // e.g. "version_info,log_level_info". Unknown operation names are ignored. If this parameter is missing + // the full set of data supported by PbServerInfo is returned. // "folder_pattern": Optional filter, only folder names containing the case-insensitive pattern are returned // "file_pattern": Optional filter, only filenames containing the case-insensitive pattern are returned SERVER_INFO = 10; @@ -190,6 +193,9 @@ enum PbOperation { // Get operation meta data (PbOperationInfo) OPERATION_INFO = 31; + + // Get statistics (PbStatisticsInfo) + STATISTICS_INFO = 32; } // The operation parameter meta data. The parameter data type is provided by the protobuf API. @@ -286,7 +292,7 @@ message PbImageFile { string name = 1; // The assumed device type, based on the filename extension PbDeviceType type = 2; - // The file size in bytes, 0 for block devices + // The file size in bytes, 0 for block devices in /dev uint64 size = 3; bool read_only = 4; } @@ -311,6 +317,41 @@ message PbNetworkInterfacesInfo { repeated string name = 1; } +// Statistics categories ordered by increasing severity +enum PbStatisticsCategory { + CATEGORY_NONE = 0; + CATEGORY_INFO = 1; + CATEGORY_WARNING = 2; + CATEGORY_ERROR = 3; + } + +message PbStatistics { + PbStatisticsCategory category = 1; + // The device ID and LUN for this statistics item. Both are -1 if the item is not device specific. + int32 id = 2; + int32 unit = 3; + // A symbolic unique item name, may be used for I18N. Supported values and their categories: + // "read_error_count" (ERROR, SCHD/SCRM/SCMO/SCCD) + // "write_error_count" (ERROR, SCHD/SCRM/SCMO) + // "cache_miss_read_count" (INFO, SCHD/SCRM/SCMO/SCCD) + // "cache_miss_write_count" (INFO, SCHD/SCRM/SCMO) + // "sector_read_count" (INFO, SCHD/SCRM/SCMO/SCCD) + // "sector_write_count" (INFO, SCHD/SCRM/SCMO) + // "byte_read_count" (INFO, SCDP) + // "byte_write_count" (INFO, SCDP) + // "print_error_count" (ERROR, SCLP) + // "print_warning_count" (WARNING, SCLP) + // "file_print_count" (INFO, SCLP) + // "byte_receive_count" (INFO, SCLP) + string key = 4; + uint64 value = 5; +} + +// The information on collected statistics +message PbStatisticsInfo { + repeated PbStatistics statistics = 1; +} + // The device definition, sent from the client to the server message PbDeviceDefinition { int32 id = 1; @@ -407,6 +448,8 @@ message PbResult { PbReservedIdsInfo reserved_ids_info = 12; // The result of an OPERATION_INFO command PbOperationInfo operation_info = 13; + // The result of a STATISTICS_INFO command + PbStatisticsInfo statistics_info = 15; } } @@ -430,4 +473,6 @@ message PbServerInfo { PbDevicesInfo devices_info = 8; // The operation meta data PbOperationInfo operation_info = 9; + // The statistics + PbStatisticsInfo statistics_info = 10; } diff --git a/cpp/scsictl.cpp b/cpp/scsictl/scsictl.cpp similarity index 100% rename from cpp/scsictl.cpp rename to cpp/scsictl/scsictl.cpp diff --git a/cpp/scsictl/scsictl_commands.cpp b/cpp/scsictl/scsictl_commands.cpp index 99adb09e..53dea40b 100644 --- a/cpp/scsictl/scsictl_commands.cpp +++ b/cpp/scsictl/scsictl_commands.cpp @@ -3,26 +3,31 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- +#include "shared/network_util.h" #include "shared/piscsi_util.h" #include "shared/protobuf_util.h" #include "shared/piscsi_exceptions.h" #include "scsictl_commands.h" +#include +#include +#include #include #include #include -#include +#include using namespace std; using namespace piscsi_interface; +using namespace network_util; using namespace piscsi_util; using namespace protobuf_util; -bool ScsictlCommands::Execute(const string& log_level, const string& default_folder, const string& reserved_ids, - const string& image_params, const string& filename) +bool ScsictlCommands::Execute(string_view log_level, string_view default_folder, string_view reserved_ids, + string_view image_params, string_view filename) { switch(command.operation()) { case LOG_LEVEL: @@ -76,9 +81,15 @@ bool ScsictlCommands::Execute(const string& log_level, const string& default_fol case MAPPING_INFO: return CommandMappingInfo(); + case STATISTICS_INFO: + return CommandStatisticsInfo(); + case OPERATION_INFO: return CommandOperationInfo(); + case NO_OPERATION: + return false; + default: return SendCommand(); } @@ -112,8 +123,8 @@ bool ScsictlCommands::SendCommand() throw io_exception("Can't write magic"); } - serializer.SerializeMessage(fd, command); - serializer.DeserializeMessage(fd, result); + SerializeMessage(fd, command); + DeserializeMessage(fd, result); close(fd); @@ -137,27 +148,23 @@ bool ScsictlCommands::CommandDevicesInfo() return true; } -bool ScsictlCommands::CommandLogLevel(const string& log_level) +bool ScsictlCommands::CommandLogLevel(string_view log_level) { SetParam(command, "level", log_level); return SendCommand(); } -bool ScsictlCommands::CommandReserveIds(const string& reserved_ids) +bool ScsictlCommands::CommandReserveIds(string_view reserved_ids) { SetParam(command, "ids", reserved_ids); return SendCommand(); } -bool ScsictlCommands::CommandCreateImage(const string& image_params) +bool ScsictlCommands::CommandCreateImage(string_view image_params) { - if (const size_t separator_pos = image_params.find(COMPONENT_SEPARATOR); separator_pos != string::npos) { - SetParam(command, "file", string_view(image_params).substr(0, separator_pos)); - SetParam(command, "size", string_view(image_params).substr(separator_pos + 1)); - } - else { + if (!EvaluateParams(image_params, "file", "size")) { cerr << "Error: Invalid file descriptor '" << image_params << "', format is NAME:SIZE" << endl; return false; @@ -168,20 +175,16 @@ bool ScsictlCommands::CommandCreateImage(const string& image_params) return SendCommand(); } -bool ScsictlCommands::CommandDeleteImage(const string& filename) +bool ScsictlCommands::CommandDeleteImage(string_view filename) { SetParam(command, "file", filename); return SendCommand(); } -bool ScsictlCommands::CommandRenameImage(const string& image_params) +bool ScsictlCommands::CommandRenameImage(string_view image_params) { - if (const size_t separator_pos = image_params.find(COMPONENT_SEPARATOR); separator_pos != string::npos) { - SetParam(command, "from", string_view(image_params).substr(0, separator_pos)); - SetParam(command, "to", string_view(image_params).substr(separator_pos + 1)); - } - else { + if (!EvaluateParams(image_params, "from", "to")) { cerr << "Error: Invalid file descriptor '" << image_params << "', format is CURRENT_NAME:NEW_NAME" << endl; return false; @@ -190,13 +193,9 @@ bool ScsictlCommands::CommandRenameImage(const string& image_params) return SendCommand(); } -bool ScsictlCommands::CommandCopyImage(const string& image_params) +bool ScsictlCommands::CommandCopyImage(string_view image_params) { - if (const size_t separator_pos = image_params.find(COMPONENT_SEPARATOR); separator_pos != string::npos) { - SetParam(command, "from", string_view(image_params).substr(0, separator_pos)); - SetParam(command, "to", string_view(image_params).substr(separator_pos + 1)); - } - else { + if (!EvaluateParams(image_params, "from", "to")) { cerr << "Error: Invalid file descriptor '" << image_params << "', format is CURRENT_NAME:NEW_NAME" << endl; return false; @@ -205,7 +204,7 @@ bool ScsictlCommands::CommandCopyImage(const string& image_params) return SendCommand(); } -bool ScsictlCommands::CommandDefaultImageFolder(const string& folder) +bool ScsictlCommands::CommandDefaultImageFolder(string_view folder) { SetParam(command, "folder", folder); @@ -249,18 +248,45 @@ bool ScsictlCommands::CommandServerInfo() PbServerInfo server_info = result.server_info(); - cout << scsictl_display.DisplayVersionInfo(server_info.version_info()); - cout << scsictl_display.DisplayLogLevelInfo(server_info.log_level_info()); - cout << scsictl_display.DisplayImageFilesInfo(server_info.image_files_info()); - cout << scsictl_display.DisplayMappingInfo(server_info.mapping_info()); - cout << scsictl_display.DisplayNetworkInterfaces(server_info.network_interfaces_info()); - cout << scsictl_display.DisplayDeviceTypesInfo(server_info.device_types_info()); - cout << scsictl_display.DisplayReservedIdsInfo(server_info.reserved_ids_info()); - cout << scsictl_display.DisplayOperationInfo(server_info.operation_info()); + if (server_info.has_version_info()) { + cout << scsictl_display.DisplayVersionInfo(server_info.version_info()); + } - if (server_info.devices_info().devices_size()) { - list sorted_devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() }; - sorted_devices.sort([](const auto& a, const auto& b) { return a.id() < b.id() || a.unit() < b.unit(); }); + if (server_info.has_log_level_info()) { + cout << scsictl_display.DisplayLogLevelInfo(server_info.log_level_info()); + } + + if (server_info.has_image_files_info()) { + cout << scsictl_display.DisplayImageFilesInfo(server_info.image_files_info()); + } + + if (server_info.has_mapping_info()) { + cout << scsictl_display.DisplayMappingInfo(server_info.mapping_info()); + } + + if (server_info.has_network_interfaces_info()) { + cout << scsictl_display.DisplayNetworkInterfaces(server_info.network_interfaces_info()); + } + + if (server_info.has_device_types_info()) { + cout << scsictl_display.DisplayDeviceTypesInfo(server_info.device_types_info()); + } + + if (server_info.has_reserved_ids_info()) { + cout << scsictl_display.DisplayReservedIdsInfo(server_info.reserved_ids_info()); + } + + if (server_info.has_statistics_info()) { + cout << scsictl_display.DisplayStatisticsInfo(server_info.statistics_info()); + } + + if (server_info.has_operation_info()) { + cout << scsictl_display.DisplayOperationInfo(server_info.operation_info()); + } + + if (server_info.has_devices_info() && server_info.devices_info().devices_size()) { + vector sorted_devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() }; + ranges::sort(sorted_devices, [](const auto& a, const auto& b) { return a.id() < b.id() || a.unit() < b.unit(); }); cout << "Attached devices:\n"; @@ -283,7 +309,7 @@ bool ScsictlCommands::CommandDefaultImageFilesInfo() return true; } -bool ScsictlCommands::CommandImageFileInfo(const string& filename) +bool ScsictlCommands::CommandImageFileInfo(string_view filename) { SetParam(command, "file", filename); @@ -330,6 +356,15 @@ bool ScsictlCommands::CommandMappingInfo() return true; } +bool ScsictlCommands::CommandStatisticsInfo() +{ + SendCommand(); + + cout << scsictl_display.DisplayStatisticsInfo(result.statistics_info()) << flush; + + return true; +} + bool ScsictlCommands::CommandOperationInfo() { SendCommand(); @@ -339,15 +374,12 @@ bool ScsictlCommands::CommandOperationInfo() return true; } -bool ScsictlCommands::ResolveHostName(const string& host, sockaddr_in *addr) +bool ScsictlCommands::EvaluateParams(string_view image_params, const string& key1, const string& key2) { - addrinfo hints = {}; - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; + if (const auto& components = Split(string(image_params), COMPONENT_SEPARATOR, 2); components.size() == 2) { + SetParam(command, key1, components[0]); + SetParam(command, key2, components[1]); - if (addrinfo *result; !getaddrinfo(host.c_str(), nullptr, &hints, &result)) { - *addr = *(sockaddr_in *)(result->ai_addr); - freeaddrinfo(result); return true; } diff --git a/cpp/scsictl/scsictl_commands.h b/cpp/scsictl/scsictl_commands.h index b9e80963..4477a14c 100644 --- a/cpp/scsictl/scsictl_commands.h +++ b/cpp/scsictl/scsictl_commands.h @@ -3,13 +3,12 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once -#include "shared/protobuf_serializer.h" #include "generated/piscsi_interface.pb.h" #include "scsictl_display.h" #include @@ -26,40 +25,39 @@ public: : command(command), hostname(hostname), port(port) {} ~ScsictlCommands() = default; - bool Execute(const string&, const string&, const string&, const string&, const string&); + bool Execute(string_view, string_view, string_view, string_view, string_view); bool CommandDevicesInfo(); private: - bool CommandLogLevel(const string&); - bool CommandReserveIds(const string&); - bool CommandCreateImage(const string&); - bool CommandDeleteImage(const string&); - bool CommandRenameImage(const string&); - bool CommandCopyImage(const string&); - bool CommandDefaultImageFolder(const string&); + bool CommandLogLevel(string_view); + bool CommandReserveIds(string_view); + bool CommandCreateImage(string_view); + bool CommandDeleteImage(string_view); + bool CommandRenameImage(string_view); + bool CommandCopyImage(string_view); + bool CommandDefaultImageFolder(string_view); bool CommandDeviceInfo(); bool CommandDeviceTypesInfo(); bool CommandVersionInfo(); bool CommandServerInfo(); bool CommandDefaultImageFilesInfo(); - bool CommandImageFileInfo(const string&); + bool CommandImageFileInfo(string_view); bool CommandNetworkInterfacesInfo(); bool CommandLogLevelInfo(); bool CommandReservedIdsInfo(); bool CommandMappingInfo(); + bool CommandStatisticsInfo(); bool CommandOperationInfo(); bool SendCommand(); + bool EvaluateParams(string_view, const string&, const string&); - static bool ResolveHostName(const string&, sockaddr_in *); - - ProtobufSerializer serializer; PbCommand& command; string hostname; int port; PbResult result; - ScsictlDisplay scsictl_display; + [[no_unique_address]] const ScsictlDisplay scsictl_display; }; diff --git a/cpp/scsictl/scsictl_core.cpp b/cpp/scsictl/scsictl_core.cpp index db2dea62..6f6b8860 100644 --- a/cpp/scsictl/scsictl_core.cpp +++ b/cpp/scsictl/scsictl_core.cpp @@ -9,12 +9,12 @@ // //--------------------------------------------------------------------------- +#include "controllers/controller_manager.h" #include "controllers/scsi_controller.h" #include "shared/piscsi_util.h" #include "shared/protobuf_util.h" #include "shared/piscsi_exceptions.h" #include "shared/piscsi_version.h" -#include "generated/piscsi_interface.pb.h" #include "scsictl/scsictl_parser.h" #include "scsictl/scsictl_commands.h" #include "scsictl/scsictl_core.h" @@ -30,27 +30,27 @@ using namespace protobuf_util; void ScsiCtl::Banner(const vector& args) const { if (args.size() < 2) { - cout << piscsi_util::Banner("(Controller App)"); - - cout << "\nUsage: " << args[0] << " -i ID[:LUN] [-c CMD] [-C FILE] [-t TYPE] [-b BLOCK_SIZE] [-n NAME] [-f FILE|PARAM] "; - cout << "[-F IMAGE_FOLDER] [-L LOG_LEVEL] [-h HOST] [-p PORT] [-r RESERVED_IDS] "; - cout << "[-C FILENAME:FILESIZE] [-d FILENAME] [-w FILENAME] [-R CURRENT_NAME:NEW_NAME] "; - cout << "[-x CURRENT_NAME:NEW_NAME] [-z LOCALE] "; - cout << "[-e] [-E FILENAME] [-D] [-I] [-l] [-m] [o] [-O] [-P] [-s] [-v] [-V] [-y] [-X]\n"; - cout << " where ID[:LUN] ID := {0-7}, LUN := {0-31}, default is 0\n"; - cout << " CMD := {attach|detach|insert|eject|protect|unprotect|show}\n"; - cout << " TYPE := {schd|scrm|sccd|scmo|scbr|scdp} or convenience type {hd|rm|mo|cd|bridge|daynaport}\n"; - cout << " BLOCK_SIZE := {512|1024|2048|4096) bytes per hard disk drive block\n"; - cout << " NAME := name of device to attach (VENDOR:PRODUCT:REVISION)\n"; - cout << " FILE|PARAM := image file path or device-specific parameter\n"; - cout << " IMAGE_FOLDER := default location for image files, default is '~/images'\n"; - cout << " HOST := piscsi host to connect to, default is 'localhost'\n"; - cout << " PORT := piscsi port to connect to, default is 6868\n"; - cout << " RESERVED_IDS := comma-separated list of IDs to reserve\n"; - cout << " LOG_LEVEL := log level {trace|debug|info|warn|err|off}, default is 'info'\n"; - cout << " If CMD is 'attach' or 'insert' the FILE parameter is required.\n"; - cout << "Usage: " << args[0] << " -l\n"; - cout << " Print device list.\n" << flush; + cout << piscsi_util::Banner("(Controller App)") + << "\nUsage: " << args[0] << " -i ID[:LUN] [-c CMD] [-C FILE] [-t TYPE] [-b BLOCK_SIZE] [-n NAME] [-f FILE|PARAM] " + << "[-F IMAGE_FOLDER] [-L LOG_LEVEL] [-h HOST] [-p PORT] [-r RESERVED_IDS] " + << "[-C FILENAME:FILESIZE] [-d FILENAME] [-w FILENAME] [-R CURRENT_NAME:NEW_NAME] " + << "[-x CURRENT_NAME:NEW_NAME] [-z LOCALE] " + << "[-e] [-E FILENAME] [-D] [-I] [-l] [-m] [o] [-O] [-P] [-s] [-S] [-v] [-V] [-y] [-X]\n" + << " where ID[:LUN] ID := {0-" << (ControllerManager::GetScsiIdMax() - 1) << "}," + << " LUN := {0-" << (ControllerManager::GetScsiLunMax() - 1) << "}, default is 0\n" + << " CMD := {attach|detach|insert|eject|protect|unprotect|show}\n" + << " TYPE := {schd|scrm|sccd|scmo|scbr|scdp} or convenience type {hd|rm|mo|cd|bridge|daynaport}\n" + << " BLOCK_SIZE := {512|1024|2048|4096) bytes per hard disk drive block\n" + << " NAME := name of device to attach (VENDOR:PRODUCT:REVISION)\n" + << " FILE|PARAM := image file path or device-specific parameter\n" + << " IMAGE_FOLDER := default location for image files, default is '~/images'\n" + << " HOST := piscsi host to connect to, default is 'localhost'\n" + << " PORT := piscsi port to connect to, default is 6868\n" + << " RESERVED_IDS := comma-separated list of IDs to reserve\n" + << " LOG_LEVEL := log level {trace|debug|info|warn|err|off}, default is 'info'\n" + << " If CMD is 'attach' or 'insert' the FILE parameter is required.\n" + << "Usage: " << args[0] << " -l\n" + << " Print device list.\n" << flush; exit(EXIT_SUCCESS); } @@ -77,18 +77,15 @@ int ScsiCtl::run(const vector& args) const string token; bool list = false; - const char *locale = setlocale(LC_MESSAGES, ""); - if (locale == nullptr || !strcmp(locale, "C")) { - locale = "en"; - } + string locale = GetLocale(); opterr = 1; int opt; while ((opt = getopt(static_cast(args.size()), args.data(), - "e::lmos::vDINOTVXa:b:c:d:f:h:i:n:p:r:t:x:z:C:E:F:L:P::R:")) != -1) { + "e::lmos::vDINOSTVXa:b:c:d:f:h:i:n:p:r:t:x:z:C:E:F:L:P::R:")) != -1) { switch (opt) { case 'i': - if (const string error = SetIdAndLun(*device, optarg, ScsiController::LUN_MAX); !error.empty()) { + if (const string error = SetIdAndLun(*device, optarg); !error.empty()) { cerr << "Error: " << error << endl; exit(EXIT_FAILURE); } @@ -133,7 +130,7 @@ int ScsiCtl::run(const vector& args) const case 'e': command.set_operation(DEFAULT_IMAGE_FILES_INFO); if (optarg) { - SetPatternParams(command, optarg); + SetCommandParams(command, optarg); } break; @@ -210,13 +207,20 @@ int ScsiCtl::run(const vector& args) const case 's': command.set_operation(SERVER_INFO); - if (optarg) { - SetPatternParams(command, optarg); + if (optarg) { + if (const string error = SetCommandParams(command, optarg); !error.empty()) { + cerr << "Error: " << error << endl; + exit(EXIT_FAILURE); + } } break; + case 'S': + command.set_operation(STATISTICS_INFO); + break; + case 'v': - cout << "scsictl version: " << piscsi_get_version_string() << endl; + cout << "scsictl version: " << piscsi_get_version_string() << '\n'; exit(EXIT_SUCCESS); break; @@ -263,7 +267,7 @@ int ScsiCtl::run(const vector& args) const bool status; try { - // Listing devices is a special case (rasctl backwards compatibility) + // Listing devices is a special case (legacy rasctl backwards compatibility) if (list) { command.clear_devices(); command.set_operation(DEVICES_INFO); diff --git a/cpp/scsictl/scsictl_display.cpp b/cpp/scsictl/scsictl_display.cpp index 705fa6a2..7250910a 100644 --- a/cpp/scsictl/scsictl_display.cpp +++ b/cpp/scsictl/scsictl_display.cpp @@ -3,26 +3,29 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- +#include "shared/piscsi_util.h" #include "shared/protobuf_util.h" -#include "generated/piscsi_interface.pb.h" #include "scsictl_display.h" #include -#include +#include +#include +#include #include using namespace std; using namespace piscsi_interface; +using namespace piscsi_util; using namespace protobuf_util; string ScsictlDisplay::DisplayDevicesInfo(const PbDevicesInfo& devices_info) const { ostringstream s; - const list& devices = { devices_info.devices().begin(), devices_info.devices().end() }; + const vector devices(devices_info.devices().begin(), devices_info.devices().end()); s << ListDevices(devices); @@ -50,51 +53,33 @@ string ScsictlDisplay::DisplayDeviceInfo(const PbDevice& pb_device) const s << " "; - bool hasProperty = false; + vector properties; if (pb_device.properties().read_only()) { - s << "read-only"; - hasProperty = true; + properties.emplace_back("read-only"); } if (pb_device.properties().protectable() && pb_device.status().protected_()) { - if (hasProperty) { - s << ", "; - } - s << "protected"; - hasProperty = true; + properties.emplace_back("protected"); } if (pb_device.properties().stoppable() && pb_device.status().stopped()) { - if (hasProperty) { - s << ", "; - } - s << "stopped"; - hasProperty = true; + properties.emplace_back("stopped"); } if (pb_device.properties().removable() && pb_device.status().removed()) { - if (hasProperty) { - s << ", "; - } - s << "removed"; - hasProperty = true; + properties.emplace_back("removed"); } if (pb_device.properties().lockable() && pb_device.status().locked()) { - if (hasProperty) { - s << ", "; - } - s << "locked"; + properties.emplace_back("locked"); } - if (hasProperty) { - s << " "; + if (!properties.empty()) { + s << Join(properties) << " "; } - DisplayParams(s, pb_device); - - s << '\n'; + s << DisplayParams(pb_device) << '\n'; return s.str(); } @@ -103,8 +88,8 @@ string ScsictlDisplay::DisplayVersionInfo(const PbVersionInfo& version_info) con { ostringstream s; - s << "piscsi server version: " << setw(2) << setfill('0') << version_info.major_version() << "." - << setw(2) << setfill('0') << version_info.minor_version(); + s << "piscsi server version: " << setfill('0') << setw(2) << version_info.major_version() << "." + << setw(2) << version_info.minor_version(); if (version_info.patch_version() > 0) { s << "." << version_info.patch_version(); @@ -149,7 +134,7 @@ string ScsictlDisplay::DisplayDeviceTypesInfo(const PbDeviceTypesInfo& device_ty const PbDeviceProperties& properties = device_type_info.properties(); - DisplayAttributes(s, properties); + s << DisplayAttributes(properties); if (properties.supports_file()) { s << "Image file support\n "; @@ -159,9 +144,9 @@ string ScsictlDisplay::DisplayDeviceTypesInfo(const PbDeviceTypesInfo& device_ty s << "Parameter support\n "; } - DisplayDefaultParameters(s, properties); + s << DisplayDefaultParameters(properties); - DisplayBlockSizes(s, properties); + s << DisplayBlockSizes(properties); } s << '\n'; @@ -174,20 +159,8 @@ string ScsictlDisplay::DisplayReservedIdsInfo(const PbReservedIdsInfo& reserved_ ostringstream s; if (reserved_ids_info.ids_size()) { - s << "Reserved device IDs: "; - - for (int i = 0; i < reserved_ids_info.ids_size(); i++) { - if(i) { - s << ", "; - } - - s << reserved_ids_info.ids(i); - } - - s << '\n'; - } - else { - s << "No reserved device IDs\n"; + const set sorted_ids(reserved_ids_info.ids().begin(), reserved_ids_info.ids().end()); + s << "Reserved device IDs: " << Join(sorted_ids) << '\n'; } return s.str(); @@ -219,12 +192,9 @@ string ScsictlDisplay::DisplayImageFilesInfo(const PbImageFilesInfo& image_files s << "Default image file folder: " << image_files_info.default_image_folder() << '\n'; s << "Supported folder depth: " << image_files_info.depth() << '\n'; - if (image_files_info.image_files().empty()) { - s << " No image files available\n"; - } - else { - list image_files = { image_files_info.image_files().begin(), image_files_info.image_files().end() }; - image_files.sort([](const auto& a, const auto& b) { return a.name() < b.name(); }); + if (!image_files_info.image_files().empty()) { + vector image_files(image_files_info.image_files().begin(), image_files_info.image_files().end()); + ranges::sort(image_files, [](const auto& a, const auto& b) { return a.name() < b.name(); }); s << "Available image files:\n"; for (const auto& image_file : image_files) { @@ -241,24 +211,8 @@ string ScsictlDisplay::DisplayNetworkInterfaces(const PbNetworkInterfacesInfo& n { ostringstream s; - s << "Available (up) network interfaces:\n"; - - const list sorted_interfaces = { network_interfaces_info.name().begin(), network_interfaces_info.name().end() }; - - bool isFirst = true; - for (const auto& interface : sorted_interfaces) { - if (!isFirst) { - s << ", "; - } - else { - s << " "; - } - - isFirst = false; - s << interface; - } - - s << '\n'; + const set> sorted_interfaces(network_interfaces_info.name().begin(), network_interfaces_info.name().end()); + s << "Available (up) network interfaces: " << Join(sorted_interfaces) << '\n'; return s.str(); } @@ -269,20 +223,51 @@ string ScsictlDisplay::DisplayMappingInfo(const PbMappingInfo& mapping_info) con s << "Supported image file extension to device type mappings:\n"; - const map> sorted_mappings = { mapping_info.mapping().begin(), mapping_info.mapping().end() }; - - for (const auto& [extension, type] : sorted_mappings) { + for (const map> sorted_mappings(mapping_info.mapping().begin(), mapping_info.mapping().end()); + const auto& [extension, type] : sorted_mappings) { s << " " << extension << "->" << PbDeviceType_Name(type) << '\n'; } return s.str(); } +string ScsictlDisplay::DisplayStatisticsInfo(const PbStatisticsInfo& statistics_info) const +{ + ostringstream s; + + s << "Statistics:\n"; + + // Sort by ascending ID, LUN and key and by descending category + vector sorted_statistics = { statistics_info.statistics().begin(), statistics_info.statistics().end() }; + ranges::sort(sorted_statistics, [] (const PbStatistics& a, const PbStatistics& b) { + if (a.category() > b.category()) return true; + if (a.category() < b.category()) return false; + if (a.id() < b.id()) return true; + if (a.id() > b.id()) return false; + if (a.unit() < b.unit()) return true; + if (a.unit() > b.unit()) return false; + return a.key() < b.key(); + }); + + PbStatisticsCategory prev_category = PbStatisticsCategory::CATEGORY_NONE; + for (const auto& statistics : sorted_statistics) { + if (statistics.category() != prev_category) { + // Strip leading "CATEGORY_" + s << " " << PbStatisticsCategory_Name(statistics.category()).substr(9) << '\n'; + prev_category = statistics.category(); + } + + s << " " << statistics.id() << ":" << statistics.unit() << " " << statistics.key() << ": " << statistics.value() << '\n'; + } + + return s.str(); +} + string ScsictlDisplay::DisplayOperationInfo(const PbOperationInfo& operation_info) const { ostringstream s; - const map> operations = { operation_info.operations().begin(), operation_info.operations().end() }; + const map> operations(operation_info.operations().begin(), operation_info.operations().end()); // Copies result into a map sorted by operation name auto unknown_operation = make_unique(); @@ -308,7 +293,7 @@ string ScsictlDisplay::DisplayOperationInfo(const PbOperationInfo& operation_inf } s << '\n'; - DisplayParameters(s, meta_data); + s << DisplayParameters(meta_data); } else { s << " " << name << " (Unknown server-side operation)\n"; @@ -318,96 +303,82 @@ string ScsictlDisplay::DisplayOperationInfo(const PbOperationInfo& operation_inf return s.str(); } -void ScsictlDisplay::DisplayParams(ostringstream& s, const PbDevice& pb_device) const +string ScsictlDisplay::DisplayParams(const PbDevice& pb_device) const { - const map> sorted_params = { pb_device.params().begin(), pb_device.params().end() }; + ostringstream s; - bool isFirst = true; - for (const auto& [key, value] : sorted_params) { - if (!isFirst) { - s << ":"; - } - - isFirst = false; - s << key << "=" << value; - } -} - -void ScsictlDisplay::DisplayAttributes(ostringstream& s, const PbDeviceProperties& properties) const -{ - if (properties.read_only() || properties.protectable() || properties.stoppable() || properties.lockable()) { - s << "Properties: "; - - bool has_property = false; - - if (properties.read_only()) { - s << "read-only"; - has_property = true; - } - - if (properties.protectable()) { - s << (has_property ? ", " : "") << "protectable"; - has_property = true; - } - if (properties.stoppable()) { - s << (has_property ? ", " : "") << "stoppable"; - has_property = true; - } - if (properties.removable()) { - s << (has_property ? ", " : "") << "removable"; - has_property = true; - } - if (properties.lockable()) { - s << (has_property ? ", " : "") << "lockable"; - } - - s << "\n "; - } -} - -void ScsictlDisplay::DisplayDefaultParameters(ostringstream& s, const PbDeviceProperties& properties) const -{ - if (properties.supports_params() && properties.default_params_size()) { - s << "Default parameters: "; - - const map> sorted_params = { properties.default_params().begin(), properties.default_params().end() }; - - bool isFirst = true; - for (const auto& [key, value] : sorted_params) { - if (!isFirst) { - s << "\n "; - } - s << key << "=" << value; - - isFirst = false; - } + set> params; + for (const auto& [key, value] : pb_device.params()) { + params.insert(key + "=" + value); } + s << Join(params, ":"); + + return s.str(); } -void ScsictlDisplay::DisplayBlockSizes(ostringstream& s, const PbDeviceProperties& properties) const +string ScsictlDisplay::DisplayAttributes(const PbDeviceProperties& props) const { + ostringstream s; + + vector properties; + if (props.read_only()) { + properties.emplace_back("read-only"); + } + if (props.protectable()) { + properties.emplace_back("protectable"); + } + if (props.stoppable()) { + properties.emplace_back("stoppable"); + } + if (props.removable()) { + properties.emplace_back("removable"); + } + if (props.lockable()) { + properties.emplace_back("lockable"); + } + + if (!properties.empty()) { + s << "Properties: " << Join(properties) << "\n "; + } + + return s.str(); +} + +string ScsictlDisplay::DisplayDefaultParameters(const PbDeviceProperties& properties) const +{ + ostringstream s; + + if (!properties.default_params().empty()) { + set> params; + for (const auto& [key, value] : properties.default_params()) { + params.insert(key + "=" + value); + } + + s << "Default parameters: " << Join(params, "\n "); + } + + return s.str(); +} + +string ScsictlDisplay::DisplayBlockSizes(const PbDeviceProperties& properties) const +{ + ostringstream s; + if (properties.block_sizes_size()) { - s << "Configurable block sizes in bytes: "; - - const set sorted_sizes = { properties.block_sizes().begin(), properties.block_sizes().end() }; - - bool isFirst = true; - for (const auto& size : sorted_sizes) { - if (!isFirst) { - s << ", "; - } - s << size; - - isFirst = false; - } + const set sorted_sizes(properties.block_sizes().begin(), properties.block_sizes().end()); + s << "Configurable block sizes in bytes: " << Join(sorted_sizes); } + + return s.str(); } -void ScsictlDisplay::DisplayParameters(ostringstream& s, const PbOperationMetaData& meta_data) const +string ScsictlDisplay::DisplayParameters(const PbOperationMetaData& meta_data) const { - list sorted_parameters = { meta_data.parameters().begin(), meta_data.parameters().end() }; - sorted_parameters.sort([](const auto& a, const auto& b) { return a.name() < b.name(); }); + vector sorted_parameters(meta_data.parameters().begin(), meta_data.parameters().end()); + ranges::sort(sorted_parameters, [](const auto& a, const auto& b) { return a.name() < b.name(); }); + + ostringstream s; for (const auto& parameter : sorted_parameters) { s << " " << parameter.name() << ": " @@ -418,30 +389,23 @@ void ScsictlDisplay::DisplayParameters(ostringstream& s, const PbOperationMetaDa } s << '\n'; - DisplayPermittedValues(s, parameter); + s << DisplayPermittedValues(parameter); if (!parameter.default_value().empty()) { s << " Default value: " << parameter.default_value() << '\n'; } } + + return s.str(); } -void ScsictlDisplay::DisplayPermittedValues(ostringstream& s, const PbOperationParameter& parameter) const +string ScsictlDisplay::DisplayPermittedValues(const PbOperationParameter& parameter) const { + ostringstream s; if (parameter.permitted_values_size()) { - s << " Permitted values: "; - - bool isFirst = true; - - for (const auto& permitted_value : parameter.permitted_values()) { - if (!isFirst) { - s << ", "; - } - - isFirst = false; - s << permitted_value; - } - - s << '\n'; + const set> sorted_values(parameter.permitted_values().begin(), parameter.permitted_values().end()); + s << " Permitted values: " << Join(parameter.permitted_values()) << '\n'; } + + return s.str(); } diff --git a/cpp/scsictl/scsictl_display.h b/cpp/scsictl/scsictl_display.h index 6a7f4c55..94ebcf93 100644 --- a/cpp/scsictl/scsictl_display.h +++ b/cpp/scsictl/scsictl_display.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -11,7 +11,6 @@ #include "generated/piscsi_interface.pb.h" #include -#include using namespace std; using namespace piscsi_interface; @@ -33,14 +32,15 @@ public: string DisplayImageFilesInfo(const PbImageFilesInfo&) const; string DisplayNetworkInterfaces(const PbNetworkInterfacesInfo&) const; string DisplayMappingInfo(const PbMappingInfo&) const; + string DisplayStatisticsInfo(const PbStatisticsInfo&) const; string DisplayOperationInfo(const PbOperationInfo&) const; private: - void DisplayParams(ostringstream&, const PbDevice&) const; - void DisplayAttributes(ostringstream&, const PbDeviceProperties&) const; - void DisplayDefaultParameters(ostringstream&, const PbDeviceProperties&) const; - void DisplayBlockSizes(ostringstream&, const PbDeviceProperties&) const; - void DisplayParameters(ostringstream&, const PbOperationMetaData&) const; - void DisplayPermittedValues(ostringstream&, const PbOperationParameter&) const; + string DisplayParams(const PbDevice&) const; + string DisplayAttributes(const PbDeviceProperties&) const; + string DisplayDefaultParameters(const PbDeviceProperties&) const; + string DisplayBlockSizes(const PbDeviceProperties&) const; + string DisplayParameters(const PbOperationMetaData&) const; + string DisplayPermittedValues(const PbOperationParameter&) const; }; diff --git a/cpp/scsictl/scsictl_parser.cpp b/cpp/scsictl/scsictl_parser.cpp index cd9ef35c..175b5ba8 100644 --- a/cpp/scsictl/scsictl_parser.cpp +++ b/cpp/scsictl/scsictl_parser.cpp @@ -3,13 +3,13 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "scsictl_parser.h" -PbOperation ScsictlParser::ParseOperation(const string& operation) const +PbOperation ScsictlParser::ParseOperation(string_view operation) const { const auto& it = operations.find(tolower(operation[0])); return it != operations.end() ? it->second : NO_OPERATION; @@ -17,8 +17,8 @@ PbOperation ScsictlParser::ParseOperation(const string& operation) const PbDeviceType ScsictlParser::ParseType(const string& type) const { - string t = type; - transform(t.begin(), t.end(), t.begin(), ::toupper); + string t; + ranges::transform(type, back_inserter(t), ::toupper); if (PbDeviceType parsed_type; PbDeviceType_Parse(t, &parsed_type)) { return parsed_type; diff --git a/cpp/scsictl/scsictl_parser.h b/cpp/scsictl/scsictl_parser.h index a09dc11a..ec5f2ee2 100644 --- a/cpp/scsictl/scsictl_parser.h +++ b/cpp/scsictl/scsictl_parser.h @@ -22,12 +22,12 @@ public: ScsictlParser() = default; ~ScsictlParser() = default; - PbOperation ParseOperation(const string&) const; + PbOperation ParseOperation(string_view) const; PbDeviceType ParseType(const string&) const; private: - unordered_map operations = { + const unordered_map operations = { { 'a', ATTACH }, { 'd', DETACH }, { 'e', EJECT }, @@ -37,7 +37,7 @@ private: { 'u', UNPROTECT } }; - unordered_map device_types = { + const unordered_map device_types = { { 'b', SCBR }, { 'c', SCCD }, { 'd', SCDP }, diff --git a/cpp/scsidump.cpp b/cpp/scsidump/scsidump.cpp similarity index 81% rename from cpp/scsidump.cpp rename to cpp/scsidump/scsidump.cpp index 35d7645c..2f885529 100644 --- a/cpp/scsidump.cpp +++ b/cpp/scsidump/scsidump.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -13,7 +13,7 @@ using namespace std; int main(int argc, char *argv[]) { - const vector args(argv, argv + argc); + vector args(argv, argv + argc); return ScsiDump().run(args); } diff --git a/cpp/scsidump/scsidump_core.cpp b/cpp/scsidump/scsidump_core.cpp index 7d1e2060..41c1dabd 100644 --- a/cpp/scsidump/scsidump_core.cpp +++ b/cpp/scsidump/scsidump_core.cpp @@ -5,22 +5,23 @@ // // Powered by XM6 TypeG Technology. // Copyright (C) 2016-2020 GIMONS -// Copyright (C) 2022 Uwe Seimet // Copyright (C) 2022 akuker +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- // TODO Evaluate CHECK CONDITION after sending a command // TODO Send IDENTIFY message in order to support LUNS > 7 +// TODO Get rid of some fields in favor of method arguments #include "scsidump/scsidump_core.h" -#include "hal/gpiobus.h" #include "hal/gpiobus_factory.h" #include "hal/systimer.h" -#include "shared/log.h" +#include "controllers/controller_manager.h" #include "shared/piscsi_exceptions.h" #include "shared/piscsi_util.h" -#include "shared/piscsi_version.h" +#include +#include #include #include #include @@ -28,10 +29,10 @@ #include #include #include -#include #include using namespace std; +using namespace filesystem; using namespace spdlog; using namespace scsi_defs; using namespace piscsi_util; @@ -43,28 +44,31 @@ void ScsiDump::CleanUp() } } -void ScsiDump::KillHandler(int) +void ScsiDump::TerminationHandler(int) { CleanUp(); - exit(EXIT_SUCCESS); + // Process will terminate automatically } -bool ScsiDump::Banner(const vector& args) const +bool ScsiDump::Banner(span args) const { cout << piscsi_util::Banner("(Hard Disk Dump/Restore Utility)"); if (args.size() < 2 || string(args[1]) == "-h" || string(args[1]) == "--help") { - cout << "Usage: " << args[0] << " -t ID[:LUN] [-i BID] -f FILE [-v] [-r] [-s BUFFER_SIZE] [-p]\n" - << " ID is the target device ID (0-7).\n" - << " LUN is the optional target device LUN (0-7). Default is 0.\n" + cout << "Usage: " << args[0] << " -t ID[:LUN] [-i BID] -f FILE [-v] [-r] [-s BUFFER_SIZE] [-p] [-I] [-S]\n" + << " ID is the target device ID (0-" << (ControllerManager::GetScsiIdMax() - 1) << ").\n" + << " LUN is the optional target device LUN (0-" << (ControllerManager::GetScsiLunMax() -1 ) << ")." + << " Default is 0.\n" << " BID is the PiSCSI board ID (0-7). Default is 7.\n" << " FILE is the dump file path.\n" - << " BUFFER_SIZE is the transfer buffer size in bytes, at least " << to_string(MINIMUM_BUFFER_SIZE) + << " BUFFER_SIZE is the transfer buffer size in bytes, at least " << MINIMUM_BUFFER_SIZE << " bytes. Default is 1 MiB.\n" << " -v Enable verbose logging.\n" << " -r Restore instead of dump.\n" << " -p Generate .properties file to be used with the PiSCSI web interface. Only valid for dump mode.\n" + << " -I Display INQUIRY data of ID[:LUN].\n" + << " -S Scan SCSI bus for devices.\n" << flush; return false; @@ -75,25 +79,27 @@ bool ScsiDump::Banner(const vector& args) const bool ScsiDump::Init() const { - // Interrupt handler setting - if (signal(SIGINT, KillHandler) == SIG_ERR || signal(SIGHUP, KillHandler) == SIG_ERR || - signal(SIGTERM, KillHandler) == SIG_ERR) { - return false; - } + // Signal handler for cleaning up + struct sigaction termination_handler; + termination_handler.sa_handler = TerminationHandler; + sigemptyset(&termination_handler.sa_mask); + termination_handler.sa_flags = 0; + sigaction(SIGTERM, &termination_handler, nullptr); + signal(SIGPIPE, SIG_IGN); bus = GPIOBUS_Factory::Create(BUS::mode_e::INITIATOR); return bus != nullptr; } -void ScsiDump::ParseArguments(const vector& args) +void ScsiDump::ParseArguments(span args) { int opt; int buffer_size = DEFAULT_BUFFER_SIZE; opterr = 0; - while ((opt = getopt(static_cast(args.size()), args.data(), "i:f:s:t:rvp")) != -1) { + while ((opt = getopt(static_cast(args.size()), args.data(), "i:f:s:t:rvpIS")) != -1) { switch (opt) { case 'i': if (!GetAsUnsignedInt(optarg, initiator_id) || initiator_id > 7) { @@ -105,19 +111,26 @@ void ScsiDump::ParseArguments(const vector& args) filename = optarg; break; + case 'I': + inquiry = true; + break; + case 's': if (!GetAsUnsignedInt(optarg, buffer_size) || buffer_size < MINIMUM_BUFFER_SIZE) { - throw parser_exception("Buffer size must be at least " + to_string(MINIMUM_BUFFER_SIZE / 1024) + "KiB"); + throw parser_exception("Buffer size must be at least " + to_string(MINIMUM_BUFFER_SIZE / 1024) + " KiB"); } break; - case 't': { - const string error = ProcessId(optarg, 8, target_id, target_lun); - if (!error.empty()) { + case 'S': + scan_bus = true; + break; + + case 't': + if (const string error = ProcessId(optarg, target_id, target_lun); !error.empty()) { throw parser_exception(error); } - } break; + break; case 'v': set_level(level::debug); @@ -136,32 +149,44 @@ void ScsiDump::ParseArguments(const vector& args) } } + if (!scan_bus && !inquiry && filename.empty()) { + throw parser_exception("Missing filename"); + } + + if (!scan_bus && target_id == -1) { + throw parser_exception("Missing target ID"); + } + if (target_id == initiator_id) { throw parser_exception("Target ID and PiSCSI board ID must not be identical"); } - if (filename.empty()) { - throw parser_exception("Missing filename"); + if (target_lun == -1) { + target_lun = 0; + } + + if (scan_bus) { + inquiry = false; } buffer = vector(buffer_size); } -void ScsiDump::WaitPhase(phase_t phase) const +void ScsiDump::WaitForPhase(phase_t phase) const { - LOGDEBUG("Waiting for %s phase", BUS::GetPhaseStrRaw(phase)) + spdlog::debug(string("Waiting for ") + BUS::GetPhaseStrRaw(phase) + " phase"); // Timeout (3000ms) const uint32_t now = SysTimer::GetTimerLow(); - while ((SysTimer::GetTimerLow() - now) < 3 * 1000 * 1000) { + while ((SysTimer::GetTimerLow() - now) < 3'000'000) { bus->Acquire(); if (bus->GetREQ() && bus->GetPhase() == phase) { return; } } - throw parser_exception("Expected " + string(BUS::GetPhaseStrRaw(phase)) + " phase, actual phase is " + - string(BUS::GetPhaseStrRaw(bus->GetPhase()))); + throw phase_exception("Expected " + string(BUS::GetPhaseStrRaw(phase)) + " phase, actual phase is " + + string(BUS::GetPhaseStrRaw(bus->GetPhase()))); } void ScsiDump::Selection() const @@ -180,11 +205,11 @@ void ScsiDump::Selection() const void ScsiDump::Command(scsi_command cmd, vector& cdb) const { - LOGDEBUG("Executing %s", command_mapping.find(cmd)->second.second) + spdlog::debug("Executing " + command_mapping.find(cmd)->second.second); Selection(); - WaitPhase(phase_t::command); + WaitForPhase(phase_t::command); cdb[0] = static_cast(cmd); cdb[1] = static_cast(static_cast(cdb[1]) | static_cast(target_lun << 5)); @@ -192,43 +217,43 @@ void ScsiDump::Command(scsi_command cmd, vector& cdb) const bus->SendHandShake(cdb.data(), static_cast(cdb.size()), BUS::SEND_NO_DELAY)) { BusFree(); - throw parser_exception(command_mapping.find(cmd)->second.second + string(" failed")); + throw phase_exception(command_mapping.find(cmd)->second.second + string(" failed")); } } void ScsiDump::DataIn(int length) { - WaitPhase(phase_t::datain); + WaitForPhase(phase_t::datain); if (!bus->ReceiveHandShake(buffer.data(), length)) { - throw parser_exception("DATA IN failed"); + throw phase_exception("DATA IN failed"); } } void ScsiDump::DataOut(int length) { - WaitPhase(phase_t::dataout); + WaitForPhase(phase_t::dataout); if (!bus->SendHandShake(buffer.data(), length, BUS::SEND_NO_DELAY)) { - throw parser_exception("DATA OUT failed"); + throw phase_exception("DATA OUT failed"); } } void ScsiDump::Status() const { - WaitPhase(phase_t::status); + WaitForPhase(phase_t::status); if (array buf; bus->ReceiveHandShake(buf.data(), 1) != 1) { - throw parser_exception("STATUS failed"); + throw phase_exception("STATUS failed"); } } void ScsiDump::MessageIn() const { - WaitPhase(phase_t::msgin); + WaitForPhase(phase_t::msgin); if (array buf; bus->ReceiveHandShake(buf.data(), 1) != 1) { - throw parser_exception("MESSAGE IN failed"); + throw phase_exception("MESSAGE IN failed"); } } @@ -324,7 +349,7 @@ pair ScsiDump::ReadCapacity() (static_cast(buffer[sector_size_offset + 2]) << 8) | static_cast(buffer[sector_size_offset + 3]); - return make_pair(capacity, sector_size); + return { capacity, sector_size }; } void ScsiDump::Read10(uint32_t bstart, uint32_t blength, uint32_t length) @@ -383,38 +408,59 @@ void ScsiDump::WaitForBusy() const // Success if the target is busy if (!bus->GetBSY()) { - throw parser_exception("SELECTION failed"); + throw phase_exception("SELECTION failed"); } } -int ScsiDump::run(const vector& args) +int ScsiDump::run(span args) { if (!Banner(args)) { return EXIT_SUCCESS; } - if (!Init()) { - cerr << "Error: Initializing. Are you root?" << endl; + try { + ParseArguments(args); + } + catch (const parser_exception& e) { + cerr << "Error: " << e.what() << endl; + return EXIT_FAILURE; + } - // Probably not root - return EPERM; + if (getuid()) { + cerr << "Error: GPIO bus access requires root permissions. Are you running as root?" << endl; + return EXIT_FAILURE; + } + +#ifndef USE_SEL_EVENT_ENABLE + cerr << "Error: No PiSCSI hardware support" << endl; + return EXIT_FAILURE; +#endif + + if (!Init()) { + cerr << "Error: Can't initialize bus" << endl; + return EXIT_FAILURE; } try { - ParseArguments(args); + if (scan_bus) { + ScanBus(); + } + else if (inquiry) { + DisplayBoardId(); -#ifndef USE_SEL_EVENT_ENABLE - cerr << "Error: No PiSCSI hardware support" << endl; - return EXIT_FAILURE; -#endif + inquiry_info_t inq_info; + DisplayInquiry(inq_info, false); + } + else { + DumpRestore(); + } + } + catch (const phase_exception& e) { + cerr << "Error: " << e.what() << endl; - return DumpRestore(); - } catch (const parser_exception& e) { - cerr << "Error: " << e.what() << endl; + CleanUp(); - CleanUp(); - - return EXIT_FAILURE; + return EXIT_FAILURE; } CleanUp(); @@ -422,9 +468,92 @@ int ScsiDump::run(const vector& args) return EXIT_SUCCESS; } +void ScsiDump::DisplayBoardId() const +{ + cout << DIVIDER << "\nPiSCSI board ID is " << initiator_id << "\n"; +} + +void ScsiDump::ScanBus() +{ + DisplayBoardId(); + + for (target_id = 0; target_id < ControllerManager::GetScsiIdMax(); target_id++) { + if (initiator_id == target_id) { + continue; + } + + for (target_lun = 0; target_lun < 8; target_lun++) { + inquiry_info_t inq_info; + try { + DisplayInquiry(inq_info, false); + } + catch(const phase_exception&) { + // Continue with next ID if there is no LUN 0 + if (!target_lun) { + break; + } + } + } + } +} + +bool ScsiDump::DisplayInquiry(ScsiDump::inquiry_info_t& inq_info, bool check_type) +{ + // Assert RST for 1 ms + bus->SetRST(true); + const timespec ts = {.tv_sec = 0, .tv_nsec = 1000 * 1000}; + nanosleep(&ts, nullptr); + bus->SetRST(false); + + cout << DIVIDER << "\nTarget device is " << target_id << ":" << target_lun << "\n" << flush; + + Inquiry(); + + const auto type = static_cast(buffer[0]); + if ((type & byte{0x1f}) == byte{0x1f}) { + // Requested LUN is not available + return false; + } + + array str = {}; + memcpy(str.data(), &buffer[8], 8); + inq_info.vendor = string(str.data()); + cout << "Vendor: " << inq_info.vendor << "\n"; + + str.fill(0); + memcpy(str.data(), &buffer[16], 16); + inq_info.product = string(str.data()); + cout << "Product: " << inq_info.product << "\n"; + + str.fill(0); + memcpy(str.data(), &buffer[32], 4); + inq_info.revision = string(str.data()); + cout << "Revision: " << inq_info.revision << "\n" << flush; + + if (const auto& t = DEVICE_TYPES.find(type & byte{0x1f}); t != DEVICE_TYPES.end()) { + cout << "Device Type: " << (*t).second << "\n"; + } + else { + cout << "Device Type: Unknown\n"; + } + + cout << "Removable: " << (((static_cast(buffer[1]) & byte{0x80}) == byte{0x80}) ? "Yes" : "No") << "\n"; + + if (check_type && type != static_cast(device_type::direct_access) && + type != static_cast(device_type::cd_rom) && type != static_cast(device_type::optical_memory)) { + cerr << "Invalid device type, supported types for dump/restore are DIRECT ACCESS, CD-ROM/DVD/BD and OPTICAL MEMORY" << endl; + return false; + } + + return true; +} + int ScsiDump::DumpRestore() { - const auto inq_info = GetDeviceInfo(); + inquiry_info_t inq_info; + if (!GetDeviceInfo(inq_info)) { + return EXIT_FAILURE; + } fstream fs; fs.open(filename, (restore ? ios::in : ios::out) | ios::binary); @@ -436,12 +565,12 @@ int ScsiDump::DumpRestore() if (restore) { cout << "Starting restore\n" << flush; - // filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle more than 2 GiB off_t size; - if (struct stat st; !stat(filename.c_str(), &st)) { - size = st.st_size; - } else { - throw parser_exception("Can't determine file size"); + try { + size = file_size(path(filename)); + } + catch (const filesystem_error& e) { + throw parser_exception(string("Can't determine file size: ") + e.what()); } cout << "Restore file size: " << size << " bytes\n"; @@ -455,11 +584,11 @@ int ScsiDump::DumpRestore() } // Dump by buffer size - auto dsiz = static_cast(buffer.size()); + auto dsiz = static_cast(buffer.size()); const int duni = dsiz / inq_info.sector_size; - auto dnum = static_cast((inq_info.capacity * inq_info.sector_size) / dsiz); + auto dnum = static_cast((inq_info.capacity * inq_info.sector_size) / dsiz); - auto start_time = chrono::high_resolution_clock::now(); + const auto start_time = chrono::high_resolution_clock::now(); int i; for (i = 0; i < dnum; i++) { @@ -501,62 +630,32 @@ int ScsiDump::DumpRestore() cout << "100% (" << inq_info.capacity << "/" << inq_info.capacity << ")\n" << flush; } - auto stop_time = chrono::high_resolution_clock::now(); + const auto stop_time = chrono::high_resolution_clock::now(); - auto duration = chrono::duration_cast(stop_time - start_time).count(); + const auto duration = chrono::duration_cast(stop_time - start_time).count(); - cout << divider_str << "\n"; - cout << "Transfered : " << to_string(inq_info.capacity * inq_info.sector_size) << " bytes [" - << to_string(inq_info.capacity * inq_info.sector_size / 1024 / 1024) << "MiB]\n"; - cout << "Total time: " << to_string(duration) << " seconds (" << to_string(duration / 60) << " minutes\n"; - cout << "Averate transfer rate: " << to_string((inq_info.capacity * inq_info.sector_size / 8) / duration) - << " bytes per second (" << to_string((inq_info.capacity * inq_info.sector_size / 8) / duration / 1024) + cout << DIVIDER << "\n"; + cout << "Transfered : " << inq_info.capacity * inq_info.sector_size << " bytes [" + << inq_info.capacity * inq_info.sector_size / 1024 / 1024 << "MiB]\n"; + cout << "Total time: " << duration << " seconds (" << duration / 60 << " minutes\n"; + cout << "Average transfer rate: " << (inq_info.capacity * inq_info.sector_size / 8) / duration + << " bytes per second (" << (inq_info.capacity * inq_info.sector_size / 8) / duration / 1024 << " KiB per second)\n"; - cout << divider_str << "\n"; + cout << DIVIDER << "\n"; if (properties_file && !restore) { - GeneratePropertiesFile(filename, inq_info); + inq_info.GeneratePropertiesFile(filename + ".properties"); } return EXIT_SUCCESS; } -ScsiDump::inquiry_info_t ScsiDump::GetDeviceInfo() +bool ScsiDump::GetDeviceInfo(inquiry_info_t& inq_info) { - // Assert RST for 1 ms - bus->SetRST(true); - const timespec ts = {.tv_sec = 0, .tv_nsec = 1000 * 1000}; - nanosleep(&ts, nullptr); - bus->SetRST(false); + DisplayBoardId(); - cout << divider_str << "\n"; - cout << "PiSCSI board ID: " << initiator_id << "\n"; - cout << divider_str << "\n" << flush; - cout << "Target device ID: " << target_id << ", LUN: " << target_lun << "\n"; - - Inquiry(); - - inquiry_info_t inq_info; - - // Display INQUIRY information - array str = {}; - memcpy(str.data(), &buffer[8], 8); - cout << "Vendor: " << str.data() << "\n"; - inq_info.vendor = string(str.data()); - - str.fill(0); - memcpy(str.data(), &buffer[16], 16); - cout << "Product: " << str.data() << "\n"; - inq_info.product = string(str.data()); - - str.fill(0); - memcpy(str.data(), &buffer[32], 4); - cout << "Revision: " << str.data() << "\n" << flush; - inq_info.revision = string(str.data()); - - if (auto type = static_cast(buffer[0]); - type != device_type::DIRECT_ACCESS && type != device_type::CD_ROM && type != device_type::OPTICAL_MEMORY) { - throw parser_exception("Invalid device type, supported types are DIRECT ACCESS, CD-ROM and OPTICAL MEMORY"); + if (!DisplayInquiry(inq_info, true)) { + return false; } TestUnitReady(); @@ -564,39 +663,31 @@ ScsiDump::inquiry_info_t ScsiDump::GetDeviceInfo() RequestSense(); const auto [capacity, sector_size] = ReadCapacity(); - inq_info.capacity = capacity; - inq_info.sector_size = sector_size; + inq_info.capacity = capacity; + inq_info.sector_size = sector_size; - cout << "Sectors: " << capacity << "\n" - << "Sector size: " << sector_size << " bytes\n" - << "Capacity: " << sector_size * capacity / 1024 / 1024 << " MiB (" << sector_size * capacity + cout << "Sectors: " << capacity << "\n" + << "Sector size: " << sector_size << " bytes\n" + << "Capacity: " << sector_size * capacity / 1024 / 1024 << " MiB (" << sector_size * capacity << " bytes)\n" - << divider_str << "\n\n" + << DIVIDER << "\n\n" << flush; - return inq_info; + return true; } -void ScsiDump::GeneratePropertiesFile(const string& filename, const inquiry_info_t& inq_info) +void ScsiDump::inquiry_info::GeneratePropertiesFile(const string& property_file) const { - string prop_filename = filename + ".properties"; - string prop_str; - stringstream prop_stream(prop_str); + ofstream prop_stream(property_file); prop_stream << "{" << endl; - prop_stream << " \"vendor\": \"" << inq_info.vendor << "\"," << endl; - prop_stream << " \"product\": \"" << inq_info.product << "\"," << endl; - prop_stream << " \"revision\": \"" << inq_info.revision << "\"," << endl; - prop_stream << " \"block_size\": \"" << to_string(inq_info.sector_size) << "\"," << endl; + prop_stream << " \"vendor\": \"" << vendor << "\"," << endl; + prop_stream << " \"product\": \"" << product << "\"," << endl; + prop_stream << " \"revision\": \"" << revision << "\"," << endl; + prop_stream << " \"block_size\": \"" << sector_size << "\"" << endl; prop_stream << "}" << endl; - FILE* fp = fopen(prop_filename.c_str(), "w"); - if (fp) { - fputs(prop_stream.str().c_str(), fp); - } else { - LOGWARN("Unable to open output file %s", prop_filename.c_str()) - return; + if (prop_stream.fail()) { + spdlog::warn("Unable to create properties file '" + property_file + "'"); } - - fclose(fp); -} \ No newline at end of file +} diff --git a/cpp/scsidump/scsidump_core.h b/cpp/scsidump/scsidump_core.h index c46a6993..58feb419 100644 --- a/cpp/scsidump/scsidump_core.h +++ b/cpp/scsidump/scsidump_core.h @@ -3,51 +3,59 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once #include "hal/bus.h" -#include "shared/scsi.h" #include #include +#include #include +#include +#include using namespace std; +class phase_exception : public runtime_error +{ + using runtime_error::runtime_error; +}; + class ScsiDump { - static const int MINIMUM_BUFFER_SIZE = 1024 * 64; - static const int DEFAULT_BUFFER_SIZE = 1024 * 1024; public: + ScsiDump() = default; ~ScsiDump() = default; - int run(const vector&); + int run(const span); - struct inquiry_info_struct { + struct inquiry_info { string vendor; string product; string revision; uint32_t sector_size; uint64_t capacity; - }; - using inquiry_info_t = struct inquiry_info_struct; - protected: - // Protected for testability - static void GeneratePropertiesFile(const string& filename, const inquiry_info_t& inq_info); + void GeneratePropertiesFile(const string&) const; + }; + using inquiry_info_t = struct inquiry_info; private: - bool Banner(const vector&) const; + + bool Banner(span) const; bool Init() const; - void ParseArguments(const vector&); + void ParseArguments(span); + void DisplayBoardId() const; + void ScanBus(); + bool DisplayInquiry(inquiry_info_t&, bool); int DumpRestore(); - inquiry_info_t GetDeviceInfo(); - void WaitPhase(phase_t) const; + bool GetDeviceInfo(inquiry_info_t&); + void WaitForPhase(phase_t) const; void Selection() const; void Command(scsi_defs::scsi_command, vector&) const; void DataIn(int); @@ -64,7 +72,7 @@ class ScsiDump void WaitForBusy() const; static void CleanUp(); - static void KillHandler(int); + static void TerminationHandler(int); // A static instance is needed because of the signal handler static inline unique_ptr bus; @@ -79,9 +87,41 @@ class ScsiDump string filename; + bool inquiry = false; + + bool scan_bus = false; + bool restore = false; bool properties_file = false; - static inline const string divider_str = "----------------------------------------"; + static const int MINIMUM_BUFFER_SIZE = 1024 * 64; + static const int DEFAULT_BUFFER_SIZE = 1024 * 1024; + + static inline const string DIVIDER = "----------------------------------------"; + + static inline const unordered_map DEVICE_TYPES = { + { byte{0}, "Direct Access" }, + { byte{1}, "Sequential Access" }, + { byte{2}, "Printer" }, + { byte{3}, "Processor" }, + { byte{4}, "Write-Once" }, + { byte{5}, "CD-ROM/DVD/BD/DVD-RAM" }, + { byte{6}, "Scanner" }, + { byte{7}, "Optical Memory" }, + { byte{8}, "Media Changer" }, + { byte{9}, "Communications" }, + { byte{10}, "Graphic Arts Pre-Press" }, + { byte{11}, "Graphic Arts Pre-Press" }, + { byte{12}, "Storage Array Controller" }, + { byte{13}, "Enclosure Services" }, + { byte{14}, "Simplified Direct Access" }, + { byte{15}, "Optical Card Reader/Writer" }, + { byte{16}, "Bridge Controller" }, + { byte{17}, "Object-based Storage" }, + { byte{18}, "Automation/Drive Interface" }, + { byte{19}, "Security Manager" }, + { byte{20}, "Host Managed Zoned Block" }, + { byte{30}, "Well Known Logical Unit" } + }; }; diff --git a/cpp/scsiloop.cpp b/cpp/scsiloop/scsiloop.cpp similarity index 100% rename from cpp/scsiloop.cpp rename to cpp/scsiloop/scsiloop.cpp diff --git a/cpp/scsiloop/scsiloop_core.cpp b/cpp/scsiloop/scsiloop_core.cpp index 09b867a3..c5806bf2 100644 --- a/cpp/scsiloop/scsiloop_core.cpp +++ b/cpp/scsiloop/scsiloop_core.cpp @@ -12,7 +12,7 @@ // //--------------------------------------------------------------------------- -#include "shared/log.h" +#include "hal/log.h" #include "shared/piscsi_version.h" #include "shared/piscsi_util.h" #include "spdlog/sinks/stdout_color_sinks.h" @@ -159,8 +159,8 @@ int ScsiLoop::run(const vector &args) // This must be executed before the timer test, since this initializes the timer ScsiLoop_GPIO gpio_test; - int result = ScsiLoop_Timer::RunTimerTest(error_list); - result += gpio_test.RunLoopbackTest(error_list); + // int result = ScsiLoop_Timer::RunTimerTest(error_list); + int result = gpio_test.RunLoopbackTest(error_list); if (result == 0) { // Only test the dat inputs/outputs if the loopback test passed. diff --git a/cpp/scsiloop/scsiloop_cout.cpp b/cpp/scsiloop/scsiloop_cout.cpp index b681fac6..8ee99332 100644 --- a/cpp/scsiloop/scsiloop_cout.cpp +++ b/cpp/scsiloop/scsiloop_cout.cpp @@ -30,11 +30,11 @@ void ScsiLoop_Cout::FinishTest(const string &test_name, int failures) } } -void ScsiLoop_Cout::PrintErrors(vector &test_errors) +void ScsiLoop_Cout::PrintErrors(const vector &test_errors) { - if (test_errors.size() > 0) { - for (auto err_string : test_errors) { + if (!test_errors.empty()) { + for (auto& err_string : test_errors) { cout << RED << err_string << endl; } } -} \ No newline at end of file +} diff --git a/cpp/scsiloop/scsiloop_cout.h b/cpp/scsiloop/scsiloop_cout.h index 35d0bc4d..6ac5a12a 100644 --- a/cpp/scsiloop/scsiloop_cout.h +++ b/cpp/scsiloop/scsiloop_cout.h @@ -18,7 +18,7 @@ class ScsiLoop_Cout static void StartTest(const string &test_name); static void PrintUpdate(); static void FinishTest(const string &test_name, int failures); - static void PrintErrors(vector &test_errors); + static void PrintErrors(const vector &test_errors); private: const static inline string RESET = "\033[0m"; @@ -30,4 +30,4 @@ class ScsiLoop_Cout const static inline string MAGENTA = "\033[35m"; /* Magenta */ const static inline string CYAN = "\033[36m"; /* Cyan */ const static inline string WHITE = "\033[37m"; /* White */ -}; \ No newline at end of file +}; diff --git a/cpp/scsiloop/scsiloop_gpio.cpp b/cpp/scsiloop/scsiloop_gpio.cpp index c4a86a96..9a803ce2 100644 --- a/cpp/scsiloop/scsiloop_gpio.cpp +++ b/cpp/scsiloop/scsiloop_gpio.cpp @@ -7,11 +7,12 @@ // //--------------------------------------------------------------------------- +#include #include "scsiloop/scsiloop_gpio.h" #include "hal/gpiobus_factory.h" #include "hal/sbc_version.h" #include "scsiloop/scsiloop_cout.h" -#include "shared/log.h" +#include "hal/log.h" #if defined CONNECT_TYPE_STANDARD #include "hal/connection_type/connection_standard.h" @@ -27,8 +28,6 @@ ScsiLoop_GPIO::ScsiLoop_GPIO() { - LOGTRACE("%s", __PRETTY_FUNCTION__); - bus = GPIOBUS_Factory::Create(BUS::mode_e::TARGET); if (bus == nullptr) { throw bus_exception("Unable to create bus"); @@ -116,91 +115,8 @@ ScsiLoop_GPIO::ScsiLoop_GPIO() local_pin_dt6 = PIN_DT6; local_pin_dt7 = PIN_DT7; local_pin_dp = PIN_DP; - - } else if (SBC_Version::GetSbcVersion() == SBC_Version::sbc_version_type::sbc_bananapi_m2_plus) { - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_DT0, .connected_pin = BPI_PIN_ACK, .dir_ctrl_pin = BPI_PIN_DTD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_DT1, .connected_pin = BPI_PIN_SEL, .dir_ctrl_pin = BPI_PIN_DTD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_DT2, .connected_pin = BPI_PIN_ATN, .dir_ctrl_pin = BPI_PIN_DTD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_DT3, .connected_pin = BPI_PIN_RST, .dir_ctrl_pin = BPI_PIN_DTD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_DT4, .connected_pin = BPI_PIN_CD, .dir_ctrl_pin = BPI_PIN_DTD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_DT5, .connected_pin = BPI_PIN_IO, .dir_ctrl_pin = BPI_PIN_DTD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_DT6, .connected_pin = BPI_PIN_MSG, .dir_ctrl_pin = BPI_PIN_DTD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_DT7, .connected_pin = BPI_PIN_REQ, .dir_ctrl_pin = BPI_PIN_DTD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_DP, .connected_pin = BPI_PIN_BSY, .dir_ctrl_pin = BPI_PIN_DTD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_ATN, .connected_pin = BPI_PIN_DT2, .dir_ctrl_pin = BPI_PIN_IND}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_RST, .connected_pin = BPI_PIN_DT3, .dir_ctrl_pin = BPI_PIN_IND}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_ACK, .connected_pin = BPI_PIN_DT0, .dir_ctrl_pin = BPI_PIN_IND}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_REQ, .connected_pin = BPI_PIN_DT7, .dir_ctrl_pin = BPI_PIN_TAD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_MSG, .connected_pin = BPI_PIN_DT6, .dir_ctrl_pin = BPI_PIN_TAD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_CD, .connected_pin = BPI_PIN_DT4, .dir_ctrl_pin = BPI_PIN_TAD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_IO, .connected_pin = BPI_PIN_DT5, .dir_ctrl_pin = BPI_PIN_TAD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_BSY, .connected_pin = BPI_PIN_DP, .dir_ctrl_pin = BPI_PIN_TAD}); - loopback_conn_table.push_back( - loopback_connection{.this_pin = BPI_PIN_SEL, .connected_pin = BPI_PIN_DT1, .dir_ctrl_pin = BPI_PIN_IND}); - - pin_name_lookup[BPI_PIN_DT0] = " d0"; - pin_name_lookup[BPI_PIN_DT1] = " d1"; - pin_name_lookup[BPI_PIN_DT2] = " d2"; - pin_name_lookup[BPI_PIN_DT3] = " d3"; - pin_name_lookup[BPI_PIN_DT4] = " d4"; - pin_name_lookup[BPI_PIN_DT5] = " d5"; - pin_name_lookup[BPI_PIN_DT6] = " d6"; - pin_name_lookup[BPI_PIN_DT7] = " d7"; - pin_name_lookup[BPI_PIN_DP] = " dp"; - pin_name_lookup[BPI_PIN_ATN] = "atn"; - pin_name_lookup[BPI_PIN_RST] = "rst"; - pin_name_lookup[BPI_PIN_ACK] = "ack"; - pin_name_lookup[BPI_PIN_REQ] = "req"; - pin_name_lookup[BPI_PIN_MSG] = "msg"; - pin_name_lookup[BPI_PIN_CD] = " cd"; - pin_name_lookup[BPI_PIN_IO] = " io"; - pin_name_lookup[BPI_PIN_BSY] = "bsy"; - pin_name_lookup[BPI_PIN_SEL] = "sel"; - pin_name_lookup[BPI_PIN_IND] = "ind"; - pin_name_lookup[BPI_PIN_TAD] = "tad"; - pin_name_lookup[BPI_PIN_DTD] = "dtd"; - - local_pin_dtd = BPI_PIN_DTD; - local_pin_tad = BPI_PIN_TAD; - local_pin_ind = BPI_PIN_IND; - local_pin_ack = BPI_PIN_ACK; - local_pin_sel = BPI_PIN_SEL; - local_pin_atn = BPI_PIN_ATN; - local_pin_rst = BPI_PIN_RST; - local_pin_cd = BPI_PIN_CD; - local_pin_io = BPI_PIN_IO; - local_pin_msg = BPI_PIN_MSG; - local_pin_req = BPI_PIN_REQ; - local_pin_bsy = BPI_PIN_BSY; - local_pin_dt0 = BPI_PIN_DT0; - local_pin_dt1 = BPI_PIN_DT1; - local_pin_dt2 = BPI_PIN_DT2; - local_pin_dt3 = BPI_PIN_DT3; - local_pin_dt4 = BPI_PIN_DT4; - local_pin_dt5 = BPI_PIN_DT5; - local_pin_dt6 = BPI_PIN_DT6; - local_pin_dt7 = BPI_PIN_DT7; - local_pin_dp = BPI_PIN_DP; - } else { - LOGERROR("Unsupported board version: %s", SBC_Version::GetString()->c_str()); + spdlog::error("Unsupported board version: " + SBC_Version::GetAsString()); } } @@ -589,4 +505,4 @@ int ScsiLoop_GPIO::RunDataOutputTest(vector &error_list) ScsiLoop_Cout::FinishTest("DAT Outputs", err_count); return err_count; -} \ No newline at end of file +} diff --git a/cpp/scsiloop/scsiloop_gpio.h b/cpp/scsiloop/scsiloop_gpio.h index d4365682..a61ae19e 100644 --- a/cpp/scsiloop/scsiloop_gpio.h +++ b/cpp/scsiloop/scsiloop_gpio.h @@ -31,7 +31,7 @@ class ScsiLoop_GPIO int connected_pin; int dir_ctrl_pin; }; - typedef loopback_connections_struct loopback_connection; + using loopback_connection = loopback_connections_struct; std::map pin_name_lookup; std::vector loopback_conn_table; @@ -78,4 +78,4 @@ class ScsiLoop_GPIO int local_pin_dp = -1; shared_ptr bus; -}; \ No newline at end of file +}; diff --git a/cpp/scsiloop/scsiloop_timer.cpp b/cpp/scsiloop/scsiloop_timer.cpp index 293c268a..4cfe627a 100644 --- a/cpp/scsiloop/scsiloop_timer.cpp +++ b/cpp/scsiloop/scsiloop_timer.cpp @@ -10,7 +10,7 @@ #include "scsiloop_timer.h" #include "hal/systimer.h" #include "scsiloop/scsiloop_cout.h" -#include "shared/log.h" +#include "hal/log.h" int ScsiLoop_Timer::RunTimerTest(vector &error_list) { @@ -56,7 +56,7 @@ int ScsiLoop_Timer::RunTimerTest(vector &error_list) } after = SysTimer::GetTimerLow(); elapsed_nanosecs = after - before; - LOGDEBUG("SysTimer::SleepUsec() Average %d", (uint32_t)(elapsed_nanosecs / 100)); + LOGDEBUG("SysTimer::SleepUsec() Average %d", elapsed_nanosecs / 100); if ((elapsed_nanosecs > expected_usec_result * (1.0 + timer_tolerance_percent)) || (elapsed_nanosecs < expected_usec_result * (1.0 - timer_tolerance_percent))) { diff --git a/cpp/scsimon.cpp b/cpp/scsimon/scsimon.cpp similarity index 93% rename from cpp/scsimon.cpp rename to cpp/scsimon/scsimon.cpp index b0b00383..9cc84565 100644 --- a/cpp/scsimon.cpp +++ b/cpp/scsimon/scsimon.cpp @@ -7,7 +7,7 @@ // //--------------------------------------------------------------------------- -#include "monitor/sm_core.h" +#include "scsimon/sm_core.h" using namespace std; diff --git a/cpp/monitor/sm_core.cpp b/cpp/scsimon/sm_core.cpp similarity index 69% rename from cpp/monitor/sm_core.cpp rename to cpp/scsimon/sm_core.cpp index dbb12fff..d90a5798 100644 --- a/cpp/monitor/sm_core.cpp +++ b/cpp/scsimon/sm_core.cpp @@ -9,12 +9,11 @@ // //--------------------------------------------------------------------------- -#include "monitor/sm_core.h" -#include "hal/data_sample.h" +#include "scsimon/sm_core.h" #include "hal/gpiobus.h" #include "hal/gpiobus_factory.h" -#include "monitor/sm_reports.h" -#include "shared/log.h" +#include "scsimon/sm_reports.h" +#include "hal/log.h" #include "shared/piscsi_version.h" #include "shared/piscsi_util.h" #include @@ -77,28 +76,28 @@ void ScsiMon::ParseArguments(const vector &args) void ScsiMon::PrintHelpText(const vector &args) const { - LOGINFO("%s -i [input file json] -b [buffer size] [output file]", args[0]) - LOGINFO(" -i [input file json] - scsimon will parse the json file instead of capturing new data") - LOGINFO(" If -i option is not specified, scsimon will read the gpio pins") - LOGINFO(" -b [buffer size] - Override the default buffer size of %d.", buff_size) - LOGINFO(" [output file] - Base name of the output files. The file extension (ex: .json)") - LOGINFO(" will be appended to this file name") + spdlog::info(string(args[0]) + " -i [input file json] -b [buffer size] [output file]"); + spdlog::info(" -i [input file json] - scsimon will parse the json file instead of capturing new data"); + spdlog::info(" If -i option is not specified, scsimon will read the gpio pins"); + spdlog::info(" -b [buffer size] - Override the default buffer size of " + to_string(buff_size)); + spdlog::info(" [output file] - Base name of the output files. The file extension (ex: .json)"); + spdlog::info(" will be appended to this file name"); } void ScsiMon::Banner() const { if (import_data) { - LOGINFO("Reading input file: %s", input_file_name.c_str()) + spdlog::info("Reading input file: " + input_file_name); } else { - LOGINFO("Reading live data from the GPIO pins") - LOGINFO(" Connection type : %s", CONNECT_DESC.c_str()) + spdlog::info("Reading live data from the GPIO pins"); + spdlog::info(" Connection type: " + CONNECT_DESC); } - LOGINFO(" Data buffer size: %u", buff_size) - LOGINFO(" ") - LOGINFO("Generating output files:") - LOGINFO(" %s - Value Change Dump file that can be opened with GTKWave", vcd_file_name.c_str()) - LOGINFO(" %s - JSON file with raw data", json_file_name.c_str()) - LOGINFO(" %s - HTML file with summary of commands", html_file_name.c_str()) + spdlog::info(" Data buffer size: " + to_string(buff_size)); + spdlog::info(" "); + spdlog::info("Generating output files:"); + spdlog::info(" " + vcd_file_name + " - Value Change Dump file that can be opened with GTKWave"); + spdlog::info(" " + json_file_name + " - JSON file with raw data"); + spdlog::info(" " + html_file_name + " - HTML file with summary of commands"); } bool ScsiMon::Init() @@ -116,7 +115,7 @@ bool ScsiMon::Init() bus = GPIOBUS_Factory::Create(BUS::mode_e::TARGET); if (bus == nullptr) { - LOGERROR("Unable to intiailize the GPIO bus. Exiting....") + spdlog::error("Unable to intiailize the GPIO bus. Exiting...."); return false; } @@ -128,14 +127,14 @@ bool ScsiMon::Init() void ScsiMon::Cleanup() const { if (!import_data) { - LOGINFO("Stopping data collection....") + spdlog::info("Stopping data collection ..."); } - LOGINFO(" ") - LOGINFO("Generating %s...", vcd_file_name.c_str()) + spdlog::info(" "); + spdlog::info("Generating " + vcd_file_name + "..."); scsimon_generate_value_change_dump(vcd_file_name, data_buffer); - LOGINFO("Generating %s...", json_file_name.c_str()) + spdlog::info("Generating " + json_file_name + "..."); scsimon_generate_json(json_file_name, data_buffer); - LOGINFO("Generating %s...", html_file_name.c_str()) + spdlog::info("Generating " + html_file_name + "..."); scsimon_generate_html(html_file_name, data_buffer); bus->Cleanup(); @@ -179,15 +178,15 @@ int ScsiMon::run(const vector &args) if (import_data) { data_idx = scsimon_read_json(input_file_name, data_buffer); if (data_idx > 0) { - LOGDEBUG("Read %d samples from %s", data_idx, input_file_name.c_str()) + spdlog::debug("Read " + to_string(data_idx) + " samples from '" + input_file_name + "'"); Cleanup(); } exit(0); } - LOGINFO(" ") - LOGINFO("Now collecting data.... Press CTRL-C to stop.") - LOGINFO(" ") + spdlog::info(" "); + spdlog::info("Now collecting data.... Press CTRL-C to stop."); + spdlog::info(" "); // Initialize int ret = 0; @@ -219,12 +218,12 @@ int ScsiMon::run(const vector &args) while (running) { loop_count++; if (loop_count > LLONG_MAX - 1) { - LOGINFO("Maximum amount of time has elapsed. SCSIMON is terminating.") + spdlog::info("Maximum amount of time has elapsed. SCSIMON is terminating."); running = false; } if (data_idx >= (buff_size - 2)) { - LOGINFO("Internal data buffer is full. SCSIMON is terminating.") + spdlog::info("Internal data buffer is full. SCSIMON is terminating."); running = false; } @@ -249,15 +248,13 @@ int ScsiMon::run(const vector &args) timersub(&stop_time, &start_time, &time_diff); elapsed_us = ((time_diff.tv_sec * 1000000) + time_diff.tv_usec); - LOGINFO("%s", ("Elapsed time: " + to_string(elapsed_us) + " microseconds (" + to_string(elapsed_us / 1000000) + - " seconds") - .c_str()) - LOGINFO("%s", ("Collected " + to_string(data_idx) + " changes").c_str()) + spdlog::info("Elapsed time: " + to_string(elapsed_us) + " microseconds (" + to_string(elapsed_us / 1000000) + + " seconds"); + spdlog::info("Collected " + to_string(data_idx) + " changes"); ns_per_loop = (double)(elapsed_us * 1000) / (double)loop_count; - LOGINFO("%s", ("Read the SCSI bus " + to_string(loop_count) + " times with an average of " + - to_string(ns_per_loop) + " ns for each read") - .c_str()) + spdlog::info("Read the SCSI bus " + to_string(loop_count) + " times with an average of " + + to_string(ns_per_loop) + " ns for each read"); Cleanup(); diff --git a/cpp/monitor/sm_core.h b/cpp/scsimon/sm_core.h similarity index 92% rename from cpp/monitor/sm_core.h rename to cpp/scsimon/sm_core.h index 1ee25675..25c18d1e 100644 --- a/cpp/monitor/sm_core.h +++ b/cpp/scsimon/sm_core.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -13,6 +13,7 @@ #include "hal/data_sample.h" #include #include +#include using namespace std; @@ -37,7 +38,7 @@ class ScsiMon static void KillHandler(int); - static inline volatile bool running; + static inline atomic running; shared_ptr bus; diff --git a/cpp/monitor/sm_html_report.cpp b/cpp/scsimon/sm_html_report.cpp similarity index 98% rename from cpp/monitor/sm_html_report.cpp rename to cpp/scsimon/sm_html_report.cpp index 7b7481f6..8b778ccd 100644 --- a/cpp/monitor/sm_html_report.cpp +++ b/cpp/scsimon/sm_html_report.cpp @@ -9,7 +9,7 @@ // //--------------------------------------------------------------------------- -#include "shared/log.h" +#include "hal/log.h" #include "shared/piscsi_version.h" #include "sm_reports.h" #include @@ -173,7 +173,7 @@ static void print_html_data(ofstream& html_fp, const vector> &data_capture_array) { - LOGINFO("Creating HTML report file (%s)", filename.c_str()) + spdlog::info("Creating HTML report file (" + filename + ")"); ofstream html_ofstream; diff --git a/cpp/monitor/sm_json_report.cpp b/cpp/scsimon/sm_json_report.cpp similarity index 94% rename from cpp/monitor/sm_json_report.cpp rename to cpp/scsimon/sm_json_report.cpp index de06215e..faa6f0e0 100644 --- a/cpp/monitor/sm_json_report.cpp +++ b/cpp/scsimon/sm_json_report.cpp @@ -10,7 +10,7 @@ //--------------------------------------------------------------------------- #include "hal/data_sample_raspberry.h" -#include "shared/log.h" +#include "hal/log.h" #include "sm_reports.h" #include "string.h" #include @@ -52,7 +52,7 @@ uint32_t scsimon_read_json(const string &json_filename, vector> &data_capture_array) { - LOGTRACE("Creating JSON file (%s)", filename.c_str()) + spdlog::trace("Creating JSON file (" + filename + ")"); ofstream json_ofstream; json_ofstream.open(filename.c_str(), ios::out); diff --git a/cpp/monitor/sm_reports.h b/cpp/scsimon/sm_reports.h similarity index 100% rename from cpp/monitor/sm_reports.h rename to cpp/scsimon/sm_reports.h diff --git a/cpp/monitor/sm_vcd_report.cpp b/cpp/scsimon/sm_vcd_report.cpp similarity index 98% rename from cpp/monitor/sm_vcd_report.cpp rename to cpp/scsimon/sm_vcd_report.cpp index 5f8f1d68..be6f1035 100644 --- a/cpp/monitor/sm_vcd_report.cpp +++ b/cpp/scsimon/sm_vcd_report.cpp @@ -11,7 +11,7 @@ #include "hal/data_sample.h" #include "hal/gpiobus.h" -#include "shared/log.h" +#include "hal/log.h" #include "sm_core.h" #include "sm_reports.h" #include @@ -77,7 +77,7 @@ static void vcd_output_if_changed_byte(ofstream &fp, uint8_t data, int pin, char void scsimon_generate_value_change_dump(const string &filename, const vector> &data_capture_array) { - LOGTRACE("Creating Value Change Dump file (%s)", filename.c_str()) + spdlog::trace("Creating Value Change Dump file (" + filename + ")"); ofstream vcd_ofstream; vcd_ofstream.open(filename.c_str(), ios::out); diff --git a/cpp/shared/network_util.cpp b/cpp/shared/network_util.cpp new file mode 100644 index 00000000..31aa1831 --- /dev/null +++ b/cpp/shared/network_util.cpp @@ -0,0 +1,75 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator PiSCSI +// for Raspberry Pi +// +// Copyright (C) 2023 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include "network_util.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +bool network_util::IsInterfaceUp(const string& interface) +{ + ifreq ifr = {}; + strncpy(ifr.ifr_name, interface.c_str(), IFNAMSIZ - 1); //NOSONAR Using strncpy is safe + const int fd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); + + if (!ioctl(fd, SIOCGIFFLAGS, &ifr) && (ifr.ifr_flags & IFF_UP)) { + close(fd); + return true; + } + + close(fd); + return false; +} + +set> network_util::GetNetworkInterfaces() +{ + set> network_interfaces; + +#ifdef __linux__ + ifaddrs *addrs; + getifaddrs(&addrs); + ifaddrs *tmp = addrs; + + while (tmp) { + if (const string name = tmp->ifa_name; tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_PACKET && + name != "lo" && name != "piscsi_bridge" && !name.starts_with("dummy") && IsInterfaceUp(name)) { + // Only list interfaces that are up + network_interfaces.insert(name); + } + + tmp = tmp->ifa_next; + } + + freeifaddrs(addrs); +#endif + + return network_interfaces; +} + +bool network_util::ResolveHostName(const string& host, sockaddr_in *addr) +{ + addrinfo hints = {}; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + if (addrinfo *result; !getaddrinfo(host.c_str(), nullptr, &hints, &result)) { + *addr = *reinterpret_cast(result->ai_addr); //NOSONAR bit_cast is not supported by the bullseye compiler + freeaddrinfo(result); + return true; + } + + return false; +} diff --git a/cpp/shared/network_util.h b/cpp/shared/network_util.h new file mode 100644 index 00000000..86d0adf9 --- /dev/null +++ b/cpp/shared/network_util.h @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator PiSCSI +// for Raspberry Pi +// +// Copyright (C) 2023 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#pragma once + +#include +#include + +using namespace std; + +struct sockaddr_in; + +namespace network_util +{ + bool IsInterfaceUp(const string&); + set> GetNetworkInterfaces(); + bool ResolveHostName(const string&, sockaddr_in *); +} diff --git a/cpp/shared/piscsi_exceptions.h b/cpp/shared/piscsi_exceptions.h index 34c58591..be003822 100644 --- a/cpp/shared/piscsi_exceptions.h +++ b/cpp/shared/piscsi_exceptions.h @@ -36,7 +36,7 @@ class scsi_exception : public exception public: - scsi_exception(scsi_defs::sense_key sense_key, scsi_defs::asc asc = scsi_defs::asc::NO_ADDITIONAL_SENSE_INFORMATION) + scsi_exception(scsi_defs::sense_key sense_key, scsi_defs::asc asc = scsi_defs::asc::no_additional_sense_information) : sense_key(sense_key), asc(asc) {} ~scsi_exception() override = default; diff --git a/cpp/shared/piscsi_util.cpp b/cpp/shared/piscsi_util.cpp index 2808ee6c..0cdccf02 100644 --- a/cpp/shared/piscsi_util.cpp +++ b/cpp/shared/piscsi_util.cpp @@ -7,13 +7,48 @@ // //--------------------------------------------------------------------------- +#include "controllers/controller_manager.h" #include "piscsi_version.h" #include "piscsi_util.h" +#include #include +#include #include +#include #include using namespace std; +using namespace filesystem; + +vector piscsi_util::Split(const string& s, char separator, int limit) +{ + assert(limit >= 0); + + string component; + vector result; + stringstream str(s); + + while (--limit > 0 && getline(str, component, separator)) { + result.push_back(component); + } + + if (!str.eof()) { + getline(str, component); + result.push_back(component); + } + + return result; +} + +string piscsi_util::GetLocale() +{ + const char *locale = setlocale(LC_MESSAGES, ""); + if (locale == nullptr || !strcmp(locale, "C")) { + locale = "en"; + } + + return locale; +} bool piscsi_util::GetAsUnsignedInt(const string& value, int& result) { @@ -35,10 +70,8 @@ bool piscsi_util::GetAsUnsignedInt(const string& value, int& result) return true; } -string piscsi_util::ProcessId(const string& id_spec, int max_luns, int& id, int& lun) +string piscsi_util::ProcessId(const string& id_spec, int& id, int& lun) { - assert(max_luns > 0); - id = -1; lun = -1; @@ -46,27 +79,32 @@ string piscsi_util::ProcessId(const string& id_spec, int max_luns, int& id, int& return "Missing device ID"; } - if (const size_t separator_pos = id_spec.find(COMPONENT_SEPARATOR); separator_pos == string::npos) { - if (!GetAsUnsignedInt(id_spec, id) || id >= 8) { - id = -1; + const int id_max = ControllerManager::GetScsiIdMax(); + const int lun_max = ControllerManager::GetScsiLunMax(); - return "Invalid device ID (0-7)"; + if (const auto& components = Split(id_spec, COMPONENT_SEPARATOR, 2); !components.empty()) { + if (components.size() == 1) { + if (!GetAsUnsignedInt(components[0], id) || id >= id_max) { + id = -1; + + return "Invalid device ID (0-" + to_string(ControllerManager::GetScsiIdMax() - 1) + ")"; + } + + return ""; } - lun = 0; - } - else if (!GetAsUnsignedInt(id_spec.substr(0, separator_pos), id) || id > 7 || - !GetAsUnsignedInt(id_spec.substr(separator_pos + 1), lun) || lun >= max_luns) { - id = -1; - lun = -1; + if (!GetAsUnsignedInt(components[0], id) || id >= id_max || !GetAsUnsignedInt(components[1], lun) || lun >= lun_max) { + id = -1; + lun = -1; - return "Invalid LUN (0-" + to_string(max_luns - 1) + ")"; + return "Invalid LUN (0-" + to_string(lun_max - 1) + ")"; + } } return ""; } -string piscsi_util::Banner(const string& app) +string piscsi_util::Banner(string_view app) { ostringstream s; @@ -79,15 +117,18 @@ string piscsi_util::Banner(const string& app) return s.str(); } -string piscsi_util::GetExtensionLowerCase(const string& filename) +string piscsi_util::GetExtensionLowerCase(string_view filename) { string ext; - if (const size_t separator = filename.rfind('.'); separator != string::npos) { - ext = filename.substr(separator + 1); - } - transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c){ return std::tolower(c); }); + ranges::transform(path(filename).extension().string(), back_inserter(ext), ::tolower); - return ext; + // Remove the leading dot + return ext.empty() ? "" : ext.substr(1); +} + +void piscsi_util::LogErrno(const string& msg) +{ + spdlog::error(errno ? msg + ": " + string(strerror(errno)) : msg); } // Pin the thread to a specific CPU diff --git a/cpp/shared/piscsi_util.h b/cpp/shared/piscsi_util.h index 762db353..477a8cab 100644 --- a/cpp/shared/piscsi_util.h +++ b/cpp/shared/piscsi_util.h @@ -3,13 +3,16 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once +#include #include +#include +#include using namespace std; @@ -18,11 +21,38 @@ namespace piscsi_util // Separator for compound options like ID:LUN static const char COMPONENT_SEPARATOR = ':'; - bool GetAsUnsignedInt(const string&, int&); - string ProcessId(const string&, int, int&, int&); - string Banner(const string&); + struct StringHash { + using is_transparent = void; - string GetExtensionLowerCase(const string&); + size_t operator()(string_view sv) const { + hash hasher; + return hasher(sv); + } + }; + + string Join(const auto& collection, const string_view separator = ", ") { + ostringstream s; + + for (const auto& element : collection) { + if (s.tellp()) { + s << separator; + } + + s << element; + } + + return s.str(); + } + + vector Split(const string&, char, int = INT_MAX); + string GetLocale(); + bool GetAsUnsignedInt(const string&, int&); + string ProcessId(const string&, int&, int&); + string Banner(string_view); + + string GetExtensionLowerCase(string_view); + + void LogErrno(const string&); void FixCpu(int); } diff --git a/cpp/shared/piscsi_version.cpp b/cpp/shared/piscsi_version.cpp index c24f5958..702a2d2b 100644 --- a/cpp/shared/piscsi_version.cpp +++ b/cpp/shared/piscsi_version.cpp @@ -4,7 +4,6 @@ // for Raspberry Pi // // Copyright (C) 2020 akuker -// [ Define the version string ] // //--------------------------------------------------------------------------- @@ -14,8 +13,8 @@ // The following should be updated for each release const int piscsi_major_version = 23; // Last two digits of year -const int piscsi_minor_version = 4; // Month -const int piscsi_patch_version = 1; // Patch number - increment for each update +const int piscsi_minor_version = 11; // Month +const int piscsi_patch_version = 1; // Patch number - increment for each update using namespace std; diff --git a/cpp/shared/protobuf_serializer.cpp b/cpp/shared/protobuf_serializer.cpp deleted file mode 100644 index ffca4e23..00000000 --- a/cpp/shared/protobuf_serializer.cpp +++ /dev/null @@ -1,80 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Copyright (C) 2022 Uwe Seimet -// -//--------------------------------------------------------------------------- - -#include "shared/protobuf_serializer.h" -#include "shared/piscsi_exceptions.h" -#include "generated/piscsi_interface.pb.h" -#include - -using namespace std; -using namespace piscsi_interface; - -//--------------------------------------------------------------------------- -// -// Serialize/Deserialize protobuf message: Length followed by the actual data. -// A little endian platform is assumed. -// -//--------------------------------------------------------------------------- - -void ProtobufSerializer::SerializeMessage(int fd, const google::protobuf::Message& message) const -{ - string data; - message.SerializeToString(&data); - - // Write the size of the protobuf data as a header - auto size = static_cast(data.length()); - if (write(fd, &size, sizeof(size)) != sizeof(size)) { - throw io_exception("Can't write protobuf message header"); - } - - // Write the actual protobuf data - if (write(fd, data.data(), size) != size) { - throw io_exception("Can't write protobuf message data"); - } -} - -void ProtobufSerializer::DeserializeMessage(int fd, google::protobuf::Message& message) const -{ - // Read the header with the size of the protobuf data - vector header_buf(4); - if (ReadBytes(fd, header_buf) < header_buf.size()) { - throw io_exception("Invalid protobuf message header"); - } - - const int size = (static_cast(header_buf[3]) << 24) + (static_cast(header_buf[2]) << 16) - + (static_cast(header_buf[1]) << 8) + static_cast(header_buf[0]); - if (size < 0) { - throw io_exception("Invalid protobuf message header"); - } - - // Read the binary protobuf data - vector data_buf(size); - if (ReadBytes(fd, data_buf) < data_buf.size()) { - throw io_exception("Missing protobuf message data"); - } - - // Create protobuf message - string data((const char *)data_buf.data(), size); - message.ParseFromString(data); -} - -size_t ProtobufSerializer::ReadBytes(int fd, vector& buf) const -{ - size_t offset = 0; - while (offset < buf.size()) { - const ssize_t len = read(fd, &buf.data()[offset], buf.size() - offset); - if (len <= 0) { - return len; - } - - offset += len; - } - - return offset; -} diff --git a/cpp/shared/protobuf_serializer.h b/cpp/shared/protobuf_serializer.h deleted file mode 100644 index b9a67302..00000000 --- a/cpp/shared/protobuf_serializer.h +++ /dev/null @@ -1,29 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Copyright (C) 2022 Uwe Seimet -// -// Helper for serializing/deserializing protobuf messages -// -//--------------------------------------------------------------------------- - -#pragma once - -#include "google/protobuf/message.h" -#include - -using namespace std; - -class ProtobufSerializer -{ -public: - - ProtobufSerializer() = default; - ~ProtobufSerializer() = default; - - void SerializeMessage(int, const google::protobuf::Message&) const; - void DeserializeMessage(int, google::protobuf::Message&) const; - size_t ReadBytes(int, vector&) const; -}; diff --git a/cpp/shared/protobuf_util.cpp b/cpp/shared/protobuf_util.cpp index 9557b5bc..e4c1b9b0 100644 --- a/cpp/shared/protobuf_util.cpp +++ b/cpp/shared/protobuf_util.cpp @@ -3,15 +3,18 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "log.h" +#include "shared/piscsi_exceptions.h" #include "piscsi_util.h" -#include "protobuf_serializer.h" #include "protobuf_util.h" +#include #include +#include +#include +#include using namespace std; using namespace piscsi_util; @@ -32,106 +35,99 @@ void protobuf_util::ParseParameters(PbDeviceDefinition& device, const string& pa return; } - stringstream ss(params); - string p; - while (getline(ss, p, COMPONENT_SEPARATOR)) { - if (!p.empty()) { - const size_t separator_pos = p.find(KEY_VALUE_SEPARATOR); - if (separator_pos != string::npos) { - SetParam(device, p.substr(0, separator_pos), string_view(p).substr(separator_pos + 1)); - } + for (const auto& p : Split(params, COMPONENT_SEPARATOR)) { + if (const auto& param = Split(p, KEY_VALUE_SEPARATOR, 2); param.size() == 2) { + SetParam(device, param[0], param[1]); } } } -string protobuf_util::GetParam(const PbCommand& command, const string& key) +string protobuf_util::SetCommandParams(PbCommand& command, const string& params) { - const auto& it = command.params().find(key); - return it != command.params().end() ? it->second : ""; -} - -string protobuf_util::GetParam(const PbDeviceDefinition& device, const string& key) -{ - const auto& it = device.params().find(key); - return it != device.params().end() ? it->second : ""; -} - -void protobuf_util::SetParam(PbCommand& command, const string& key, string_view value) -{ - if (!key.empty() && !value.empty()) { - auto& map = *command.mutable_params(); - map[key] = value; + if (params.find(KEY_VALUE_SEPARATOR) != string::npos) { + return SetFromGenericParams(command, params); } -} -void protobuf_util::SetParam(PbDevice& device, const string& key, string_view value) -{ - if (!key.empty() && !value.empty()) { - auto& map = *device.mutable_params(); - map[key] = value; - } -} - -void protobuf_util::SetParam(PbDeviceDefinition& device, const string& key, string_view value) -{ - if (!key.empty() && !value.empty()) { - auto& map = *device.mutable_params(); - map[key] = value; - } -} - -void protobuf_util::SetPatternParams(PbCommand& command, string_view patterns) -{ string folder_pattern; string file_pattern; - if (const size_t separator_pos = patterns.find(COMPONENT_SEPARATOR); separator_pos != string::npos) { - folder_pattern = patterns.substr(0, separator_pos); - file_pattern = patterns.substr(separator_pos + 1); - } - else { - file_pattern = patterns; + string operations; + + switch (const auto& components = Split(params, COMPONENT_SEPARATOR, 3); components.size()) { + case 3: + operations = components[2]; + [[fallthrough]]; + + case 2: + folder_pattern = components[0]; + file_pattern = components[1]; + break; + + case 1: + file_pattern = components[0]; + break; + + default: + break; } SetParam(command, "folder_pattern", folder_pattern); SetParam(command, "file_pattern", file_pattern); -} - -void protobuf_util::SetProductData(PbDeviceDefinition& device, const string& data) -{ - string name = data; - - if (size_t separator_pos = name.find(COMPONENT_SEPARATOR); separator_pos != string::npos) { - device.set_vendor(name.substr(0, separator_pos)); - name = name.substr(separator_pos + 1); - separator_pos = name.find(COMPONENT_SEPARATOR); - if (separator_pos != string::npos) { - device.set_product(name.substr(0, separator_pos)); - device.set_revision(name.substr(separator_pos + 1)); - } - else { - device.set_product(name); - } - } - else { - device.set_vendor(name); - } -} - -string protobuf_util::SetIdAndLun(PbDeviceDefinition& device, const string& value, int max_luns) -{ - int id; - int lun; - if (const string error = ProcessId(value, max_luns, id, lun); !error.empty()) { - return error; - } - - device.set_id(id); - device.set_unit(lun); + SetParam(command, "operations", operations); return ""; } -string protobuf_util::ListDevices(const list& pb_devices) +string protobuf_util::SetFromGenericParams(PbCommand& command, const string& params) +{ + for (const string& key_value : Split(params, COMPONENT_SEPARATOR)) { + const auto& param = Split(key_value, KEY_VALUE_SEPARATOR, 2); + if (param.size() > 1 && !param[0].empty()) { + SetParam(command, param[0], param[1]); + } + else { + return "Parameter '" + key_value + "' has to be a key/value pair"; + } + } + + return ""; +} + +void protobuf_util::SetProductData(PbDeviceDefinition& device, const string& data) +{ + const auto& components = Split(data, COMPONENT_SEPARATOR, 3); + switch (components.size()) { + case 3: + device.set_revision(components[2]); + [[fallthrough]]; + + case 2: + device.set_product(components[1]); + [[fallthrough]]; + + case 1: + device.set_vendor(components[0]); + break; + + default: + break; + } +} + +string protobuf_util::SetIdAndLun(PbDeviceDefinition& device, const string& value) +{ + int id; + int lun; + if (const string error = ProcessId(value, id, lun); !error.empty()) { + return error; + } + + device.set_id(id); + device.set_unit(lun != -1 ? lun : 0); + + return ""; +} + +string protobuf_util::ListDevices(const vector& pb_devices) { if (pb_devices.empty()) { return "No devices currently attached.\n"; @@ -142,8 +138,8 @@ string protobuf_util::ListDevices(const list& pb_devices) << "| ID | LUN | TYPE | IMAGE FILE\n" << "+----+-----+------+-------------------------------------\n"; - list devices = pb_devices; - devices.sort([](const auto& a, const auto& b) { return a.id() < b.id() || a.unit() < b.unit(); }); + vector devices = pb_devices; + ranges::sort(devices, [](const auto& a, const auto& b) { return a.id() < b.id() || a.unit() < b.unit(); }); for (const auto& device : devices) { string filename; @@ -169,7 +165,7 @@ string protobuf_util::ListDevices(const list& pb_devices) break; } - s << "| " << device.id() << " | " << device.unit() << " | " << PbDeviceType_Name(device.type()) << " | " + s << "| " << device.id() << " | " << setw(3) << device.unit() << " | " << PbDeviceType_Name(device.type()) << " | " << (filename.empty() ? "NO MEDIUM" : filename) << (!device.status().removed() && (device.properties().read_only() || device.status().protected_()) ? " (READ-ONLY)" : "") << '\n'; @@ -179,3 +175,68 @@ string protobuf_util::ListDevices(const list& pb_devices) return s.str(); } + +//--------------------------------------------------------------------------- +// +// Serialize/Deserialize protobuf message: Length followed by the actual data. +// A little endian platform is assumed. +// +//--------------------------------------------------------------------------- + +void protobuf_util::SerializeMessage(int fd, const google::protobuf::Message& message) +{ + const string data = message.SerializeAsString(); + + // Write the size of the protobuf data as a header + const auto size = static_cast(data.length()); + if (write(fd, &size, sizeof(size)) != sizeof(size)) { + throw io_exception("Can't write protobuf message size"); + } + + // Write the actual protobuf data + if (write(fd, data.data(), size) != size) { + throw io_exception("Can't write protobuf message data"); + } +} + +void protobuf_util::DeserializeMessage(int fd, google::protobuf::Message& message) +{ + // Read the header with the size of the protobuf data + array header_buf; + if (ReadBytes(fd, header_buf) < header_buf.size()) { + throw io_exception("Can't read protobuf message size"); + } + + const int size = (static_cast(header_buf[3]) << 24) + (static_cast(header_buf[2]) << 16) + + (static_cast(header_buf[1]) << 8) + static_cast(header_buf[0]); + if (size < 0) { + throw io_exception("Invalid protobuf message size"); + } + + // Read the binary protobuf data + vector data_buf(size); + if (ReadBytes(fd, data_buf) != data_buf.size()) { + throw io_exception("Invalid protobuf message data"); + } + + message.ParseFromArray(data_buf.data(), size); +} + +size_t protobuf_util::ReadBytes(int fd, span buf) +{ + size_t offset = 0; + while (offset < buf.size()) { + const auto len = read(fd, &buf.data()[offset], buf.size() - offset); + if (len == -1) { + throw io_exception("Read error: " + string(strerror(errno))); + } + + if (!len) { + break; + } + + offset += len; + } + + return offset; +} diff --git a/cpp/shared/protobuf_util.h b/cpp/shared/protobuf_util.h index a0dcd760..f07b0322 100644 --- a/cpp/shared/protobuf_util.h +++ b/cpp/shared/protobuf_util.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // // Helper methods for setting up/evaluating protobuf messages // @@ -11,10 +11,10 @@ #pragma once -#include "google/protobuf/message.h" -#include "generated/piscsi_interface.pb.h" #include -#include +#include +#include +#include "generated/piscsi_interface.pb.h" using namespace std; using namespace piscsi_interface; @@ -23,14 +23,28 @@ namespace protobuf_util { static const char KEY_VALUE_SEPARATOR = '='; + string GetParam(const auto& item, const string& key) + { + const auto& it = item.params().find(key); + return it != item.params().end() ? it->second : ""; + } + + void SetParam(auto& item, const string& key, string_view value) + { + if (!key.empty() && !value.empty()) { + auto& map = *item.mutable_params(); + map[key] = value; + } + } + void ParseParameters(PbDeviceDefinition&, const string&); - string GetParam(const PbCommand&, const string&); - string GetParam(const PbDeviceDefinition&, const string&); - void SetParam(PbCommand&, const string&, string_view); - void SetParam(PbDevice&, const string&, string_view); - void SetParam(PbDeviceDefinition&, const string&, string_view); - void SetPatternParams(PbCommand&, string_view); + string SetCommandParams(PbCommand&, const string&); + string SetFromGenericParams(PbCommand&, const string&); void SetProductData(PbDeviceDefinition&, const string&); - string SetIdAndLun(PbDeviceDefinition&, const string&, int); - string ListDevices(const list&); + string SetIdAndLun(PbDeviceDefinition&, const string&); + string ListDevices(const vector&); + + void SerializeMessage(int, const google::protobuf::Message&); + void DeserializeMessage(int, google::protobuf::Message&); + size_t ReadBytes(int, span); } diff --git a/cpp/shared/scsi.h b/cpp/shared/scsi.h index c8a4279b..b5611512 100644 --- a/cpp/shared/scsi.h +++ b/cpp/shared/scsi.h @@ -1,33 +1,39 @@ //--------------------------------------------------------------------------- // -// X68000 EMULATOR "XM6" +// X68000 EMULATOR "XM6" // -// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) -// Copyright (C) 2014-2020 GIMONS +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once +#include #include +#include using namespace std; +// Command Descriptor Block +using cdb_t = span; + namespace scsi_defs { -enum class scsi_level : int { - SCSI_1_CCS = 1, - SCSI_2 = 2, - SPC = 3, - SPC_2 = 4, - SPC_3 = 5, - SPC_4 = 6, - SPC_5 = 7, - SPC_6 = 8 +enum class scsi_level { + scsi_1_ccs = 1, + scsi_2 = 2, + spc = 3, + spc_2 = 4, + spc_3 = 5, + spc_4 = 6, + spc_5 = 7, + spc_6 = 8 }; -// Phase definitions -enum class phase_t : int { +// Phase definitions +enum class phase_t { busfree, arbitration, selection, @@ -41,16 +47,16 @@ enum class phase_t : int { reserved }; -enum class device_type : int { - DIRECT_ACCESS = 0, - PRINTER = 2, - PROCESSOR = 3, - CD_ROM = 5, - OPTICAL_MEMORY = 7, - COMMUNICATIONS = 9 +enum class device_type { + direct_access = 0, + printer = 2, + processor = 3, + cd_rom = 5, + optical_memory = 7, + communications = 9 }; -enum class scsi_command : int { +enum class scsi_command { eCmdTestUnitReady = 0x00, eCmdRezero = 0x01, eCmdRequestSense = 0x03, @@ -104,35 +110,39 @@ enum class scsi_command : int { eCmdReportLuns = 0xA0 }; -enum class status : int { GOOD = 0x00, CHECK_CONDITION = 0x02, RESERVATION_CONFLICT = 0x18 }; - -enum class sense_key : int { - NO_SENSE = 0x00, - NOT_READY = 0x02, - MEDIUM_ERROR = 0x03, - ILLEGAL_REQUEST = 0x05, - UNIT_ATTENTION = 0x06, - DATA_PROTECT = 0x07, - ABORTED_COMMAND = 0x0b +enum class status { + good = 0x00, + check_condition = 0x02, + reservation_conflict = 0x18 }; -enum class asc : int { - NO_ADDITIONAL_SENSE_INFORMATION = 0x00, - WRITE_FAULT = 0x03, - READ_FAULT = 0x11, - INVALID_COMMAND_OPERATION_CODE = 0x20, - LBA_OUT_OF_RANGE = 0x21, - INVALID_FIELD_IN_CDB = 0x24, - INVALID_LUN = 0x25, - INVALID_FIELD_IN_PARAMETER_LIST = 0x26, - WRITE_PROTECTED = 0x27, - NOT_READY_TO_READY_CHANGE = 0x28, - POWER_ON_OR_RESET = 0x29, - MEDIUM_NOT_PRESENT = 0x3a, - LOAD_OR_EJECT_FAILED = 0x53 +enum class sense_key { + no_sense = 0x00, + not_ready = 0x02, + medium_error = 0x03, + illegal_request = 0x05, + unit_attention = 0x06, + data_protect = 0x07, + aborted_command = 0x0b }; -static const unordered_map> command_mapping = { +enum class asc { + no_additional_sense_information = 0x00, + write_fault = 0x03, + read_fault = 0x11, + invalid_command_operation_code = 0x20, + lba_out_of_range = 0x21, + invalid_field_in_cdb = 0x24, + invalid_lun = 0x25, + invalid_field_in_parameter_list = 0x26, + write_protected = 0x27, + not_ready_to_ready_change = 0x28, + power_on_or_reset = 0x29, + medium_not_present = 0x3a, + load_or_eject_failed = 0x53 +}; + +static const unordered_map> command_mapping = { {scsi_command::eCmdTestUnitReady, make_pair(6, "TestUnitReady")}, {scsi_command::eCmdRezero, make_pair(6, "Rezero")}, {scsi_command::eCmdRequestSense, make_pair(6, "RequestSense")}, diff --git a/cpp/test/abstract_controller_test.cpp b/cpp/test/abstract_controller_test.cpp index 8e1e5352..41270b4d 100644 --- a/cpp/test/abstract_controller_test.cpp +++ b/cpp/test/abstract_controller_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -15,9 +15,7 @@ using namespace scsi_defs; TEST(AbstractControllerTest, AllocateCmd) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; EXPECT_EQ(16, controller.GetCmd().size()); controller.AllocateCmd(1234); @@ -26,9 +24,7 @@ TEST(AbstractControllerTest, AllocateCmd) TEST(AbstractControllerTest, AllocateBuffer) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; controller.AllocateBuffer(1); EXPECT_LE(1, controller.GetBuffer().size()); @@ -39,25 +35,23 @@ TEST(AbstractControllerTest, AllocateBuffer) TEST(AbstractControllerTest, Reset) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); + auto controller = make_shared(bus, 0); auto device = make_shared(0); controller->AddDevice(device); controller->SetPhase(phase_t::status); EXPECT_EQ(phase_t::status, controller->GetPhase()); + EXPECT_CALL(*bus, Reset()); controller->Reset(); EXPECT_TRUE(controller->IsBusFree()); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); EXPECT_EQ(0, controller->GetLength()); } TEST(AbstractControllerTest, Next) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; controller.SetNext(0x1234); EXPECT_EQ(0x1234, controller.GetNext()); @@ -67,9 +61,7 @@ TEST(AbstractControllerTest, Next) TEST(AbstractControllerTest, Message) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; controller.SetMessage(0x12); EXPECT_EQ(0x12, controller.GetMessage()); @@ -77,9 +69,7 @@ TEST(AbstractControllerTest, Message) TEST(AbstractControllerTest, ByteTransfer) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; controller.SetByteTransfer(false); EXPECT_FALSE(controller.IsByteTransfer()); @@ -89,9 +79,7 @@ TEST(AbstractControllerTest, ByteTransfer) TEST(AbstractControllerTest, BytesToTransfer) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; controller.SetBytesToTransfer(0x1234); EXPECT_EQ(0x1234, controller.GetBytesToTransfer()); @@ -101,70 +89,17 @@ TEST(AbstractControllerTest, BytesToTransfer) TEST(AbstractControllerTest, GetMaxLuns) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; EXPECT_EQ(32, controller.GetMaxLuns()); } TEST(AbstractControllerTest, Status) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; - controller.SetStatus(status::RESERVATION_CONFLICT); - EXPECT_EQ(status::RESERVATION_CONFLICT, controller.GetStatus()); -} - -TEST(AbstractControllerTest, ProcessPhase) -{ - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); - - controller.SetPhase(phase_t::selection); - EXPECT_CALL(controller, Selection); - controller.ProcessPhase(); - - controller.SetPhase(phase_t::busfree); - EXPECT_CALL(controller, BusFree); - controller.ProcessPhase(); - - controller.SetPhase(phase_t::datain); - EXPECT_CALL(controller, DataIn); - controller.ProcessPhase(); - - controller.SetPhase(phase_t::dataout); - EXPECT_CALL(controller, DataOut); - controller.ProcessPhase(); - - controller.SetPhase(phase_t::command); - EXPECT_CALL(controller, Command); - controller.ProcessPhase(); - - controller.SetPhase(phase_t::status); - EXPECT_CALL(controller, Status); - controller.ProcessPhase(); - - controller.SetPhase(phase_t::msgin); - EXPECT_CALL(controller, MsgIn); - controller.ProcessPhase(); - - controller.SetPhase(phase_t::msgout); - EXPECT_CALL(controller, MsgOut); - controller.ProcessPhase(); - - controller.SetPhase(phase_t::reselection); - EXPECT_THAT([&] { controller.ProcessPhase(); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ABORTED_COMMAND), - Property(&scsi_exception::get_asc, asc::NO_ADDITIONAL_SENSE_INFORMATION)))); - - controller.SetPhase(phase_t::reserved); - EXPECT_THAT([&] { controller.ProcessPhase(); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ABORTED_COMMAND), - Property(&scsi_exception::get_asc, asc::NO_ADDITIONAL_SENSE_INFORMATION)))); + controller.SetStatus(status::reservation_conflict); + EXPECT_EQ(status::reservation_conflict, controller.GetStatus()); } TEST(AbstractControllerTest, DeviceLunLifeCycle) @@ -172,9 +107,7 @@ TEST(AbstractControllerTest, DeviceLunLifeCycle) const int ID = 1; const int LUN = 4; - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, ID); + auto controller = make_shared>(ID); auto device1 = make_shared(LUN); auto device2 = make_shared(32); @@ -190,33 +123,16 @@ TEST(AbstractControllerTest, DeviceLunLifeCycle) EXPECT_FALSE(controller->HasDeviceForLun(0)); EXPECT_NE(nullptr, controller->GetDeviceForLun(LUN)); EXPECT_EQ(nullptr, controller->GetDeviceForLun(0)); - EXPECT_TRUE(controller->RemoveDevice(device1)); + EXPECT_TRUE(controller->RemoveDevice(*device1)); EXPECT_EQ(0, controller->GetLunCount()); - EXPECT_FALSE(controller->RemoveDevice(device1)); -} - -TEST(AbstractControllerTest, ExtractInitiatorId) -{ - const int ID = 1; - const int INITIATOR_ID = 7; - - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, ID); - - EXPECT_EQ(INITIATOR_ID, controller.ExtractInitiatorId((1 << INITIATOR_ID) | ( 1 << ID))); - EXPECT_EQ(AbstractController::UNKNOWN_INITIATOR_ID, controller.ExtractInitiatorId(1 << ID)); + EXPECT_FALSE(controller->RemoveDevice(*device1)); } TEST(AbstractControllerTest, GetOpcode) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; - auto& cmd = controller.GetCmd(); - - cmd[0] = static_cast(scsi_command::eCmdInquiry); + controller.SetCmdByte(0, static_cast(scsi_command::eCmdInquiry)); EXPECT_EQ(scsi_command::eCmdInquiry, controller.GetOpcode()); } @@ -224,33 +140,25 @@ TEST(AbstractControllerTest, GetLun) { const int LUN = 3; - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; - auto& cmd = controller.GetCmd(); - - cmd[1] = LUN << 5; + controller.SetCmdByte(1, LUN << 5); EXPECT_EQ(LUN, controller.GetLun()); } TEST(AbstractControllerTest, Blocks) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; controller.SetBlocks(1); - EXPECT_EQ(1, controller.GetBlocks()); + EXPECT_TRUE(controller.HasBlocks()); controller.DecrementBlocks(); - EXPECT_EQ(0, controller.GetBlocks()); + EXPECT_FALSE(controller.HasBlocks()); } TEST(AbstractControllerTest, Length) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; EXPECT_FALSE(controller.HasValidLength()); @@ -261,9 +169,7 @@ TEST(AbstractControllerTest, Length) TEST(AbstractControllerTest, UpdateOffsetAndLength) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; EXPECT_FALSE(controller.HasValidLength()); @@ -273,9 +179,7 @@ TEST(AbstractControllerTest, UpdateOffsetAndLength) TEST(AbstractControllerTest, Offset) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; controller.ResetOffset(); EXPECT_EQ(0, controller.GetOffset()); diff --git a/cpp/test/command_context_test.cpp b/cpp/test/command_context_test.cpp index d99a1d59..f1f9772d 100644 --- a/cpp/test/command_context_test.cpp +++ b/cpp/test/command_context_test.cpp @@ -3,52 +3,149 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include +#include "test/test_shared.h" +#include "shared/piscsi_exceptions.h" +#include "shared/protobuf_util.h" #include "piscsi/command_context.h" +#include +#include -TEST(CommandContext, GetSerializer) +using namespace protobuf_util; + +TEST(CommandContext, SetGetDefaultFolder) { - CommandContext context("", -1); + PbCommand command; + CommandContext context(command, "folder1", ""); - // There is nothing more that can be tested - context.GetSerializer(); + EXPECT_EQ("folder1", context.GetDefaultFolder()); + context.SetDefaultFolder("folder2"); + EXPECT_EQ("folder2", context.GetDefaultFolder()); } -TEST(CommandContext, IsValid) +TEST(CommandContext, ReadCommand) { - CommandContext context("", -1); + int fd = open(CreateTempFile(0).string().c_str(), O_RDONLY); + CommandContext context1(fd); + EXPECT_FALSE(context1.ReadCommand()); + close(fd); - EXPECT_FALSE(context.IsValid()); + // Invalid magic with wrong length + vector data = { byte{'1'}, byte{'2'}, byte{'3'} }; + fd = open(CreateTempFileWithData(data).string().c_str(), O_RDONLY); + CommandContext context2(fd); + EXPECT_THROW(context2.ReadCommand(), io_exception); + close(fd); - context.SetFd(1); - EXPECT_TRUE(context.IsValid()); + // Invalid magic with right length + data = { byte{'1'}, byte{'2'}, byte{'3'}, byte{'4'}, byte{'5'}, byte{'6'} }; + fd = open(CreateTempFileWithData(data).string().c_str(), O_RDONLY); + CommandContext context3(fd); + EXPECT_THROW(context3.ReadCommand(), io_exception); + close(fd); + + data = { byte{'R'}, byte{'A'}, byte{'S'}, byte{'C'}, byte{'S'}, byte{'I'}, byte{'1'} }; + // Valid magic but invalid command + fd = open(CreateTempFileWithData(data).string().c_str(), O_RDONLY); + CommandContext context4(fd); + EXPECT_THROW(context4.ReadCommand(), io_exception); + close(fd); + + data = { byte{'R'}, byte{'A'}, byte{'S'}, byte{'C'}, byte{'S'}, byte{'I'} }; + // Valid magic but missing command + fd = open(CreateTempFileWithData(data).string().c_str(), O_RDONLY); + CommandContext context5(fd); + EXPECT_THROW(context5.ReadCommand(), io_exception); + close(fd); + + const string filename = CreateTempFileWithData(data).string(); + fd = open(filename.c_str(), O_RDWR | O_APPEND); + PbCommand command; + command.set_operation(PbOperation::SERVER_INFO); + SerializeMessage(fd, command); + close(fd); + fd = open(filename.c_str(), O_RDONLY); + CommandContext context6(fd); + EXPECT_TRUE(context6.ReadCommand()); + close(fd); + EXPECT_EQ(PbOperation::SERVER_INFO, context6.GetCommand().operation()); } -TEST(CommandContext, Cleanup) +TEST(CommandContext, GetCommand) { - CommandContext context("", 0); + PbCommand command; + command.set_operation(PbOperation::SERVER_INFO); + CommandContext context(command, "", ""); + EXPECT_EQ(PbOperation::SERVER_INFO, context.GetCommand().operation()); +} - EXPECT_EQ(0, context.GetFd()); - context.Cleanup(); - EXPECT_EQ(-1, context.GetFd()); +TEST(CommandContext, WriteResult) +{ + const string filename = CreateTempFile(0); + int fd = open(filename.c_str(), O_RDWR | O_APPEND); + PbResult result; + result.set_status(false); + result.set_error_code(PbErrorCode::UNAUTHORIZED); + CommandContext context(fd); + context.WriteResult(result); + close(fd); + EXPECT_FALSE(result.status()); + + fd = open(filename.c_str(), O_RDONLY); + result.set_status(true); + DeserializeMessage(fd, result); + close(fd); + EXPECT_FALSE(result.status()); + EXPECT_EQ(PbErrorCode::UNAUTHORIZED, result.error_code()); +} + +TEST(CommandContext, WriteSuccessResult) +{ + const string filename = CreateTempFile(0); + int fd = open(filename.c_str(), O_RDWR | O_APPEND); + PbResult result; + result.set_status(false); + CommandContext context(fd); + context.WriteSuccessResult(result); + close(fd); + EXPECT_TRUE(result.status()); } TEST(CommandContext, ReturnLocalizedError) { - CommandContext context("en_US", -1); + PbCommand command; + CommandContext context(command, "", "en_US"); EXPECT_FALSE(context.ReturnLocalizedError(LocalizationKey::ERROR_LOG_LEVEL)); } -TEST(CommandContext, ReturnStatus) +TEST(CommandContext, ReturnSuccessStatus) { - CommandContext context("", -1); + PbCommand command; - EXPECT_TRUE(context.ReturnStatus(true, "status")); - EXPECT_FALSE(context.ReturnStatus(false, "status")); + CommandContext context1(command, "", ""); + EXPECT_TRUE(context1.ReturnSuccessStatus()); + + const int fd = open("/dev/null", O_RDWR); + CommandContext context2(fd); + EXPECT_TRUE(context2.ReturnSuccessStatus()); + close(fd); +} + +TEST(CommandContext, ReturnErrorStatus) +{ + PbCommand command; + + CommandContext context1(command, "", ""); + EXPECT_FALSE(context1.ReturnErrorStatus("error")); + + const int fd = open("/dev/null", O_RDWR); + CommandContext context2(fd); + EXPECT_FALSE(context2.ReturnErrorStatus("error")); + close(fd); } diff --git a/cpp/test/controller_manager_test.cpp b/cpp/test/controller_manager_test.cpp index 9ae770a4..22a329e8 100644 --- a/cpp/test/controller_manager_test.cpp +++ b/cpp/test/controller_manager_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -13,54 +13,75 @@ TEST(ControllerManagerTest, LifeCycle) { - const int ID = 4; + const int ID1 = 4; + const int ID2 = 5; const int LUN1 = 0; const int LUN2 = 3; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; DeviceFactory device_factory; auto device = device_factory.CreateDevice(SCHS, -1, ""); - EXPECT_FALSE(controller_manager->AttachToScsiController(ID, device)); + EXPECT_FALSE(controller_manager.AttachToController(*bus, ID1, device)); device = device_factory.CreateDevice(SCHS, LUN1, ""); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device)); - auto controller = controller_manager->FindController(ID); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID1, device)); + EXPECT_TRUE(controller_manager.HasController(ID1)); + auto controller = controller_manager.FindController(ID1); EXPECT_NE(nullptr, controller); EXPECT_EQ(1, controller->GetLunCount()); - EXPECT_NE(nullptr, controller_manager->IdentifyController(1 << ID)); - EXPECT_EQ(nullptr, controller_manager->IdentifyController(0)); - EXPECT_EQ(nullptr, controller_manager->FindController(0)); - EXPECT_NE(nullptr, controller_manager->GetDeviceByIdAndLun(ID, LUN1)); - EXPECT_EQ(nullptr, controller_manager->GetDeviceByIdAndLun(0, 0)); + EXPECT_FALSE(controller_manager.HasController(0)); + EXPECT_EQ(nullptr, controller_manager.FindController(0)); + EXPECT_TRUE(controller_manager.HasDeviceForIdAndLun(ID1, LUN1)); + EXPECT_NE(nullptr, controller_manager.GetDeviceForIdAndLun(ID1, LUN1)); + EXPECT_FALSE(controller_manager.HasDeviceForIdAndLun(0, 0)); + EXPECT_EQ(nullptr, controller_manager.GetDeviceForIdAndLun(0, 0)); device = device_factory.CreateDevice(SCHS, LUN2, ""); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device)); - controller = controller_manager->FindController(ID); - EXPECT_TRUE(controller_manager->DeleteController(controller)); - EXPECT_EQ(nullptr, controller_manager->FindController(ID)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID1, device)); + EXPECT_TRUE(controller_manager.HasController(ID1)); + controller = controller_manager.FindController(ID1); + EXPECT_NE(nullptr, controller_manager.FindController(ID1)); + EXPECT_TRUE(controller_manager.DeleteController(*controller)); + EXPECT_EQ(nullptr, controller_manager.FindController(ID1)); - controller_manager->DeleteAllControllers(); - EXPECT_EQ(nullptr, controller_manager->FindController(ID)); - EXPECT_EQ(nullptr, controller_manager->GetDeviceByIdAndLun(ID, LUN1)); + auto disk = make_shared(); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID2, disk)); + EXPECT_CALL(*disk, FlushCache); + controller_manager.DeleteAllControllers(); + EXPECT_FALSE(controller_manager.HasController(ID1)); + EXPECT_EQ(nullptr, controller_manager.FindController(ID1)); + EXPECT_EQ(nullptr, controller_manager.GetDeviceForIdAndLun(ID1, LUN1)); + EXPECT_FALSE(controller_manager.HasDeviceForIdAndLun(ID1, LUN1)); + EXPECT_FALSE(controller_manager.HasController(ID2)); + EXPECT_EQ(nullptr, controller_manager.FindController(ID2)); + EXPECT_EQ(nullptr, controller_manager.GetDeviceForIdAndLun(ID2, LUN1)); + EXPECT_FALSE(controller_manager.HasDeviceForIdAndLun(ID2, LUN1)); } -TEST(ControllerManagerTest, AttachToScsiController) +TEST(ControllerManagerTest, AttachToController) { const int ID = 4; const int LUN1 = 3; const int LUN2 = 0; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; DeviceFactory device_factory; auto device1 = device_factory.CreateDevice(SCHS, LUN1, ""); - EXPECT_FALSE(controller_manager->AttachToScsiController(ID, device1)) << "LUN 0 is missing"; + EXPECT_FALSE(controller_manager.AttachToController(*bus, ID, device1)) << "LUN 0 is missing"; auto device2 = device_factory.CreateDevice(SCLP, LUN2, ""); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device2)); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device1)); - EXPECT_FALSE(controller_manager->AttachToScsiController(ID, device1)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device2)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device1)); + EXPECT_FALSE(controller_manager.AttachToController(*bus, ID, device1)); +} + +TEST(ControllerManager, ProcessOnController) +{ + ControllerManager controller_manager; + + EXPECT_EQ(AbstractController::piscsi_shutdown_mode::NONE, controller_manager.ProcessOnController(0)); } diff --git a/cpp/test/ctapdriver_test.cpp b/cpp/test/ctapdriver_test.cpp index 031e1acc..b587d471 100644 --- a/cpp/test/ctapdriver_test.cpp +++ b/cpp/test/ctapdriver_test.cpp @@ -3,11 +3,12 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "mocks.h" +#include #include "devices/ctapdriver.h" TEST(CTapDriverTest, Crc32) @@ -15,27 +16,27 @@ TEST(CTapDriverTest, Crc32) array buf; buf.fill(0x00); - EXPECT_EQ(0xe3d887bb, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN)); + EXPECT_EQ(0xe3d887bb, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); buf.fill(0xff); - EXPECT_EQ(0x814765f4, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN)); + EXPECT_EQ(0x814765f4, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); buf.fill(0x10); - EXPECT_EQ(0xb7288Cd3, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN)); + EXPECT_EQ(0xb7288Cd3, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); buf.fill(0x7f); - EXPECT_EQ(0x4b543477, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN)); + EXPECT_EQ(0x4b543477, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); buf.fill(0x80); - EXPECT_EQ(0x29cbd638, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN)); + EXPECT_EQ(0x29cbd638, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); for (size_t i = 0; i < buf.size(); i++) { buf[i] = (uint8_t)i; } - EXPECT_EQ(0xe7870705, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN)); + EXPECT_EQ(0xe7870705, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); for (size_t i = buf.size() - 1; i > 0; i--) { buf[i] = (uint8_t)i; } - EXPECT_EQ(0xe7870705, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN)); + EXPECT_EQ(0xe7870705, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); } diff --git a/cpp/test/device_factory_test.cpp b/cpp/test/device_factory_test.cpp index def637b6..0c51c194 100644 --- a/cpp/test/device_factory_test.cpp +++ b/cpp/test/device_factory_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -42,44 +42,43 @@ TEST(DeviceFactoryTest, GetTypeForFile) TEST(DeviceFactoryTest, GetSectorSizes) { DeviceFactory device_factory; - unordered_set sector_sizes; - sector_sizes = device_factory.GetSectorSizes(SCHD); + unordered_set sector_sizes = device_factory.GetSectorSizes(SCHD); EXPECT_EQ(4, sector_sizes.size()); - EXPECT_TRUE(sector_sizes.find(512) != sector_sizes.end()); - EXPECT_TRUE(sector_sizes.find(1024) != sector_sizes.end()); - EXPECT_TRUE(sector_sizes.find(2048) != sector_sizes.end()); - EXPECT_TRUE(sector_sizes.find(4096) != sector_sizes.end()); + EXPECT_TRUE(sector_sizes.contains(512)); + EXPECT_TRUE(sector_sizes.contains(1024)); + EXPECT_TRUE(sector_sizes.contains(2048)); + EXPECT_TRUE(sector_sizes.contains(4096)); sector_sizes = device_factory.GetSectorSizes(SCRM); EXPECT_EQ(4, sector_sizes.size()); - EXPECT_TRUE(sector_sizes.find(512) != sector_sizes.end()); - EXPECT_TRUE(sector_sizes.find(1024) != sector_sizes.end()); - EXPECT_TRUE(sector_sizes.find(2048) != sector_sizes.end()); - EXPECT_TRUE(sector_sizes.find(4096) != sector_sizes.end()); + EXPECT_TRUE(sector_sizes.contains(512)); + EXPECT_TRUE(sector_sizes.contains(1024)); + EXPECT_TRUE(sector_sizes.contains(2048)); + EXPECT_TRUE(sector_sizes.contains(4096)); sector_sizes = device_factory.GetSectorSizes(SCMO); EXPECT_EQ(4, sector_sizes.size()); - EXPECT_TRUE(sector_sizes.find(512) != sector_sizes.end()); - EXPECT_TRUE(sector_sizes.find(1024) != sector_sizes.end()); - EXPECT_TRUE(sector_sizes.find(2048) != sector_sizes.end()); - EXPECT_TRUE(sector_sizes.find(4096) != sector_sizes.end()); + EXPECT_TRUE(sector_sizes.contains(512)); + EXPECT_TRUE(sector_sizes.contains(1024)); + EXPECT_TRUE(sector_sizes.contains(2048)); + EXPECT_TRUE(sector_sizes.contains(4096)); sector_sizes = device_factory.GetSectorSizes(SCCD); EXPECT_EQ(2, sector_sizes.size()); - EXPECT_TRUE(sector_sizes.find(512) != sector_sizes.end()); - EXPECT_TRUE(sector_sizes.find(2048) != sector_sizes.end()); + EXPECT_TRUE(sector_sizes.contains(512)); + EXPECT_TRUE(sector_sizes.contains(2048)); } TEST(DeviceFactoryTest, GetExtensionMapping) { DeviceFactory device_factory; - unordered_map mapping = device_factory.GetExtensionMapping(); + auto mapping = device_factory.GetExtensionMapping(); EXPECT_EQ(10, mapping.size()); EXPECT_EQ(SCHD, mapping["hd1"]); EXPECT_EQ(SCHD, mapping["hds"]); @@ -93,35 +92,6 @@ TEST(DeviceFactoryTest, GetExtensionMapping) EXPECT_EQ(SCCD, mapping["is1"]); } -TEST(DeviceFactoryTest, GetDefaultParams) -{ - DeviceFactory device_factory; - - unordered_map params = device_factory.GetDefaultParams(SCHD); - EXPECT_TRUE(params.empty()); - - params = device_factory.GetDefaultParams(SCRM); - EXPECT_TRUE(params.empty()); - - params = device_factory.GetDefaultParams(SCMO); - EXPECT_TRUE(params.empty()); - - params = device_factory.GetDefaultParams(SCCD); - EXPECT_TRUE(params.empty()); - - params = device_factory.GetDefaultParams(SCHS); - EXPECT_TRUE(params.empty()); - - params = device_factory.GetDefaultParams(SCBR); - EXPECT_EQ(2, params.size()); - - params = device_factory.GetDefaultParams(SCDP); - EXPECT_EQ(2, params.size()); - - params = device_factory.GetDefaultParams(SCLP); - EXPECT_EQ(1, params.size()); -} - TEST(DeviceFactoryTest, UnknownDeviceType) { DeviceFactory device_factory; diff --git a/cpp/test/device_test.cpp b/cpp/test/device_test.cpp index 740adfbf..60ad3b36 100644 --- a/cpp/test/device_test.cpp +++ b/cpp/test/device_test.cpp @@ -3,14 +3,21 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "mocks.h" -#include "shared/piscsi_exceptions.h" #include "devices/device.h" +TEST(DeviceTest, GetDefaultParams) +{ + MockDevice device(0); + + const auto params = device.GetDefaultParams(); + EXPECT_TRUE(params.empty()); +} + TEST(DeviceTest, Properties) { const int LUN = 5; @@ -118,28 +125,36 @@ TEST(DeviceTest, Properties) TEST(DeviceTest, GetTypeString) { MockDevice schd(SCHD); - EXPECT_STREQ("SCHD", schd.GetTypeString()); + EXPECT_EQ("SCHD", schd.GetTypeString()); MockDevice scrm(SCRM); - EXPECT_STREQ("SCRM", scrm.GetTypeString()); + EXPECT_EQ("SCRM", scrm.GetTypeString()); MockDevice scmo(SCMO); - EXPECT_STREQ("SCMO", scmo.GetTypeString()); + EXPECT_EQ("SCMO", scmo.GetTypeString()); MockDevice sccd(SCCD); - EXPECT_STREQ("SCCD", sccd.GetTypeString()); + EXPECT_EQ("SCCD", sccd.GetTypeString()); MockDevice schs(SCHS); - EXPECT_STREQ("SCHS", schs.GetTypeString()); + EXPECT_EQ("SCHS", schs.GetTypeString()); MockDevice scbr(SCBR); - EXPECT_STREQ("SCBR", scbr.GetTypeString()); + EXPECT_EQ("SCBR", scbr.GetTypeString()); MockDevice scdp(SCDP); - EXPECT_STREQ("SCDP", scdp.GetTypeString()); + EXPECT_EQ("SCDP", scdp.GetTypeString()); MockDevice sclp(SCLP); - EXPECT_STREQ("SCLP", sclp.GetTypeString()); + EXPECT_EQ("SCLP", sclp.GetTypeString()); +} + +TEST(DeviceTest, GetIdentifier) +{ + MockDevice device(1); + + EXPECT_CALL(device, GetId()); + EXPECT_EQ("UNDEFINED 0:1", device.GetIdentifier()); } TEST(DeviceTest, Vendor) @@ -185,26 +200,6 @@ TEST(DeviceTest, GetPaddedName) EXPECT_EQ("V P R ", device.GetPaddedName()); } -TEST(DeviceTest, Params) -{ - MockDevice device(0); - unordered_map params; - params["key"] = "value"; - - EXPECT_EQ("", device.GetParam("key")); - - device.SetParams(params); - EXPECT_EQ("", device.GetParam("key")); - - unordered_map default_params; - default_params["key"] = "value"; - device.SetDefaultParams(default_params); - EXPECT_EQ("", device.GetParam("key")); - - device.SetParams(params); - EXPECT_EQ("value", device.GetParam("key")); -} - TEST(DeviceTest, StatusCode) { MockDevice device(0); diff --git a/cpp/test/disk_test.cpp b/cpp/test/disk_test.cpp index 82c5a3c5..b0206403 100644 --- a/cpp/test/disk_test.cpp +++ b/cpp/test/disk_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -16,23 +16,26 @@ using namespace scsi_defs; using namespace scsi_command_util; +pair, shared_ptr> CreateDisk() +{ + auto controller = make_shared>(0); + auto disk = make_shared(); + EXPECT_TRUE(disk->Init({})); + EXPECT_TRUE(controller->AddDevice(disk)); + + return { controller, disk }; +} + TEST(DiskTest, Dispatch) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); - - controller->AddDevice(disk); + auto [controller, disk] = CreateDisk(); disk->SetRemovable(true); disk->SetMediumChanged(false); disk->SetReady(true); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdTestUnitReady); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); disk->SetMediumChanged(true); EXPECT_CALL(*controller, Error); @@ -42,167 +45,132 @@ TEST(DiskTest, Dispatch) TEST(DiskTest, Rezero) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdRezero); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdRezero); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "REZERO must fail because drive is not ready"; disk->SetReady(true); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdRezero); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(DiskTest, FormatUnit) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdFormatUnit); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdFormatUnit); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "FORMAT UNIT must fail because drive is not ready"; disk->SetReady(true); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdFormatUnit); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); - cmd[1] = 0x10; - cmd[4] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdFormatUnit); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))); + controller->SetCmdByte(1, 0x10); + controller->SetCmdByte(4, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdFormatUnit); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))); } TEST(DiskTest, ReassignBlocks) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReassignBlocks); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReassignBlocks); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "REASSIGN must fail because drive is not ready"; disk->SetReady(true); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdReassignBlocks); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(DiskTest, Seek6) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdSeek6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSeek6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "SEEK(6) must fail for a medium with 0 blocks"; disk->SetBlockCount(1); // Block count - cmd[4] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdSeek6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + controller->SetCmdByte(4, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSeek6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "SEEK(6) must fail because drive is not ready"; disk->SetReady(true); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdSeek6); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(DiskTest, Seek10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdSeek10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSeek10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "SEEK(10) must fail for a medium with 0 blocks"; disk->SetBlockCount(1); // Block count - cmd[5] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdSeek10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + controller->SetCmdByte(5, 1); + + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSeek10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "SEEK(10) must fail because drive is not ready"; disk->SetReady(true); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdSeek10); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(DiskTest, ReadCapacity10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReadCapacity10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReadCapacity10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "READ CAPACITY(10) must fail because drive is not ready"; disk->SetReady(true); - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReadCapacity10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReadCapacity10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "READ CAPACITY(10) must fail because the medium has no capacity"; disk->SetBlockCount(0x12345678); @@ -222,34 +190,28 @@ TEST(DiskTest, ReadCapacity10) TEST(DiskTest, ReadCapacity16) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); + controller->SetCmdByte(1, 0x00); - auto& cmd = controller->GetCmd(); - - cmd[1] = 0x00; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Neither READ CAPACITY(16) nor READ LONG(16)"; // READ CAPACITY(16), not READ LONG(16) - cmd[1] = 0x10; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + controller->SetCmdByte(1, 0x10); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "READ CAPACITY(16) must fail because drive is not ready"; disk->SetReady(true); - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "READ CAPACITY(16) must fail because the medium has no capacity"; disk->SetBlockCount(0x1234567887654321); @@ -267,18 +229,13 @@ TEST(DiskTest, ReadCapacity16) TEST(DiskTest, Read6) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdRead6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdRead6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "READ(6) must fail for a medium with 0 blocks"; // Further testing requires filesystem access @@ -286,389 +243,328 @@ TEST(DiskTest, Read6) TEST(DiskTest, Read10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdRead10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdRead10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "READ(10) must fail for a medium with 0 blocks"; disk->SetBlockCount(1); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdRead10); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); // Further testing requires filesystem access } TEST(DiskTest, Read16) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdRead16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdRead16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "READ(16) must fail for a medium with 0 blocks"; disk->SetBlockCount(1); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdRead16); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); // Further testing requires filesystem access } TEST(DiskTest, Write6) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "WRITE(6) must fail for a medium with 0 blocks"; disk->SetBlockCount(1); disk->SetReady(true); disk->SetProtectable(true); disk->SetProtected(true); - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::DATA_PROTECT), - Property(&scsi_exception::get_asc, asc::WRITE_PROTECTED)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::data_protect), + Property(&scsi_exception::get_asc, asc::write_protected)))); // Further testing requires filesystem access } TEST(DiskTest, Write10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdWrite10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWrite10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "WRITE(10) must fail for a medium with 0 blocks"; disk->SetBlockCount(1); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdWrite10); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); // Further testing requires filesystem access } TEST(DiskTest, Write16) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdWrite16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWrite16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "WRITE(16) must fail for a medium with 0 blocks"; disk->SetBlockCount(1); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdWrite16); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); // Further testing requires filesystem access } TEST(DiskTest, Verify10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdVerify10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdVerify10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "VERIFY(10) must fail for a medium with 0 blocks"; + disk->SetReady(true); + // Verify 0 sectors disk->SetBlockCount(1); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdVerify10); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); + + // Verify 1 sector with BytChk=0 + controller->SetCmdByte(8, 1); + EXPECT_CALL(*controller, Status); + disk->Dispatch(scsi_command::eCmdVerify10); + EXPECT_EQ(status::good, controller->GetStatus()); // Further testing requires filesystem access } TEST(DiskTest, Verify16) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdVerify16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdVerify16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "VERIFY(16) must fail for a medium with 0 blocks"; + disk->SetReady(true); + // Verify 0 sectors disk->SetBlockCount(1); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdVerify16); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); + + // Verify 1 sector with BytChk=0 + controller->SetCmdByte(13, 1); + EXPECT_CALL(*controller, Status); + disk->Dispatch(scsi_command::eCmdVerify16); + EXPECT_EQ(status::good, controller->GetStatus()); // Further testing requires filesystem access } TEST(DiskTest, ReadLong10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); - - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdReadLong10); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); - cmd[2] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReadLong10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + controller->SetCmdByte(2, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReadLong10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "READ LONG(10) must fail because the capacity is exceeded"; - cmd[2] = 0; + controller->SetCmdByte(2, 0); - cmd[7] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReadLong10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(7, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReadLong10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "READ LONG(10) must fail because it currently only supports 0 bytes transfer length"; } TEST(DiskTest, ReadLong16) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); - - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; // READ LONG(16), not READ CAPACITY(16) - cmd[1] = 0x11; - cmd[2] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + controller->SetCmdByte(1, 0x11); + controller->SetCmdByte(2, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "READ LONG(16) must fail because the capacity is exceeded"; - cmd[2] = 0; + controller->SetCmdByte(2, 0); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); - cmd[13] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(13, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "READ LONG(16) must fail because it currently only supports 0 bytes transfer length"; } TEST(DiskTest, WriteLong10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); - - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdWriteLong10); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); - cmd[2] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdWriteLong10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + controller->SetCmdByte(2, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWriteLong10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "WRITE LONG(10) must fail because the capacity is exceeded"; - cmd[2] = 0; + controller->SetCmdByte(2, 0); - cmd[7] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdWriteLong10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(7, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWriteLong10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "WRITE LONG(10) must fail because it currently only supports 0 bytes transfer length"; } TEST(DiskTest, WriteLong16) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); - - cmd[2] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdWriteLong16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + controller->SetCmdByte(2, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWriteLong16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "WRITE LONG(16) must fail because the capacity is exceeded"; - cmd[2] = 0; + controller->SetCmdByte(2, 0); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdWriteLong16); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); - cmd[13] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdWriteLong16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(13, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWriteLong16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "WRITE LONG(16) must fail because it currently only supports 0 bytes transfer length"; } TEST(DiskTest, StartStopUnit) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; disk->SetRemovable(true); - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); - // Stop/Unload disk->SetReady(true); EXPECT_CALL(*controller, Status); EXPECT_CALL(*disk, FlushCache); disk->Dispatch(scsi_command::eCmdStartStop); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); EXPECT_TRUE(disk->IsStopped()); // Stop/Load - cmd[4] = 0x02; + controller->SetCmdByte(4, 0x02); disk->SetReady(true); disk->SetLocked(false); EXPECT_CALL(*controller, Status); EXPECT_CALL(*disk, FlushCache); disk->Dispatch(scsi_command::eCmdStartStop); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); disk->SetReady(false); EXPECT_CALL(*disk, FlushCache).Times(0); - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdStartStop); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LOAD_OR_EJECT_FAILED)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdStartStop); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::load_or_eject_failed)))); disk->SetReady(true); disk->SetLocked(true); EXPECT_CALL(*disk, FlushCache).Times(0); - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdStartStop); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LOAD_OR_EJECT_FAILED)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdStartStop); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::load_or_eject_failed)))); // Start/Unload - cmd[4] = 0x01; + controller->SetCmdByte(4, 0x01); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdStartStop); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); EXPECT_FALSE(disk->IsStopped()); // Start/Load - cmd[4] = 0x03; + controller->SetCmdByte(4, 0x03); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdStartStop); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(DiskTest, PreventAllowMediumRemoval) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdPreventAllowMediumRemoval); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdPreventAllowMediumRemoval); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "REMOVAL must fail because drive is not ready"; disk->SetReady(true); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdPreventAllowMediumRemoval); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); EXPECT_FALSE(disk->IsLocked()); - cmd[4] = 1; + controller->SetCmdByte(4, 1); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdPreventAllowMediumRemoval); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); EXPECT_TRUE(disk->IsLocked()); } @@ -701,9 +597,9 @@ TEST(DiskTest, Eject) EXPECT_TRUE(disk.Eject(true)); } -void DiskTest_ValidateFormatPage(shared_ptr controller, int offset) +void DiskTest_ValidateFormatPage(AbstractController& controller, int offset) { - const auto& buf = controller->GetBuffer(); + const auto& buf = controller.GetBuffer(); EXPECT_EQ(0x08, buf[offset + 3]) << "Wrong number of trackes in one zone"; EXPECT_EQ(25, GetInt16(buf, offset + 10)) << "Wrong number of sectors per track"; EXPECT_EQ(1024, GetInt16(buf, offset + 12)) << "Wrong number of bytes per sector"; @@ -714,18 +610,18 @@ void DiskTest_ValidateFormatPage(shared_ptr controller, int EXPECT_TRUE(buf[offset + 20] & 0x40) << "Wrong hard-sectored flag"; } -void DiskTest_ValidateDrivePage(shared_ptr controller, int offset) +void DiskTest_ValidateDrivePage(AbstractController& controller, int offset) { - const auto& buf = controller->GetBuffer(); + const auto& buf = controller.GetBuffer(); EXPECT_EQ(0x17, buf[offset + 2]); EXPECT_EQ(0x4d3b, GetInt16(buf, offset + 3)); EXPECT_EQ(8, buf[offset + 5]) << "Wrong number of heads"; EXPECT_EQ(7200, GetInt16(buf, offset + 20)) << "Wrong medium rotation rate"; } -void DiskTest_ValidateCachePage(shared_ptr controller, int offset) +void DiskTest_ValidateCachePage(AbstractController& controller, int offset) { - const auto& buf = controller->GetBuffer(); + const auto& buf = controller.GetBuffer(); EXPECT_EQ(0xffff, GetInt16(buf, offset + 4)) << "Wrong pre-fetch transfer length"; EXPECT_EQ(0xffff, GetInt16(buf, offset + 8)) << "Wrong maximum pre-fetch"; EXPECT_EQ(0xffff, GetInt16(buf, offset + 10)) << "Wrong maximum pre-fetch ceiling"; @@ -733,28 +629,19 @@ void DiskTest_ValidateCachePage(shared_ptr controller, int o TEST(DiskTest, ModeSense6) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); - - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); + auto [controller, disk] = CreateDisk(); // Drive must be ready on order to return all data disk->SetReady(true); - cmd[2] = 0x3f; + controller->SetCmdByte(2, 0x3f); // ALLOCATION LENGTH - cmd[4] = 255; + controller->SetCmdByte(4, 255); disk->Dispatch(scsi_command::eCmdModeSense6); EXPECT_EQ(0x08, controller->GetBuffer()[3]) << "Wrong block descriptor length"; // No block descriptor - cmd[1] = 0x08; + controller->SetCmdByte(1, 0x08); disk->Dispatch(scsi_command::eCmdModeSense6); EXPECT_EQ(0x00, controller->GetBuffer()[2]) << "Wrong device-specific parameter"; @@ -766,50 +653,41 @@ TEST(DiskTest, ModeSense6) EXPECT_EQ(0x80, buf[2]) << "Wrong device-specific parameter"; // Return block descriptor - cmd[1] = 0x00; + controller->SetCmdByte(1, 0x00); // Format page - cmd[2] = 3; + controller->SetCmdByte(2, 3); disk->SetSectorSizeInBytes(1024); disk->Dispatch(scsi_command::eCmdModeSense6); - DiskTest_ValidateFormatPage(controller, 12); + DiskTest_ValidateFormatPage(*controller, 12); // Rigid disk drive page - cmd[2] = 4; + controller->SetCmdByte(2, 4); disk->SetBlockCount(0x12345678); disk->Dispatch(scsi_command::eCmdModeSense6); - DiskTest_ValidateDrivePage(controller, 12); + DiskTest_ValidateDrivePage(*controller, 12); // Cache page - cmd[2] = 8; + controller->SetCmdByte(2, 8); disk->Dispatch(scsi_command::eCmdModeSense6); - DiskTest_ValidateCachePage(controller, 12); + DiskTest_ValidateCachePage(*controller, 12); } TEST(DiskTest, ModeSense10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); - - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); + auto [controller, disk] = CreateDisk(); // Drive must be ready on order to return all data disk->SetReady(true); - cmd[2] = 0x3f; + controller->SetCmdByte(2, 0x3f); // ALLOCATION LENGTH - cmd[8] = 255; + controller->SetCmdByte(8, 255); disk->Dispatch(scsi_command::eCmdModeSense10); EXPECT_EQ(0x08, controller->GetBuffer()[7]) << "Wrong block descriptor length"; // No block descriptor - cmd[1] = 0x08; + controller->SetCmdByte(1, 0x08); disk->Dispatch(scsi_command::eCmdModeSense10); auto& buf = controller->GetBuffer(); EXPECT_EQ(0x00, controller->GetBuffer()[3]) << "Wrong device-specific parameter"; @@ -822,7 +700,7 @@ TEST(DiskTest, ModeSense10) EXPECT_EQ(0x80, buf[3]) << "Wrong device-specific parameter"; // Return short block descriptor - cmd[1] = 0x00; + controller->SetCmdByte(1, 0x00); disk->SetBlockCount(0x1234); disk->SetSectorSizeInBytes(1024); disk->Dispatch(scsi_command::eCmdModeSense10); @@ -835,7 +713,7 @@ TEST(DiskTest, ModeSense10) EXPECT_EQ(1024, GetInt16(buf, 14)); // Return long block descriptor - cmd[1] = 0x10; + controller->SetCmdByte(1, 0x10); disk->SetBlockCount((uint64_t)0xffffffff + 1); disk->Dispatch(scsi_command::eCmdModeSense10); buf = controller->GetBuffer(); @@ -847,62 +725,49 @@ TEST(DiskTest, ModeSense10) EXPECT_EQ(0x00, GetInt16(buf, 14)); EXPECT_EQ(0x00, GetInt16(buf, 20)); EXPECT_EQ(1024, GetInt16(buf, 22)); - cmd[1] = 0x00; + + controller->SetCmdByte(1, 0x00); // Format page - cmd[2] = 3; + controller->SetCmdByte(2, 3); disk->SetSectorSizeInBytes(1024); disk->Dispatch(scsi_command::eCmdModeSense10); - DiskTest_ValidateFormatPage(controller, 16); + DiskTest_ValidateFormatPage(*controller, 16); // Rigid disk drive page - cmd[2] = 4; + controller->SetCmdByte(2, 4); disk->SetBlockCount(0x12345678); disk->Dispatch(scsi_command::eCmdModeSense10); - DiskTest_ValidateDrivePage(controller, 16); + DiskTest_ValidateDrivePage(*controller, 16); // Cache page - cmd[2] = 8; + controller->SetCmdByte(2, 8); disk->Dispatch(scsi_command::eCmdModeSense10); - DiskTest_ValidateCachePage(controller, 16); + DiskTest_ValidateCachePage(*controller, 16); } TEST(DiskTest, SynchronizeCache) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); - - controller->AddDevice(disk); + auto [controller, disk] = CreateDisk(); EXPECT_CALL(*disk, FlushCache); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdSynchronizeCache10); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); EXPECT_CALL(*disk, FlushCache); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdSynchronizeCache16); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(DiskTest, ReadDefectData) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); - - controller->AddDevice(disk); + auto [controller, disk] = CreateDisk(); EXPECT_CALL(*controller, DataIn); disk->Dispatch(scsi_command::eCmdReadDefectData10); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(DiskTest, SectorSize) @@ -951,8 +816,7 @@ TEST(DiskTest, SectorSize) TEST(DiskTest, ConfiguredSectorSize) { DeviceFactory device_factory; - const unordered_set sector_sizes; - MockSCSIHD disk(0, sector_sizes, false); + MockSCSIHD disk(0, {}, false); EXPECT_TRUE(disk.SetConfiguredSectorSize(device_factory, 512)); EXPECT_EQ(512, disk.GetConfiguredSectorSize()); diff --git a/cpp/test/gpiobus_raspberry_test.cpp b/cpp/test/gpiobus_raspberry_test.cpp index a96e4b7c..5a94d670 100644 --- a/cpp/test/gpiobus_raspberry_test.cpp +++ b/cpp/test/gpiobus_raspberry_test.cpp @@ -9,7 +9,7 @@ #include "hal/gpiobus_raspberry.h" #include "mocks.h" -#include "stdlib.h" +#include #include "test/test_shared.h" class SetableGpiobusRaspberry : public GPIOBUS_Raspberry @@ -23,9 +23,9 @@ class SetableGpiobusRaspberry : public GPIOBUS_Raspberry { // Level is inverted logic if (!value) { - *level |= (1 << pin); + *level = *level | (1 << pin); } else { - *level &= ~(1 << pin); + *level = *level & ~(1 << pin); } } SetableGpiobusRaspberry() @@ -65,7 +65,7 @@ TEST(GpiobusRaspberry, GetDtRanges) EXPECT_EQ(0x20000000, GPIOBUS_Raspberry::bcm_host_get_peripheral_address()); DeleteTempFile("/proc/device-tree/soc/ranges"); - CleanupAllTempFiles(); + remove_all(test_data_temp_path); } TEST(GpiobusRaspberry, GetDat) diff --git a/cpp/test/host_services_test.cpp b/cpp/test/host_services_test.cpp index bb069a9c..5bcf7ab4 100644 --- a/cpp/test/host_services_test.cpp +++ b/cpp/test/host_services_test.cpp @@ -9,7 +9,6 @@ #include "mocks.h" #include "shared/piscsi_exceptions.h" -#include "controllers/controller_manager.h" #include "devices/host_services.h" using namespace std; @@ -22,82 +21,70 @@ void HostServices_SetUpModePages(map>& pages) TEST(HostServicesTest, TestUnitReady) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto services = CreateDevice(SCHS, *controller); + auto [controller, services] = CreateDevice(SCHS); EXPECT_CALL(*controller, Status()); services->Dispatch(scsi_command::eCmdTestUnitReady); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(HostServicesTest, Inquiry) { - TestInquiry(SCHS, device_type::PROCESSOR, scsi_level::SPC_3, "PiSCSI Host Services ", 0x1f, false); + TestInquiry::Inquiry(SCHS, device_type::processor, scsi_level::spc_3, "PiSCSI Host Services ", 0x1f, false); } TEST(HostServicesTest, StartStopUnit) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto services = CreateDevice(SCHS, *controller); - - auto& cmd = controller->GetCmd(); + auto [controller, services] = CreateDevice(SCHS); + // Required by the bullseye clang++ compiler + auto s = services; // STOP - EXPECT_CALL(*controller, ScheduleShutdown(AbstractController::piscsi_shutdown_mode::STOP_PISCSI)); EXPECT_CALL(*controller, Status()); services->Dispatch(scsi_command::eCmdStartStop); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); // LOAD - cmd[4] = 0x02; - EXPECT_CALL(*controller, ScheduleShutdown(AbstractController::piscsi_shutdown_mode::STOP_PI)); + controller->SetCmdByte(4, 0x02); EXPECT_CALL(*controller, Status()); services->Dispatch(scsi_command::eCmdStartStop); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); // UNLOAD - cmd[4] = 0x03; - EXPECT_CALL(*controller, ScheduleShutdown(AbstractController::piscsi_shutdown_mode::RESTART_PI)); + controller->SetCmdByte(4, 0x03); EXPECT_CALL(*controller, Status()); services->Dispatch(scsi_command::eCmdStartStop); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); // START - cmd[4] = 0x01; - EXPECT_THAT([&] { services->Dispatch(scsi_command::eCmdStartStop); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))); + controller->SetCmdByte(4, 0x01); + EXPECT_THAT([&] { s->Dispatch(scsi_command::eCmdStartStop); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))); } TEST(HostServicesTest, ModeSense6) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto services = CreateDevice(SCHS, *controller); - const unordered_map params; - services->Init(params); + auto [controller, services] = CreateDevice(SCHS); + // Required by the bullseye clang++ compiler + auto s = services; - auto& cmd = controller->GetCmd(); + EXPECT_TRUE(services->Init({})); - EXPECT_THAT([&] { services->Dispatch(scsi_command::eCmdModeSense6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + EXPECT_THAT([&] { s->Dispatch(scsi_command::eCmdModeSense6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Unsupported mode page was returned"; - cmd[2] = 0x20; - EXPECT_THAT([&] { services->Dispatch(scsi_command::eCmdModeSense6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(2, 0x20); + EXPECT_THAT([&] { s->Dispatch(scsi_command::eCmdModeSense6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Block descriptors are not supported"; - cmd[1] = 0x08; + controller->SetCmdByte(1, 0x08); // ALLOCATION LENGTH - cmd[4] = 255; + controller->SetCmdByte(4, 255); EXPECT_CALL(*controller, DataIn()); services->Dispatch(scsi_command::eCmdModeSense6); vector& buffer = controller->GetBuffer(); @@ -111,7 +98,7 @@ TEST(HostServicesTest, ModeSense6) EXPECT_NE(0x00, buffer[10]); // ALLOCATION LENGTH - cmd[4] = 2; + controller->SetCmdByte(4, 2); EXPECT_CALL(*controller, DataIn()); services->Dispatch(scsi_command::eCmdModeSense6); buffer = controller->GetBuffer(); @@ -120,29 +107,26 @@ TEST(HostServicesTest, ModeSense6) TEST(HostServicesTest, ModeSense10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto services = CreateDevice(SCHS, *controller); - const unordered_map params; - services->Init(params); + auto [controller, services] = CreateDevice(SCHS); + // Required by the bullseye clang++ compiler + auto s = services; + + EXPECT_TRUE(services->Init({})); - auto& cmd = controller->GetCmd(); - - EXPECT_THAT([&] { services->Dispatch(scsi_command::eCmdModeSense10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + EXPECT_THAT([&] { s->Dispatch(scsi_command::eCmdModeSense10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Unsupported mode page was returned"; - cmd[2] = 0x20; - EXPECT_THAT([&] { services->Dispatch(scsi_command::eCmdModeSense10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(2, 0x20); + EXPECT_THAT([&] { s->Dispatch(scsi_command::eCmdModeSense10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Block descriptors are not supported"; - cmd[1] = 0x08; + controller->SetCmdByte(1, 0x08); // ALLOCATION LENGTH - cmd[8] = 255; + controller->SetCmdByte(8, 255); EXPECT_CALL(*controller, DataIn()); services->Dispatch(scsi_command::eCmdModeSense10); vector& buffer = controller->GetBuffer(); @@ -156,7 +140,7 @@ TEST(HostServicesTest, ModeSense10) EXPECT_NE(0x00, buffer[14]); // ALLOCATION LENGTH - cmd[8] = 2; + controller->SetCmdByte(8, 2); EXPECT_CALL(*controller, DataIn()); services->Dispatch(scsi_command::eCmdModeSense10); buffer = controller->GetBuffer(); diff --git a/cpp/test/linux_os_stubs.cpp b/cpp/test/linux_os_stubs.cpp index f5885e27..98cdbdb7 100644 --- a/cpp/test/linux_os_stubs.cpp +++ b/cpp/test/linux_os_stubs.cpp @@ -13,11 +13,13 @@ #include #include -#include -#include -#include +#include +#include +#include #include +#ifdef __linux__ #include +#endif #include #include #include @@ -61,4 +63,4 @@ FILE *__wrap_fopen(const char *__restrict __filename, const char *__restrict __m #endif } -} // end extern "C" \ No newline at end of file +} // end extern "C" diff --git a/cpp/test/mocks.h b/cpp/test/mocks.h index f654054d..fcf45cd5 100644 --- a/cpp/test/mocks.h +++ b/cpp/test/mocks.h @@ -14,15 +14,10 @@ #include "test_shared.h" #include "hal/bus.h" #include "controllers/scsi_controller.h" -#include "devices/primary_device.h" -#include "devices/storage_device.h" -#include "devices/disk.h" -#include "devices/scsihd.h" #include "devices/scsihd_nec.h" #include "devices/scsicd.h" #include "devices/scsimo.h" #include "devices/host_services.h" -#include "piscsi/command_context.h" #include "piscsi/piscsi_executor.h" #include @@ -81,10 +76,11 @@ public: class MockPhaseHandler : public PhaseHandler { FRIEND_TEST(PhaseHandlerTest, Phases); + FRIEND_TEST(PhaseHandlerTest, ProcessPhase); public: - MOCK_METHOD(phase_t, Process, (int), (override)); + MOCK_METHOD(bool, Process, (int), (override)); MOCK_METHOD(void, Status, (), ()); MOCK_METHOD(void, DataIn, (), ()); MOCK_METHOD(void, DataOut, (), ()); @@ -97,15 +93,16 @@ public: using PhaseHandler::PhaseHandler; }; +inline static const auto mock_bus = make_shared(); + class MockAbstractController : public AbstractController //NOSONAR Having many fields/methods cannot be avoided { + friend class TestInquiry; + friend shared_ptr CreateDevice(piscsi_interface::PbDeviceType, AbstractController&, int); - friend void TestInquiry(piscsi_interface::PbDeviceType, scsi_defs::device_type, scsi_defs::scsi_level, - scsi_defs::scsi_level, const std::string&, int, bool); FRIEND_TEST(AbstractControllerTest, AllocateCmd); FRIEND_TEST(AbstractControllerTest, Reset); - FRIEND_TEST(AbstractControllerTest, ProcessPhase); FRIEND_TEST(AbstractControllerTest, DeviceLunLifeCycle); FRIEND_TEST(AbstractControllerTest, ExtractInitiatorId); FRIEND_TEST(AbstractControllerTest, GetOpcode); @@ -148,10 +145,26 @@ class MockAbstractController : public AbstractController //NOSONAR Having many f FRIEND_TEST(DiskTest, PreventAllowMediumRemoval); FRIEND_TEST(DiskTest, SynchronizeCache); FRIEND_TEST(DiskTest, ReadDefectData); + FRIEND_TEST(DiskTest, StartStopUnit); + FRIEND_TEST(DiskTest, ModeSense6); + FRIEND_TEST(DiskTest, ModeSense10); + FRIEND_TEST(ScsiDaynaportTest, Read); + FRIEND_TEST(ScsiDaynaportTest, Write); + FRIEND_TEST(ScsiDaynaportTest, Read6); + FRIEND_TEST(ScsiDaynaportTest, Write6); + FRIEND_TEST(ScsiDaynaportTest, TestRetrieveStats); + FRIEND_TEST(ScsiDaynaportTest, SetInterfaceMode); + FRIEND_TEST(ScsiDaynaportTest, SetMcastAddr); + FRIEND_TEST(ScsiDaynaportTest, EnableInterface); + FRIEND_TEST(HostServicesTest, StartStopUnit); + FRIEND_TEST(HostServicesTest, ModeSense6); + FRIEND_TEST(HostServicesTest, ModeSense10); + FRIEND_TEST(HostServicesTest, SetUpModePages); + FRIEND_TEST(ScsiPrinterTest, Print); public: - MOCK_METHOD(phase_t, Process, (int), (override)); + MOCK_METHOD(bool, Process, (int), (override)); MOCK_METHOD(int, GetEffectiveLun, (), (const override)); MOCK_METHOD(void, Error, (scsi_defs::sense_key, scsi_defs::asc, scsi_defs::status), (override)); MOCK_METHOD(int, GetInitiatorId, (), (const override)); @@ -163,10 +176,12 @@ public: MOCK_METHOD(void, Command, (), ()); MOCK_METHOD(void, MsgIn, (), ()); MOCK_METHOD(void, MsgOut, (), ()); - MOCK_METHOD(void, ScheduleShutdown, (piscsi_shutdown_mode), (override)); - explicit MockAbstractController(shared_ptr controller_manager, int target_id) - : AbstractController(controller_manager, target_id, 32) { + MockAbstractController() : AbstractController(*mock_bus, 0, 32) {} + explicit MockAbstractController(int target_id) : AbstractController(*mock_bus, target_id, 32) { + AllocateBuffer(512); + } + MockAbstractController(shared_ptr bus, int target_id) : AbstractController(*bus, target_id, 32) { AllocateBuffer(512); } ~MockAbstractController() override = default; @@ -193,10 +208,8 @@ public: MOCK_METHOD(void, Execute, (), ()); using ScsiController::ScsiController; - MockScsiController(shared_ptr controller_manager, int target_id) - : ScsiController(controller_manager, target_id) {} - explicit MockScsiController(shared_ptr controller_manager) - : ScsiController(controller_manager, 0) {} + MockScsiController(shared_ptr bus, int target_id) : ScsiController(*bus, target_id) {} + explicit MockScsiController(shared_ptr bus) : ScsiController(*bus, 0) {} ~MockScsiController() override = default; }; @@ -233,6 +246,7 @@ class MockPrimaryDevice : public PrimaryDevice public: MOCK_METHOD(vector, InquiryInternal, (), (const)); + MOCK_METHOD(void, FlushCache, (), ()); explicit MockPrimaryDevice(int lun) : PrimaryDevice(UNDEFINED, lun) {} ~MockPrimaryDevice() override = default; @@ -247,8 +261,8 @@ class MockModePageDevice : public ModePageDevice public: MOCK_METHOD(vector, InquiryInternal, (), (const)); - MOCK_METHOD(int, ModeSense6, (const vector&, vector&), (const override)); - MOCK_METHOD(int, ModeSense10, (const vector&, vector&), (const override)); + MOCK_METHOD(int, ModeSense6, (span, vector&), (const override)); + MOCK_METHOD(int, ModeSense10, (span, vector&), (const override)); MockModePageDevice() : ModePageDevice(UNDEFINED, 0) {} ~MockModePageDevice() override = default; @@ -290,8 +304,8 @@ public: MOCK_METHOD(vector, InquiryInternal, (), (const)); MOCK_METHOD(void, Open, (), (override)); - MOCK_METHOD(int, ModeSense6, (const vector&, vector&), (const override)); - MOCK_METHOD(int, ModeSense10, (const vector&, vector&), (const override)); + MOCK_METHOD(int, ModeSense6, (span, vector&), (const override)); + MOCK_METHOD(int, ModeSense10, (span, vector&), (const override)); MOCK_METHOD(void, SetUpModePages, ((map>&), int, bool), (const override)); MockStorageDevice() : StorageDevice(UNDEFINED, 0) {} @@ -391,16 +405,6 @@ class MockHostServices : public HostServices using HostServices::HostServices; }; -class MockCommandContext : public CommandContext -{ -public: - - MockCommandContext() { - SetFd(open("/dev/null", O_WRONLY)); - } - ~MockCommandContext() = default; -}; - class MockPiscsiExecutor : public PiscsiExecutor { public: diff --git a/cpp/test/mode_page_device_test.cpp b/cpp/test/mode_page_device_test.cpp index 31d0a2a9..8515ec55 100644 --- a/cpp/test/mode_page_device_test.cpp +++ b/cpp/test/mode_page_device_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -33,8 +33,8 @@ TEST(ModePageDeviceTest, AddModePages) // Page 0 cdb[2] = 0x00; EXPECT_THAT([&] { device.AddModePages(cdb, buf, 0, 12, 255); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Data were returned for non-existing mode page 0"; // All pages, non changeable @@ -42,8 +42,8 @@ TEST(ModePageDeviceTest, AddModePages) EXPECT_EQ(0, device.AddModePages(cdb, buf, 0, 0, 255)); EXPECT_EQ(3, device.AddModePages(cdb, buf, 0, 3, 255)); EXPECT_THAT([&] { device.AddModePages(cdb, buf, 0, 12, -1); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Maximum size was ignored"; // All pages, changeable @@ -51,8 +51,8 @@ TEST(ModePageDeviceTest, AddModePages) EXPECT_EQ(0, device.AddModePages(cdb, buf, 0, 0, 255)); EXPECT_EQ(3, device.AddModePages(cdb, buf, 0, 3, 255)); EXPECT_THAT([&] { device.AddModePages(cdb, buf, 0, 12, -1); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Maximum size was ignored"; } @@ -80,12 +80,9 @@ TEST(ModePageDeviceTest, AddVendorPage) TEST(ModePageDeviceTest, ModeSense6) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); + auto controller = make_shared(0); auto device = make_shared>(); - const unordered_map params; - device->Init(params); + EXPECT_TRUE(device->Init({})); controller->AddDevice(device); @@ -95,12 +92,9 @@ TEST(ModePageDeviceTest, ModeSense6) TEST(ModePageDeviceTest, ModeSense10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); + auto controller = make_shared(0); auto device = make_shared>(); - const unordered_map params; - device->Init(params); + EXPECT_TRUE(device->Init({})); controller->AddDevice(device); @@ -115,57 +109,47 @@ TEST(ModePageDeviceTest, ModeSelect) vector buf; EXPECT_THAT([&] { device.ModeSelect(scsi_command::eCmdModeSelect6, cmd, buf, 0); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_COMMAND_OPERATION_CODE)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_command_operation_code)))) << "Unexpected MODE SELECT(6) default implementation"; EXPECT_THAT([&] { device.ModeSelect(scsi_command::eCmdModeSelect10, cmd, buf, 0); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_COMMAND_OPERATION_CODE)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_command_operation_code)))) << "Unexpected MODE SELECT(10) default implementation"; } TEST(ModePageDeviceTest, ModeSelect6) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); + auto controller = make_shared(0); auto device = make_shared(); - const unordered_map params; - device->Init(params); + EXPECT_TRUE(device->Init({})); controller->AddDevice(device); - auto& cmd = controller->GetCmd(); - EXPECT_CALL(*controller, DataOut()); device->Dispatch(scsi_command::eCmdModeSelect6); - cmd[1] = 0x01; + controller->SetCmdByte(1, 0x01); EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdModeSelect6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Saving parameters is not supported by base class"; } TEST(ModePageDeviceTest, ModeSelect10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); + auto controller = make_shared(0); auto device = make_shared(); - const unordered_map params; - device->Init(params); + EXPECT_TRUE(device->Init({})); controller->AddDevice(device); - auto& cmd = controller->GetCmd(); - EXPECT_CALL(*controller, DataOut()); device->Dispatch(scsi_command::eCmdModeSelect10); - cmd[1] = 0x01; + controller->SetCmdByte(1, 0x01); EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdModeSelect10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Saving parameters is not supported for by base class"; } diff --git a/cpp/test/network_util_test.cpp b/cpp/test/network_util_test.cpp new file mode 100644 index 00000000..d6f49d62 --- /dev/null +++ b/cpp/test/network_util_test.cpp @@ -0,0 +1,33 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator PiSCSI +// for Raspberry Pi +// +// Copyright (C) 2023 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include + +#include +#include +#include "shared/network_util.h" + +using namespace network_util; + +TEST(NetworkUtilTest, IsInterfaceUp) +{ + EXPECT_FALSE(IsInterfaceUp("foo_bar")); +} + +TEST(NetworkUtilTest, GetNetworkInterfaces) +{ + EXPECT_FALSE(GetNetworkInterfaces().empty()); +} + +TEST(NetworkUtilTest, ResolveHostName) +{ + sockaddr_in server_addr = {}; + EXPECT_FALSE(ResolveHostName("foo.foobar", &server_addr)); + EXPECT_TRUE(ResolveHostName("127.0.0.1", &server_addr)); +} diff --git a/cpp/test/phase_handler_test.cpp b/cpp/test/phase_handler_test.cpp index 7af4f6b1..fc063d99 100644 --- a/cpp/test/phase_handler_test.cpp +++ b/cpp/test/phase_handler_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -94,3 +94,51 @@ TEST(PhaseHandlerTest, Phases) EXPECT_FALSE(handler.IsDataOut()); EXPECT_FALSE(handler.IsMsgIn()); } + +TEST(PhaseHandlerTest, ProcessPhase) +{ + MockPhaseHandler handler; + handler.Init(); + + handler.SetPhase(phase_t::selection); + EXPECT_CALL(handler, Selection); + handler.ProcessPhase(); + + handler.SetPhase(phase_t::busfree); + EXPECT_CALL(handler, BusFree); + handler.ProcessPhase(); + + handler.SetPhase(phase_t::datain); + EXPECT_CALL(handler, DataIn); + handler.ProcessPhase(); + + handler.SetPhase(phase_t::dataout); + EXPECT_CALL(handler, DataOut); + handler.ProcessPhase(); + + handler.SetPhase(phase_t::command); + EXPECT_CALL(handler, Command); + handler.ProcessPhase(); + + handler.SetPhase(phase_t::status); + EXPECT_CALL(handler, Status); + handler.ProcessPhase(); + + handler.SetPhase(phase_t::msgin); + EXPECT_CALL(handler, MsgIn); + handler.ProcessPhase(); + + handler.SetPhase(phase_t::msgout); + EXPECT_CALL(handler, MsgOut); + handler.ProcessPhase(); + + handler.SetPhase(phase_t::reselection); + EXPECT_THAT([&] { handler.ProcessPhase(); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::aborted_command), + Property(&scsi_exception::get_asc, asc::no_additional_sense_information)))); + + handler.SetPhase(phase_t::reserved); + EXPECT_THAT([&] { handler.ProcessPhase(); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::aborted_command), + Property(&scsi_exception::get_asc, asc::no_additional_sense_information)))); +} diff --git a/cpp/test/piscsi_exceptions_test.cpp b/cpp/test/piscsi_exceptions_test.cpp index 6def9716..a0b338e5 100644 --- a/cpp/test/piscsi_exceptions_test.cpp +++ b/cpp/test/piscsi_exceptions_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -36,18 +36,18 @@ TEST(PiscsiExceptionsTest, FileNotFoundException) TEST(PiscsiExceptionsTest, ScsiErrorException) { try { - throw scsi_exception(sense_key::UNIT_ATTENTION); + throw scsi_exception(sense_key::unit_attention); } catch(const scsi_exception& e) { - EXPECT_EQ(sense_key::UNIT_ATTENTION, e.get_sense_key()); - EXPECT_EQ(asc::NO_ADDITIONAL_SENSE_INFORMATION, e.get_asc()); + EXPECT_EQ(sense_key::unit_attention, e.get_sense_key()); + EXPECT_EQ(asc::no_additional_sense_information, e.get_asc()); } try { - throw scsi_exception(sense_key::UNIT_ATTENTION, asc::LBA_OUT_OF_RANGE); + throw scsi_exception(sense_key::illegal_request, asc::lba_out_of_range); } catch(const scsi_exception& e) { - EXPECT_EQ(sense_key::UNIT_ATTENTION, e.get_sense_key()); - EXPECT_EQ(asc::LBA_OUT_OF_RANGE, e.get_asc()); + EXPECT_EQ(sense_key::illegal_request, e.get_sense_key()); + EXPECT_EQ(asc::lba_out_of_range, e.get_asc()); } } diff --git a/cpp/test/piscsi_executor_test.cpp b/cpp/test/piscsi_executor_test.cpp index 9b038c49..d6557be4 100644 --- a/cpp/test/piscsi_executor_test.cpp +++ b/cpp/test/piscsi_executor_test.cpp @@ -3,11 +3,10 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "spdlog/spdlog.h" #include "mocks.h" #include "shared/protobuf_util.h" #include "shared/piscsi_exceptions.h" @@ -16,7 +15,6 @@ #include "generated/piscsi_interface.pb.h" #include "piscsi/command_context.h" #include "piscsi/piscsi_response.h" -#include "piscsi/piscsi_image.h" #include "piscsi/piscsi_executor.h" #include @@ -24,224 +22,162 @@ using namespace filesystem; using namespace piscsi_interface; using namespace protobuf_util; -const extern bool enable_logging; - -// This test fixture is required in order to reset the log level changed by the log level tests -class PiscsiExecutorTest : public Test -{ - void TearDown() override { - spdlog::set_level(enable_logging ? spdlog::level::trace : spdlog::level::off); - } -}; - -TEST_F(PiscsiExecutorTest, ProcessDeviceCmd) +TEST(PiscsiExecutorTest, ProcessDeviceCmd) { const int ID = 3; const int LUN = 0; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, ID); - PiscsiImage piscsi_image; - auto executor = make_shared(piscsi_image, *controller_manager); + ControllerManager controller_manager; + MockAbstractController controller(bus, ID); + auto executor = make_shared(*bus, controller_manager); PbDeviceDefinition definition; PbCommand command; - MockCommandContext context; + CommandContext context(command, "", ""); definition.set_id(8); definition.set_unit(32); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Invalid ID and LUN must fail"; + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, true)) << "Invalid ID and LUN must fail"; definition.set_unit(LUN); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Invalid ID must fail"; + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, true)) << "Invalid ID must fail"; definition.set_id(ID); definition.set_unit(32); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Invalid LUN must fail"; + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, true)) << "Invalid LUN must fail"; definition.set_unit(LUN); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Unknown operation must fail"; + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, true)) << "Unknown operation must fail"; command.set_operation(ATTACH); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Operation for unknown device type must fail"; + CommandContext context_attach(command, "", ""); + EXPECT_FALSE(executor->ProcessDeviceCmd(context_attach, definition, true)) << "Operation for unknown device type must fail"; auto device1 = make_shared(LUN); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device1)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device1)); definition.set_type(SCHS); command.set_operation(INSERT); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Operation unsupported by device must fail"; - controller_manager->DeleteAllControllers(); + CommandContext context_insert1(command, "", ""); + EXPECT_FALSE(executor->ProcessDeviceCmd(context_insert1, definition, true)) << "Operation unsupported by device must fail"; + controller_manager.DeleteAllControllers(); definition.set_type(SCRM); auto device2 = make_shared(LUN); device2->SetRemovable(true); device2->SetProtectable(true); device2->SetReady(true); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device2)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device2)); - command.set_operation(ATTACH); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "ID and LUN already exist"; + EXPECT_FALSE(executor->ProcessDeviceCmd(context_attach, definition, true)) << "ID and LUN already exist"; command.set_operation(START); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); + CommandContext context_start(command, "", ""); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_start, definition, true)); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_start, definition, false)); command.set_operation(PROTECT); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); + CommandContext context_protect(command, "", ""); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_protect, definition, true)); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_protect, definition, false)); command.set_operation(UNPROTECT); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); + CommandContext context_unprotect(command, "", ""); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_unprotect, definition, true)); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_unprotect, definition, false)); command.set_operation(STOP); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); + CommandContext context_stop(command, "", ""); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_stop, definition, true)); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_stop, definition, false)); command.set_operation(EJECT); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); + CommandContext context_eject(command, "", ""); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_eject, definition, true)); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_eject, definition, false)); command.set_operation(INSERT); SetParam(definition, "file", "filename"); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Non-existing file"; - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, false)) << "Non-existing file"; + CommandContext context_insert2(command, "", ""); + EXPECT_FALSE(executor->ProcessDeviceCmd(context_insert2, definition, true)) << "Non-existing file"; + EXPECT_FALSE(executor->ProcessDeviceCmd(context_insert2, definition, false)) << "Non-existing file"; command.set_operation(DETACH); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device2)); - - command.set_operation(CHECK_AUTHENTICATION); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); - - command.set_operation(NO_OPERATION); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); - - // The operations below are not related to a device - - command.set_operation(DETACH_ALL); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); - - command.set_operation(RESERVE_IDS); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); - - command.set_operation(CREATE_IMAGE); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); - - command.set_operation(DELETE_IMAGE); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); - - command.set_operation(RENAME_IMAGE); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); - - command.set_operation(COPY_IMAGE); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); - - command.set_operation(PROTECT_IMAGE); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); - - command.set_operation(UNPROTECT_IMAGE); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); + CommandContext context_detach(command, "", ""); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_detach, definition, true)); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_detach, definition, false)); } -TEST_F(PiscsiExecutorTest, ProcessCmd) +TEST(PiscsiExecutorTest, ProcessCmd) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); - PiscsiImage piscsi_image; - auto executor = make_shared(piscsi_image, *controller_manager); - PbCommand command1; - PbCommand command2; - MockCommandContext context; + ControllerManager controller_manager; + MockAbstractController controller(bus, 0); + auto executor = make_shared(*bus, controller_manager); - command1.set_operation(DETACH_ALL); - EXPECT_TRUE(executor->ProcessCmd(context, command1)); + PbCommand command_detach_all; + command_detach_all.set_operation(DETACH_ALL); + CommandContext context_detach_all(command_detach_all, "", ""); + EXPECT_TRUE(executor->ProcessCmd(context_detach_all)); - command1.set_operation(RESERVE_IDS); - SetParam(command1, "ids", "2,3"); - EXPECT_TRUE(executor->ProcessCmd(context, command1)); + PbCommand command_reserve_ids1; + command_reserve_ids1.set_operation(RESERVE_IDS); + SetParam(command_reserve_ids1, "ids", "2,3"); + CommandContext context_reserve_ids1(command_reserve_ids1, "", ""); + EXPECT_TRUE(executor->ProcessCmd(context_reserve_ids1)); const unordered_set ids = executor->GetReservedIds(); EXPECT_EQ(2, ids.size()); - EXPECT_NE(ids.end(), ids.find(2)); - EXPECT_NE(ids.end(), ids.find(3)); - command2.set_operation(RESERVE_IDS); - EXPECT_TRUE(executor->ProcessCmd(context, command2)); + EXPECT_TRUE(ids.contains(2)); + EXPECT_TRUE(ids.contains(3)); + + PbCommand command_reserve_ids2; + command_reserve_ids2.set_operation(RESERVE_IDS); + CommandContext context_reserve_ids2(command_reserve_ids2, "", ""); + EXPECT_TRUE(executor->ProcessCmd(context_reserve_ids2)); EXPECT_TRUE(executor->GetReservedIds().empty()); - SetParam(command2, "ids", "-1"); - EXPECT_FALSE(executor->ProcessCmd(context, command2)); + PbCommand command_reserve_ids3; + command_reserve_ids3.set_operation(RESERVE_IDS); + SetParam(command_reserve_ids3, "ids", "-1"); + CommandContext context_reserve_ids3(command_reserve_ids3, "", ""); + EXPECT_FALSE(executor->ProcessCmd(context_reserve_ids3)); EXPECT_TRUE(executor->GetReservedIds().empty()); - command1.set_operation(NO_OPERATION); - EXPECT_TRUE(executor->ProcessCmd(context, command1)); + PbCommand command_no_operation; + command_no_operation.set_operation(NO_OPERATION); + CommandContext context_no_operation(command_no_operation, "", ""); + EXPECT_TRUE(executor->ProcessCmd(context_no_operation)); - command1.set_operation(ATTACH); - auto device = command1.add_devices(); - device->set_type(SCHS); - device->set_id(-1); - EXPECT_FALSE(executor->ProcessCmd(context, command1)); - device->set_id(0); - device->set_unit(1); - EXPECT_FALSE(executor->ProcessCmd(context, command1)) << "LUN 0 is missing"; - device->set_unit(0); - EXPECT_TRUE(executor->ProcessCmd(context, command1)); + PbCommand command_attach1; + command_attach1.set_operation(ATTACH); + auto device1 = command_attach1.add_devices(); + device1->set_type(SCHS); + device1->set_id(-1); + CommandContext context_attach1(command_attach1, "", ""); + EXPECT_FALSE(executor->ProcessCmd(context_attach1)); - // The operations below must fail because of missing parameters. - // The respective functionality is tested in piscsi_image_test.cpp. - - command1.set_operation(CREATE_IMAGE); - EXPECT_FALSE(executor->ProcessCmd(context, command1)); - - command1.set_operation(DELETE_IMAGE); - EXPECT_FALSE(executor->ProcessCmd(context, command1)); - - command1.set_operation(RENAME_IMAGE); - EXPECT_FALSE(executor->ProcessCmd(context, command1)); - - command1.set_operation(COPY_IMAGE); - EXPECT_FALSE(executor->ProcessCmd(context, command1)); - - command1.set_operation(PROTECT_IMAGE); - EXPECT_FALSE(executor->ProcessCmd(context, command1)); - - command1.set_operation(UNPROTECT_IMAGE); - EXPECT_FALSE(executor->ProcessCmd(context, command1)); + PbCommand command_attach2; + command_attach2.set_operation(ATTACH); + auto device2 = command_attach2.add_devices(); + device2->set_type(SCHS); + device2->set_id(0); + device2->set_unit(1); + CommandContext context_attach2(command_attach2, "", ""); + EXPECT_FALSE(executor->ProcessCmd(context_attach2)) << "LUN 0 is missing"; } -TEST_F(PiscsiExecutorTest, SetLogLevel) -{ - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - - EXPECT_TRUE(executor.SetLogLevel("trace")); - EXPECT_TRUE(executor.SetLogLevel("debug")); - EXPECT_TRUE(executor.SetLogLevel("info")); - EXPECT_TRUE(executor.SetLogLevel("warn")); - EXPECT_TRUE(executor.SetLogLevel("err")); - EXPECT_TRUE(executor.SetLogLevel("off")); - EXPECT_FALSE(executor.SetLogLevel("xyz")); -} - -TEST_F(PiscsiExecutorTest, Attach) +TEST(PiscsiExecutorTest, Attach) { const int ID = 3; const int LUN = 0; DeviceFactory device_factory; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); + ControllerManager controller_manager; + PiscsiExecutor executor(*bus, controller_manager); PbDeviceDefinition definition; - MockCommandContext context; + PbCommand command; + CommandContext context(command, "", ""); definition.set_unit(32); EXPECT_FALSE(executor.Attach(context, definition, false)); @@ -258,7 +194,7 @@ TEST_F(PiscsiExecutorTest, Attach) definition.set_type(PbDeviceType::SCHS); EXPECT_TRUE(executor.Attach(context, definition, false)); - controller_manager->DeleteAllControllers(); + controller_manager.DeleteAllControllers(); definition.set_type(PbDeviceType::SCHD); EXPECT_FALSE(executor.Attach(context, definition, false)) << "Drive without sectors not rejected"; @@ -277,19 +213,19 @@ TEST_F(PiscsiExecutorTest, Attach) EXPECT_FALSE(executor.Attach(context, definition, false)) << "Drive with non-existing image file not rejected"; path filename = CreateTempFile(1); - SetParam(definition, "file", filename.c_str()); + SetParam(definition, "file", filename.string()); EXPECT_FALSE(executor.Attach(context, definition, false)) << "Too small image file not rejected"; remove(filename); filename = CreateTempFile(512); - SetParam(definition, "file", filename.c_str()); + SetParam(definition, "file", filename.string()); bool result = executor.Attach(context, definition, false); remove(filename); EXPECT_TRUE(result); - controller_manager->DeleteAllControllers(); + controller_manager.DeleteAllControllers(); filename = CreateTempFile(513); - SetParam(definition, "file", filename.c_str()); + SetParam(definition, "file", filename.string()); result = executor.Attach(context, definition, false); remove(filename); EXPECT_TRUE(result); @@ -297,7 +233,7 @@ TEST_F(PiscsiExecutorTest, Attach) definition.set_type(PbDeviceType::SCCD); definition.set_unit(LUN + 1); filename = CreateTempFile(2048); - SetParam(definition, "file", filename.c_str()); + SetParam(definition, "file", filename.string()); result = executor.Attach(context, definition, false); remove(filename); EXPECT_TRUE(result); @@ -306,25 +242,25 @@ TEST_F(PiscsiExecutorTest, Attach) definition.set_unit(LUN + 2); SetParam(definition, "read_only", "true"); filename = CreateTempFile(4096); - SetParam(definition, "file", filename.c_str()); + SetParam(definition, "file", filename.string()); result = executor.Attach(context, definition, false); remove(filename); EXPECT_TRUE(result); - controller_manager->DeleteAllControllers(); + controller_manager.DeleteAllControllers(); } -TEST_F(PiscsiExecutorTest, Insert) +TEST(PiscsiExecutorTest, Insert) { DeviceFactory device_factory; - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - PbDeviceDefinition definition; - MockCommandContext context; - auto device = device_factory.CreateDevice(SCRM, 0, "test"); + auto bus = make_shared(); + ControllerManager controller_manager; + auto [controller, device] = CreateDevice(SCHD); + PiscsiExecutor executor(*bus, controller_manager); + PbDeviceDefinition definition; + PbCommand command; + CommandContext context(command, "", ""); device->SetRemoved(false); EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "Medium is not removed"; @@ -358,18 +294,18 @@ TEST_F(PiscsiExecutorTest, Insert) EXPECT_FALSE(executor.Insert(context, definition, device, false)); path filename = CreateTempFile(1); - SetParam(definition, "file", filename.c_str()); + SetParam(definition, "file", filename.string()); EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "Too small image file not rejected"; remove(filename); filename = CreateTempFile(512); - SetParam(definition, "file", filename.c_str()); + SetParam(definition, "file", filename.string()); const bool result = executor.Insert(context, definition, device, false); remove(filename); EXPECT_TRUE(result); } -TEST_F(PiscsiExecutorTest, Detach) +TEST(PiscsiExecutorTest, Detach) { const int ID = 3; const int LUN1 = 0; @@ -377,68 +313,51 @@ TEST_F(PiscsiExecutorTest, Detach) DeviceFactory device_factory; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - MockCommandContext context; + ControllerManager controller_manager; + PiscsiExecutor executor(*bus, controller_manager); + PbCommand command; + CommandContext context(command, "", ""); auto device1 = device_factory.CreateDevice(SCHS, LUN1, ""); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device1)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device1)); auto device2 = device_factory.CreateDevice(SCHS, LUN2, ""); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device2)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device2)); - auto d1 = controller_manager->GetDeviceByIdAndLun(ID, LUN1); - EXPECT_FALSE(executor.Detach(context, d1, false)) << "LUNs > 0 have to be detached first"; - auto d2 = controller_manager->GetDeviceByIdAndLun(ID, LUN2); - EXPECT_TRUE(executor.Detach(context, d2, false)); - EXPECT_TRUE(executor.Detach(context, d1, false)); - EXPECT_TRUE(controller_manager->GetAllDevices().empty()); + auto d1 = controller_manager.GetDeviceForIdAndLun(ID, LUN1); + EXPECT_FALSE(executor.Detach(context, *d1, false)) << "LUNs > 0 have to be detached first"; + auto d2 = controller_manager.GetDeviceForIdAndLun(ID, LUN2); + EXPECT_TRUE(executor.Detach(context, *d2, false)); + EXPECT_TRUE(executor.Detach(context, *d1, false)); + EXPECT_TRUE(controller_manager.GetAllDevices().empty()); - EXPECT_FALSE(executor.Detach(context, d1, false)); + EXPECT_FALSE(executor.Detach(context, *d1, false)); } -TEST_F(PiscsiExecutorTest, DetachAll) +TEST(PiscsiExecutorTest, DetachAll) { const int ID = 4; DeviceFactory device_factory; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); + ControllerManager controller_manager; + PiscsiExecutor executor(*bus, controller_manager); auto device = device_factory.CreateDevice(SCHS, 0, ""); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device)); - EXPECT_NE(nullptr, controller_manager->FindController(ID)); - EXPECT_FALSE(controller_manager->GetAllDevices().empty()); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device)); + EXPECT_TRUE(controller_manager.HasController(ID)); + EXPECT_FALSE(controller_manager.GetAllDevices().empty()); executor.DetachAll(); - EXPECT_EQ(nullptr, controller_manager->FindController(ID)); - EXPECT_TRUE(controller_manager->GetAllDevices().empty()); + EXPECT_EQ(nullptr, controller_manager.FindController(ID)); + EXPECT_TRUE(controller_manager.GetAllDevices().empty()); } -TEST_F(PiscsiExecutorTest, ShutDown) -{ - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - MockCommandContext context; - - EXPECT_FALSE(executor.ShutDown(context, "")); - EXPECT_FALSE(executor.ShutDown(context, "xyz")); - EXPECT_TRUE(executor.ShutDown(context, "rascsi")); - EXPECT_FALSE(executor.ShutDown(context, "system")); - EXPECT_FALSE(executor.ShutDown(context, "reboot")); -} - -TEST_F(PiscsiExecutorTest, SetReservedIds) +TEST(PiscsiExecutorTest, SetReservedIds) { DeviceFactory device_factory; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); + ControllerManager controller_manager; + PiscsiExecutor executor(*bus, controller_manager); string error = executor.SetReservedIds("xyz"); EXPECT_FALSE(error.empty()); @@ -460,61 +379,82 @@ TEST_F(PiscsiExecutorTest, SetReservedIds) EXPECT_TRUE(error.empty()); unordered_set reserved_ids = executor.GetReservedIds(); EXPECT_EQ(5, reserved_ids.size()); - EXPECT_NE(reserved_ids.end(), reserved_ids.find(1)); - EXPECT_NE(reserved_ids.end(), reserved_ids.find(2)); - EXPECT_NE(reserved_ids.end(), reserved_ids.find(3)); - EXPECT_NE(reserved_ids.end(), reserved_ids.find(5)); - EXPECT_NE(reserved_ids.end(), reserved_ids.find(7)); + EXPECT_TRUE(reserved_ids.contains(1)); + EXPECT_TRUE(reserved_ids.contains(2)); + EXPECT_TRUE(reserved_ids.contains(3)); + EXPECT_TRUE(reserved_ids.contains(5)); + EXPECT_TRUE(reserved_ids.contains(7)); auto device = device_factory.CreateDevice(SCHS, 0, ""); - EXPECT_TRUE(controller_manager->AttachToScsiController(5, device)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, 5, device)); error = executor.SetReservedIds("5"); EXPECT_FALSE(error.empty()); } -TEST_F(PiscsiExecutorTest, ValidateImageFile) +TEST(PiscsiExecutorTest, ValidateImageFile) { DeviceFactory device_factory; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - MockCommandContext context; + ControllerManager controller_manager; + PiscsiExecutor executor(*bus, controller_manager); + PbCommand command; + CommandContext context(command, "", ""); - string full_path; auto device = dynamic_pointer_cast(device_factory.CreateDevice(SCHD, 0, "test")); - EXPECT_TRUE(executor.ValidateImageFile(context, *device, "", full_path)); - EXPECT_TRUE(full_path.empty()); + EXPECT_TRUE(executor.ValidateImageFile(context, *device, "")); - EXPECT_FALSE(executor.ValidateImageFile(context, *device, "/non_existing_file", full_path)); - EXPECT_TRUE(full_path.empty()); + EXPECT_FALSE(executor.ValidateImageFile(context, *device, "/non_existing_file")); } -TEST_F(PiscsiExecutorTest, ValidateLunSetup) +TEST(PiscsiExecutorTest, PrintCommand) +{ + auto bus = make_shared(); + ControllerManager controller_manager; + PiscsiExecutor executor(*bus, controller_manager); + PbDeviceDefinition definition; + + PbCommand command; + string s = executor.PrintCommand(command, definition); + EXPECT_NE(s.find("operation="), string::npos); + EXPECT_EQ(s.find("key1=value1"), string::npos); + EXPECT_EQ(s.find("key2=value2"), string::npos); + + SetParam(command, "key1", "value1"); + s = executor.PrintCommand(command, definition); + EXPECT_NE(s.find("operation="), string::npos); + EXPECT_NE(s.find("key1=value1"), string::npos); + + SetParam(command, "key2", "value2"); + s = executor.PrintCommand(command, definition); + EXPECT_NE(s.find("operation="), string::npos); + EXPECT_NE(s.find("key1=value1"), string::npos); + EXPECT_NE(s.find("key2=value2"), string::npos); +} + +TEST(PiscsiExecutorTest, EnsureLun0) { DeviceFactory device_factory; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); + ControllerManager controller_manager; + PiscsiExecutor executor(*bus, controller_manager); PbCommand command; auto device1 = command.add_devices(); device1->set_unit(0); - string error = executor.ValidateLunSetup(command); + string error = executor.EnsureLun0(command); EXPECT_TRUE(error.empty()); device1->set_unit(1); - error = executor.ValidateLunSetup(command); + error = executor.EnsureLun0(command); EXPECT_FALSE(error.empty()); auto device2 = device_factory.CreateDevice(SCHS, 0, ""); - EXPECT_TRUE(controller_manager->AttachToScsiController(0, device2)); - error = executor.ValidateLunSetup(command); + EXPECT_TRUE(controller_manager.AttachToController(*bus, 0, device2)); + error = executor.EnsureLun0(command); EXPECT_TRUE(error.empty()); } -TEST_F(PiscsiExecutorTest, VerifyExistingIdAndLun) +TEST(PiscsiExecutorTest, VerifyExistingIdAndLun) { const int ID = 1; const int LUN1 = 0; @@ -522,25 +462,25 @@ TEST_F(PiscsiExecutorTest, VerifyExistingIdAndLun) DeviceFactory device_factory; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - MockCommandContext context; + ControllerManager controller_manager; + PiscsiExecutor executor(*bus, controller_manager); + PbCommand command; + CommandContext context(command, "", ""); EXPECT_FALSE(executor.VerifyExistingIdAndLun(context, ID, LUN1)); auto device = device_factory.CreateDevice(SCHS, LUN1, ""); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device)); EXPECT_TRUE(executor.VerifyExistingIdAndLun(context, ID, LUN1)); EXPECT_FALSE(executor.VerifyExistingIdAndLun(context, ID, LUN2)); } -TEST_F(PiscsiExecutorTest, CreateDevice) +TEST(PiscsiExecutorTest, CreateDevice) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - MockCommandContext context; + ControllerManager controller_manager; + PiscsiExecutor executor(*bus, controller_manager); + PbCommand command; + CommandContext context(command, "", ""); EXPECT_EQ(nullptr, executor.CreateDevice(context, UNDEFINED, 0, "")); #pragma GCC diagnostic push @@ -551,13 +491,13 @@ TEST_F(PiscsiExecutorTest, CreateDevice) EXPECT_NE(nullptr, executor.CreateDevice(context, SCHS, 0, "")); } -TEST_F(PiscsiExecutorTest, SetSectorSize) +TEST(PiscsiExecutorTest, SetSectorSize) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - MockCommandContext context; + ControllerManager controller_manager; + PiscsiExecutor executor(*bus, controller_manager); + PbCommand command; + CommandContext context(command, "", ""); unordered_set sizes; auto hd = make_shared(0, sizes, false); @@ -570,13 +510,13 @@ TEST_F(PiscsiExecutorTest, SetSectorSize) EXPECT_TRUE(executor.SetSectorSize(context, hd, 512)); } -TEST_F(PiscsiExecutorTest, ValidateOperationAgainstDevice) +TEST(PiscsiExecutorTest, ValidateOperationAgainstDevice) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - MockCommandContext context; + ControllerManager controller_manager; + PiscsiExecutor executor(*bus, controller_manager); + PbCommand command; + CommandContext context(command, "", ""); auto device = make_shared(0); @@ -623,13 +563,13 @@ TEST_F(PiscsiExecutorTest, ValidateOperationAgainstDevice) EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, *device, UNPROTECT)); } -TEST_F(PiscsiExecutorTest, ValidateIdAndLun) +TEST(PiscsiExecutorTest, ValidateIdAndLun) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - MockCommandContext context; + ControllerManager controller_manager; + PiscsiExecutor executor(*bus, controller_manager); + PbCommand command; + CommandContext context(command, "", ""); EXPECT_FALSE(executor.ValidateIdAndLun(context, -1, 0)); EXPECT_FALSE(executor.ValidateIdAndLun(context, 8, 0)); @@ -639,13 +579,13 @@ TEST_F(PiscsiExecutorTest, ValidateIdAndLun) EXPECT_TRUE(executor.ValidateIdAndLun(context, 7, 31)); } -TEST_F(PiscsiExecutorTest, SetProductData) +TEST(PiscsiExecutorTest, SetProductData) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - MockCommandContext context; + ControllerManager controller_manager; + PiscsiExecutor executor(*bus, controller_manager); + PbCommand command; + CommandContext context(command, "", ""); PbDeviceDefinition definition; auto device = make_shared(0); diff --git a/cpp/test/piscsi_image_test.cpp b/cpp/test/piscsi_image_test.cpp index ac48aa2b..aa5bbdab 100644 --- a/cpp/test/piscsi_image_test.cpp +++ b/cpp/test/piscsi_image_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -35,104 +35,129 @@ TEST(PiscsiImageTest, SetGetDefaultFolder) TEST(PiscsiImageTest, CreateImage) { - MockCommandContext context; - PbCommand command; PiscsiImage image; StorageDevice::UnreserveAll(); - EXPECT_FALSE(image.CreateImage(context, command)) << "Filename must be reported as missing"; + PbCommand command1; + CommandContext context1(command1, "", ""); + EXPECT_FALSE(image.CreateImage(context1)) << "Filename must be reported as missing"; - SetParam(command, "file", "/a/b/c/filename"); - EXPECT_FALSE(image.CreateImage(context, command)) << "Depth must be reported as invalid"; + PbCommand command2; + SetParam(command2, "file", "/a/b/c/filename"); + CommandContext context2(command2, "", ""); + EXPECT_FALSE(image.CreateImage(context2)) << "Depth must be reported as invalid"; - SetParam(command, "file", "filename"); - SetParam(command, "size", "-1"); - EXPECT_FALSE(image.CreateImage(context, command)) << "Size must be reported as invalid"; + PbCommand command3; + SetParam(command3, "file", "filename"); + SetParam(command3, "size", "-1"); + CommandContext context3(command3, "", ""); + EXPECT_FALSE(image.CreateImage(context3)) << "Size must be reported as invalid"; - SetParam(command, "size", "1"); - EXPECT_FALSE(image.CreateImage(context, command)) << "Size must be reported as invalid"; + PbCommand command4; + SetParam(command4, "size", "1"); + CommandContext context4(command4, "", ""); + EXPECT_FALSE(image.CreateImage(context4)) << "Size must be reported as invalid"; - SetParam(command, "size", "513"); - EXPECT_FALSE(image.CreateImage(context, command)) << "Size must be reported as not a multiple of 512"; + PbCommand command5; + SetParam(command5, "size", "513"); + CommandContext context5(command5, "", ""); + EXPECT_FALSE(image.CreateImage(context4)) << "Size must be reported as not a multiple of 512"; // Further tests would modify the filesystem } TEST(PiscsiImageTest, DeleteImage) { - MockCommandContext context; - PbCommand command; PiscsiImage image; StorageDevice::UnreserveAll(); - EXPECT_FALSE(image.DeleteImage(context, command)) << "Filename must be reported as missing"; + PbCommand command1; + CommandContext context1(command1, "", ""); + EXPECT_FALSE(image.DeleteImage(context1)) << "Filename must be reported as missing"; - SetParam(command, "file", "/a/b/c/filename"); - EXPECT_FALSE(image.DeleteImage(context, command)) << "Depth must be reported as invalid"; + PbCommand command2; + SetParam(command2, "file", "/a/b/c/filename"); + CommandContext context2(command2, "", ""); + EXPECT_FALSE(image.DeleteImage(context2)) << "Depth must be reported as invalid"; MockStorageDevice device; - device.ReserveFile("filename", 0, 0); - SetParam(command, "file", "filename"); - EXPECT_FALSE(image.DeleteImage(context, command)) << "File must be reported as in use"; + device.SetFilename("filename"); + device.ReserveFile(); + PbCommand command3; + SetParam(command3, "file", "filename"); + CommandContext context3(command3, "", ""); + EXPECT_FALSE(image.DeleteImage(context3)) << "File must be reported as in use"; // Further testing would modify the filesystem } TEST(PiscsiImageTest, RenameImage) { - MockCommandContext context; - PbCommand command; PiscsiImage image; StorageDevice::UnreserveAll(); - EXPECT_FALSE(image.RenameImage(context, command)) << "Source filename must be reported as missing"; + PbCommand command1; + CommandContext context1(command1, "", ""); + EXPECT_FALSE(image.RenameImage(context1)) << "Source filename must be reported as missing"; - SetParam(command, "from", "/a/b/c/filename_from"); - EXPECT_FALSE(image.RenameImage(context, command)) << "Depth must be reported as invalid"; + PbCommand command2; + SetParam(command2, "from", "/a/b/c/filename_from"); + CommandContext context2(command2, "", ""); + EXPECT_FALSE(image.RenameImage(context2)) << "Depth must be reported as invalid"; - SetParam(command, "from", "filename_from"); - EXPECT_FALSE(image.RenameImage(context, command)) << "Source file must be reported as missing"; + PbCommand command3; + SetParam(command3, "from", "filename_from"); + CommandContext context3(command3, "", ""); + EXPECT_FALSE(image.RenameImage(context3)) << "Source file must be reported as missing"; // Further testing would modify the filesystem } TEST(PiscsiImageTest, CopyImage) { - MockCommandContext context; - PbCommand command; PiscsiImage image; StorageDevice::UnreserveAll(); - EXPECT_FALSE(image.CopyImage(context, command)) << "Source filename must be reported as missing"; + PbCommand command1; + CommandContext context1(command1, "", ""); + EXPECT_FALSE(image.CopyImage(context1)) << "Source filename must be reported as missing"; - SetParam(command, "from", "/a/b/c/filename_from"); - EXPECT_FALSE(image.CopyImage(context, command)) << "Depth must be reported as invalid"; + PbCommand command2; + SetParam(command2, "from", "/a/b/c/filename_from"); + CommandContext context2(command2, "", ""); + EXPECT_FALSE(image.CopyImage(context2)) << "Depth must be reported as invalid"; - SetParam(command, "from", "filename_from"); - EXPECT_FALSE(image.CopyImage(context, command)) << "Source file must be reported as missing"; + PbCommand command3; + SetParam(command3, "from", "filename_from"); + CommandContext context3(command3, "", ""); + EXPECT_FALSE(image.CopyImage(context3)) << "Source file must be reported as missing"; // Further testing would modify the filesystem } TEST(PiscsiImageTest, SetImagePermissions) { - MockCommandContext context; - PbCommand command; PiscsiImage image; StorageDevice::UnreserveAll(); - EXPECT_FALSE(image.SetImagePermissions(context, command)) << "Filename must be reported as missing"; + PbCommand command1; + CommandContext context1(command1, "", ""); + EXPECT_FALSE(image.SetImagePermissions(context1)) << "Filename must be reported as missing"; - SetParam(command, "file", "/a/b/c/filename"); - EXPECT_FALSE(image.SetImagePermissions(context, command)) << "Depth must be reported as invalid"; + PbCommand command2; + SetParam(command2, "file", "/a/b/c/filename"); + CommandContext context2(command2, "", ""); + EXPECT_FALSE(image.SetImagePermissions(context2)) << "Depth must be reported as invalid"; - SetParam(command, "file", "filename"); - EXPECT_FALSE(image.CopyImage(context, command)) << "Source file must be reported as missing"; + PbCommand command3; + SetParam(command3, "file", "filename"); + CommandContext context3(command3, "", ""); + EXPECT_FALSE(image.CopyImage(context3)) << "Source file must be reported as missing"; // Further testing would modify the filesystem } diff --git a/cpp/test/piscsi_response_test.cpp b/cpp/test/piscsi_response_test.cpp index 168eb5bc..510c2637 100644 --- a/cpp/test/piscsi_response_test.cpp +++ b/cpp/test/piscsi_response_test.cpp @@ -3,42 +3,46 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "mocks.h" #include "shared/piscsi_version.h" +#include "shared/protobuf_util.h" #include "controllers/controller_manager.h" #include "devices/device_factory.h" #include "generated/piscsi_interface.pb.h" #include "piscsi/piscsi_response.h" +#include using namespace piscsi_interface; +using namespace spdlog; +using namespace protobuf_util; TEST(PiscsiResponseTest, Operation_Count) { PiscsiResponse response; - PbResult result; - const auto info = response.GetOperationInfo(result, 0); - EXPECT_EQ(PbOperation_ARRAYSIZE - 1, info->operations_size()); + PbOperationInfo info; + response.GetOperationInfo(info, 0); + EXPECT_EQ(PbOperation_ARRAYSIZE - 1, info.operations_size()); } void TestNonDiskDevice(PbDeviceType type, int default_param_count) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; DeviceFactory device_factory; PiscsiResponse response; auto d = device_factory.CreateDevice(type, 0, ""); - const unordered_map params; + const param_map params; d->Init(params); - EXPECT_TRUE(controller_manager->AttachToScsiController(0, d)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, 0, d)); PbServerInfo info; - response.GetDevices(controller_manager->GetAllDevices(), info, "image_folder"); + response.GetDevices(controller_manager.GetAllDevices(), info, "image_folder"); EXPECT_EQ(1, info.devices_info().devices().size()); @@ -52,7 +56,6 @@ void TestNonDiskDevice(PbDeviceType type, int default_param_count) EXPECT_EQ(0, device.block_size()); EXPECT_EQ(0, device.block_count()); EXPECT_EQ(default_param_count, device.properties().default_params().size()); - EXPECT_EQ(default_param_count, device.params().size()); EXPECT_FALSE(device.properties().supports_file()); if (default_param_count) { EXPECT_TRUE(device.properties().supports_params()); @@ -81,21 +84,29 @@ TEST(PiscsiResponseTest, GetImageFile) EXPECT_EQ(SCHD, image_file.type()); } +TEST(PiscsiResponseTest, GetImageFilesInfo) +{ + PiscsiResponse response; + + PbImageFilesInfo info; + response.GetImageFilesInfo(info, "default_folder", "", "", 1); + EXPECT_TRUE(info.image_files().empty()); +} + TEST(PiscsiResponseTest, GetReservedIds) { PiscsiResponse response; unordered_set ids; - PbResult result; - const auto& info1 = response.GetReservedIds(result, ids); - EXPECT_TRUE(result.status()); - EXPECT_TRUE(info1->ids().empty()); + PbReservedIdsInfo info1; + response.GetReservedIds(info1, ids); + EXPECT_TRUE(info1.ids().empty()); ids.insert(3); - const auto& info2 = response.GetReservedIds(result, ids); - EXPECT_TRUE(result.status()); - EXPECT_EQ(1, info2->ids().size()); - EXPECT_EQ(3, info2->ids()[0]); + PbReservedIdsInfo info2; + response.GetReservedIds(info2, ids); + EXPECT_EQ(1, info2.ids().size()); + EXPECT_EQ(3, info2.ids()[0]); } TEST(PiscsiResponseTest, GetDevicesInfo) @@ -106,40 +117,42 @@ TEST(PiscsiResponseTest, GetDevicesInfo) const int LUN3 = 6; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiResponse response; PbCommand command; - PbResult result; - response.GetDevicesInfo(controller_manager->GetAllDevices(), result, command, ""); - EXPECT_TRUE(result.status()); - EXPECT_TRUE(result.devices_info().devices().empty()); + PbResult result1; + response.GetDevicesInfo(controller_manager.GetAllDevices(), result1, command, ""); + EXPECT_TRUE(result1.status()); + EXPECT_TRUE(result1.devices_info().devices().empty()); auto device1 = make_shared(LUN1); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device1)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device1)); - response.GetDevicesInfo(controller_manager->GetAllDevices(), result, command, ""); - EXPECT_TRUE(result.status()); - auto& devices1 = result.devices_info().devices(); + response.GetDevicesInfo(controller_manager.GetAllDevices(), result1, command, ""); + EXPECT_TRUE(result1.status()); + auto& devices1 = result1.devices_info().devices(); EXPECT_EQ(1, devices1.size()); EXPECT_EQ(SCHS, devices1[0].type()); EXPECT_EQ(ID, devices1[0].id()); EXPECT_EQ(LUN1, devices1[0].unit()); auto device2 = make_shared(LUN2); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device2)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device2)); - response.GetDevicesInfo(controller_manager->GetAllDevices(), result, command, ""); - EXPECT_TRUE(result.status()); - auto& devices2 = result.devices_info().devices(); - EXPECT_EQ(2, devices2.size()) << "Data for all devices must be returned"; + PbResult result2; + response.GetDevicesInfo(controller_manager.GetAllDevices(), result2, command, ""); + EXPECT_TRUE(result2.status()); + auto& devices2 = result2.devices_info().devices(); + EXPECT_EQ(2, devices2.size()) << "Device count mismatch"; auto requested_device = command.add_devices(); requested_device->set_id(ID); requested_device->set_unit(LUN1); - response.GetDevicesInfo(controller_manager->GetAllDevices(), result, command, ""); - EXPECT_TRUE(result.status()); - auto& devices3 = result.devices_info().devices(); + PbResult result3; + response.GetDevicesInfo(controller_manager.GetAllDevices(), result3, command, ""); + EXPECT_TRUE(result3.status()); + auto& devices3 = result3.devices_info().devices(); EXPECT_EQ(1, devices3.size()) << "Only data for the specified ID and LUN must be returned"; EXPECT_EQ(SCHS, devices3[0].type()); EXPECT_EQ(ID, devices3[0].id()); @@ -147,79 +160,99 @@ TEST(PiscsiResponseTest, GetDevicesInfo) requested_device->set_id(ID); requested_device->set_unit(LUN3); - response.GetDevicesInfo(controller_manager->GetAllDevices(), result, command, ""); - EXPECT_FALSE(result.status()) << "Only data for the specified ID and LUN must be returned"; + PbResult result4; + response.GetDevicesInfo(controller_manager.GetAllDevices(), result4, command, ""); + EXPECT_FALSE(result4.status()) << "Only data for the specified ID and LUN must be returned"; } TEST(PiscsiResponseTest, GetDeviceTypesInfo) { PiscsiResponse response; - PbResult result; - const auto& info = response.GetDeviceTypesInfo(result); - EXPECT_TRUE(result.status()); - EXPECT_EQ(8, info->properties().size()); + PbDeviceTypesInfo info; + response.GetDeviceTypesInfo(info); + EXPECT_EQ(8, info.properties().size()); } TEST(PiscsiResponseTest, GetServerInfo) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); PiscsiResponse response; const unordered_set> devices; const unordered_set ids = { 1, 3 }; - PbResult result; - const auto& info = response.GetServerInfo(devices, result, ids, "log_level", "default_folder", "", "", 1234); - EXPECT_TRUE(result.status()); - EXPECT_EQ(piscsi_major_version, info->version_info().major_version()); - EXPECT_EQ(piscsi_minor_version, info->version_info().minor_version()); - EXPECT_EQ(piscsi_patch_version, info->version_info().patch_version()); - EXPECT_EQ("log_level", info->log_level_info().current_log_level()); - EXPECT_EQ("default_folder", info->image_files_info().default_image_folder()); - EXPECT_EQ(1234, info->image_files_info().depth()); - EXPECT_EQ(2, info->reserved_ids_info().ids().size()); + PbCommand command; + PbServerInfo info1; + response.GetServerInfo(info1, command, devices, ids, "default_folder", 1234); + EXPECT_TRUE(info1.has_version_info()); + EXPECT_TRUE(info1.has_log_level_info()); + EXPECT_TRUE(info1.has_device_types_info()); + EXPECT_TRUE(info1.has_image_files_info()); + EXPECT_TRUE(info1.has_network_interfaces_info()); + EXPECT_TRUE(info1.has_mapping_info()); + EXPECT_TRUE(info1.has_statistics_info()); + EXPECT_FALSE(info1.has_devices_info()); + EXPECT_TRUE(info1.has_reserved_ids_info()); + EXPECT_TRUE(info1.has_operation_info()); + + EXPECT_EQ(piscsi_major_version, info1.version_info().major_version()); + EXPECT_EQ(piscsi_minor_version, info1.version_info().minor_version()); + EXPECT_EQ(piscsi_patch_version, info1.version_info().patch_version()); + EXPECT_EQ(level::level_string_views[get_level()], info1.log_level_info().current_log_level()); + EXPECT_EQ("default_folder", info1.image_files_info().default_image_folder()); + EXPECT_EQ(1234, info1.image_files_info().depth()); + EXPECT_EQ(2, info1.reserved_ids_info().ids().size()); + + SetParam(command, "operations", "log_level_info,mapping_info"); + PbServerInfo info2; + response.GetServerInfo(info2, command, devices, ids, "default_folder", 1234); + EXPECT_FALSE(info2.has_version_info()); + EXPECT_TRUE(info2.has_log_level_info()); + EXPECT_FALSE(info2.has_device_types_info()); + EXPECT_FALSE(info2.has_image_files_info()); + EXPECT_FALSE(info2.has_network_interfaces_info()); + EXPECT_TRUE(info2.has_mapping_info()); + EXPECT_FALSE(info2.has_statistics_info()); + EXPECT_FALSE(info2.has_devices_info()); + EXPECT_FALSE(info2.has_reserved_ids_info()); + EXPECT_FALSE(info2.has_operation_info()); } TEST(PiscsiResponseTest, GetVersionInfo) { PiscsiResponse response; - PbResult result; - const auto& info = response.GetVersionInfo(result); - EXPECT_TRUE(result.status()); - EXPECT_EQ(piscsi_major_version, info->major_version()); - EXPECT_EQ(piscsi_minor_version, info->minor_version()); - EXPECT_EQ(piscsi_patch_version, info->patch_version()); + PbVersionInfo info; + response.GetVersionInfo(info); + EXPECT_EQ(piscsi_major_version, info.major_version()); + EXPECT_EQ(piscsi_minor_version, info.minor_version()); + EXPECT_EQ(piscsi_patch_version, info.patch_version()); } TEST(PiscsiResponseTest, GetLogLevelInfo) { PiscsiResponse response; - PbResult result; - const auto& info = response.GetLogLevelInfo(result, "level"); - EXPECT_TRUE(result.status()); - EXPECT_EQ("level", info->current_log_level()); - EXPECT_EQ(6, info->log_levels().size()); + PbLogLevelInfo info; + response.GetLogLevelInfo(info); + EXPECT_EQ(level::level_string_views[get_level()], info.current_log_level()); + EXPECT_EQ(7, info.log_levels().size()); } TEST(PiscsiResponseTest, GetNetworkInterfacesInfo) { PiscsiResponse response; - PbResult result; - const auto& info = response.GetNetworkInterfacesInfo(result); - EXPECT_TRUE(result.status()); - EXPECT_FALSE(info->name().empty()); + PbNetworkInterfacesInfo info; + response.GetNetworkInterfacesInfo(info); + EXPECT_FALSE(info.name().empty()); } TEST(PiscsiResponseTest, GetMappingInfo) { PiscsiResponse response; - PbResult result; - const auto& info = response.GetMappingInfo(result); - EXPECT_TRUE(result.status()); - EXPECT_EQ(10, info->mapping().size()); + PbMappingInfo info; + response.GetMappingInfo(info); + EXPECT_EQ(10, info.mapping().size()); } diff --git a/cpp/test/piscsi_service_test.cpp b/cpp/test/piscsi_service_test.cpp index a29a8172..80a1c8c6 100644 --- a/cpp/test/piscsi_service_test.cpp +++ b/cpp/test/piscsi_service_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // These tests only test up the point where a network connection is required. // @@ -11,16 +11,100 @@ #include +#include "generated/piscsi_interface.pb.h" +#include "shared/protobuf_util.h" +#include "shared/network_util.h" +#include "shared/piscsi_exceptions.h" +#include "piscsi/command_context.h" #include "piscsi/piscsi_service.h" +#include +#include +#include +#include +#include -TEST(PiscsiServiceTest, LifeCycle) +using namespace piscsi_interface; +using namespace protobuf_util; +using namespace network_util; + +void SendCommand(const PbCommand& command, PbResult& result) +{ + sockaddr_in server_addr = {}; + ASSERT_TRUE(ResolveHostName("127.0.0.1", &server_addr)); + server_addr.sin_port = htons(uint16_t(9999)); + + const int fd = socket(AF_INET, SOCK_STREAM, 0); + ASSERT_NE(-1, fd); + EXPECT_TRUE(connect(fd, reinterpret_cast(&server_addr), sizeof(server_addr)) >= 0) << "Service should be running"; //NOSONAR bit_cast is not supported by the bullseye clang++ compiler + ASSERT_EQ(6, write(fd, "RASCSI", 6)); + SerializeMessage(fd, command); + DeserializeMessage(fd, result); + close(fd); +} + +TEST(PiscsiServiceTest, Init) { PiscsiService service; - EXPECT_TRUE(service.Init(nullptr, 65535)); - EXPECT_FALSE(service.Init(nullptr, 65536)); - EXPECT_FALSE(service.Init(nullptr, 0)); - EXPECT_FALSE(service.Init(nullptr, -1)); - - service.Cleanup(); + EXPECT_FALSE(service.Init(nullptr, 65536).empty()) << "Illegal port number"; + EXPECT_FALSE(service.Init(nullptr, 0).empty()) << "Illegal port number"; + EXPECT_FALSE(service.Init(nullptr, -1).empty()) << "Illegal port number"; + EXPECT_FALSE(service.Init(nullptr, 1).empty()) << "Port 1 is only available for the root user"; + EXPECT_TRUE(service.Init(nullptr, 9999).empty()) << "Port 9999 is expected not to be in use for this test"; + service.Stop(); +} + +TEST(PiscsiServiceTest, IsRunning) +{ + PiscsiService service; + EXPECT_FALSE(service.IsRunning()); + EXPECT_TRUE(service.Init(nullptr, 9999).empty()) << "Port 9999 is expected not to be in use for this test"; + EXPECT_FALSE(service.IsRunning()); + + service.Start(); + EXPECT_TRUE(service.IsRunning()); + service.Stop(); + EXPECT_FALSE(service.IsRunning()); +} + +TEST(PiscsiServiceTest, Execute) +{ + sockaddr_in server_addr = {}; + ASSERT_TRUE(ResolveHostName("127.0.0.1", &server_addr)); + + const int fd = socket(AF_INET, SOCK_STREAM, 0); + ASSERT_NE(-1, fd); + + server_addr.sin_port = htons(uint16_t(9999)); + EXPECT_FALSE(connect(fd, reinterpret_cast(&server_addr), sizeof(server_addr)) >= 0) << "Service should not be running"; //NOSONAR bit_cast is not supported by the bullseye clang++ compiler + + close(fd); + + PiscsiService service; + service.Init([] (const CommandContext& context) { + if (context.GetCommand().operation() == PbOperation::NO_OPERATION) { + PbResult result; + result.set_status(true); + context.WriteResult(result); + } + else { + throw io_exception("error"); + } + return true; + }, 9999); + + service.Start(); + + PbCommand command; + PbResult result; + + SendCommand(command, result); + command.set_operation(PbOperation::NO_OPERATION); + EXPECT_TRUE(result.status()) << "Command should have been successful"; + + command.set_operation(PbOperation::EJECT); + SendCommand(command, result); + EXPECT_FALSE(result.status()) << "Exception should have been raised"; + + service.Stop(); } diff --git a/cpp/test/piscsi_util_test.cpp b/cpp/test/piscsi_util_test.cpp index bfe2bdd1..03a5133e 100644 --- a/cpp/test/piscsi_util_test.cpp +++ b/cpp/test/piscsi_util_test.cpp @@ -3,53 +3,109 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include #include "shared/piscsi_util.h" -#include "generated/piscsi_interface.pb.h" #ifdef __linux__ #include #endif using namespace std; -using namespace piscsi_interface; using namespace piscsi_util; +TEST(PiscsiUtilTest, Split) +{ + auto v = Split("this_is_a_test", '_'); + EXPECT_EQ(4, v.size()); + EXPECT_EQ("this", v[0]); + EXPECT_EQ("is", v[1]); + EXPECT_EQ("a", v[2]); + EXPECT_EQ("test", v[3]); + v = Split("test", ':'); + EXPECT_EQ(1, v.size()); + EXPECT_EQ("test", v[0]); + v = Split(":test", ':'); + EXPECT_EQ(2, v.size()); + EXPECT_EQ("", v[0]); + EXPECT_EQ("test", v[1]); + v = Split("test:", ':'); + EXPECT_EQ(1, v.size()); + EXPECT_EQ("test", v[0]); + v = Split(":", ':'); + EXPECT_EQ(1, v.size()); + EXPECT_EQ("", v[0]); + v = Split("", ':'); + EXPECT_EQ(0, v.size()); + + v = Split("this:is:a:test", ':', 1); + EXPECT_EQ(1, v.size()); + EXPECT_EQ("this:is:a:test", v[0]); + v = Split("this:is:a:test", ':', 2); + EXPECT_EQ(2, v.size()); + EXPECT_EQ("this", v[0]); + EXPECT_EQ("is:a:test", v[1]); +} + +TEST(PiscsiUtilTest, GetLocale) +{ + EXPECT_LE(2, GetLocale().size()); +} + TEST(PiscsiUtilTest, ProcessId) { int id = -1; int lun = -1; - string error = ProcessId("", 32, id, lun); + string error = ProcessId("", id, lun); EXPECT_FALSE(error.empty()); EXPECT_EQ(-1, id); EXPECT_EQ(-1, lun); - error = ProcessId("0:32", 32, id, lun); + error = ProcessId("8", id, lun); EXPECT_FALSE(error.empty()); EXPECT_EQ(-1, id); EXPECT_EQ(-1, lun); - error = ProcessId("-1:", 32, id, lun); + error = ProcessId("0:32", id, lun); EXPECT_FALSE(error.empty()); EXPECT_EQ(-1, id); EXPECT_EQ(-1, lun); - error = ProcessId("0:-1", 32, id, lun); + error = ProcessId("-1:", id, lun); EXPECT_FALSE(error.empty()); EXPECT_EQ(-1, id); EXPECT_EQ(-1, lun); - error = ProcessId("0", 32, id, lun); + error = ProcessId("0:-1", id, lun); + EXPECT_FALSE(error.empty()); + EXPECT_EQ(-1, id); + EXPECT_EQ(-1, lun); + + error = ProcessId("a", id, lun); + EXPECT_FALSE(error.empty()); + EXPECT_EQ(-1, id); + EXPECT_EQ(-1, lun); + + error = ProcessId("a:0", id, lun); + EXPECT_FALSE(error.empty()); + EXPECT_EQ(-1, id); + EXPECT_EQ(-1, lun); + + error = ProcessId("0:a", id, lun); + EXPECT_FALSE(error.empty()); + EXPECT_EQ(-1, id); + EXPECT_EQ(-1, lun); + + error = ProcessId("0", id, lun); EXPECT_TRUE(error.empty()); EXPECT_EQ(0, id); - EXPECT_EQ(0, lun); + EXPECT_EQ(-1, lun); - error = ProcessId("7:31", 32, id, lun); + error = ProcessId("7:31", id, lun); EXPECT_TRUE(error.empty()); EXPECT_EQ(7, id); EXPECT_EQ(31, lun); @@ -78,10 +134,10 @@ TEST(PiscsiUtilTest, GetExtensionLowerCase) { EXPECT_EQ("", GetExtensionLowerCase("")); EXPECT_EQ("", GetExtensionLowerCase(".")); + EXPECT_EQ("", GetExtensionLowerCase(".ext")); + EXPECT_EQ("", GetExtensionLowerCase(".ext_long")); EXPECT_EQ("ext", GetExtensionLowerCase("file.ext")); EXPECT_EQ("ext", GetExtensionLowerCase("FILE.EXT")); - EXPECT_EQ("ext", GetExtensionLowerCase(".ext")); - EXPECT_EQ("ext_long", GetExtensionLowerCase(".ext_long")); EXPECT_EQ("ext", GetExtensionLowerCase(".XYZ.EXT")); } diff --git a/cpp/test/primary_device_test.cpp b/cpp/test/primary_device_test.cpp index 233e5f38..0a3739a0 100644 --- a/cpp/test/primary_device_test.cpp +++ b/cpp/test/primary_device_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -17,33 +17,28 @@ using namespace scsi_defs; using namespace scsi_command_util; +pair, shared_ptr> CreatePrimaryDevice(int id = 0) +{ + auto controller = make_shared>(id); + auto device = make_shared(0); + EXPECT_TRUE(device->Init({})); + EXPECT_TRUE(controller->AddDevice(device)); + + return { controller, device }; +} + TEST(PrimaryDeviceTest, GetId) { const int ID = 5; - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, ID); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); + auto [controller, device] = CreatePrimaryDevice(ID); - EXPECT_EQ(-1, device->GetId()) << "Device ID cannot be known without assignment to a controller"; - - controller->AddDevice(device); EXPECT_EQ(ID, device->GetId()); } TEST(PrimaryDeviceTest, PhaseChange) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); - - controller->AddDevice(device); + auto [controller, device] = CreatePrimaryDevice(); EXPECT_CALL(*controller, Status); device->EnterStatusPhase(); @@ -57,14 +52,7 @@ TEST(PrimaryDeviceTest, PhaseChange) TEST(PrimaryDeviceTest, Reset) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); - - controller->AddDevice(device); + auto [controller, device] = CreatePrimaryDevice(); device->Dispatch(scsi_command::eCmdReserve6); EXPECT_FALSE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false)) @@ -76,14 +64,7 @@ TEST(PrimaryDeviceTest, Reset) TEST(PrimaryDeviceTest, CheckReservation) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); - - controller->AddDevice(device); + auto [controller, device] = CreatePrimaryDevice(); EXPECT_TRUE(device->CheckReservation(0, scsi_command::eCmdTestUnitReady, false)) << "Device must not be reserved for initiator ID 0"; @@ -110,14 +91,7 @@ TEST(PrimaryDeviceTest, CheckReservation) TEST(PrimaryDeviceTest, ReserveReleaseUnit) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); - - controller->AddDevice(device); + auto [controller, device] = CreatePrimaryDevice(); device->Dispatch(scsi_command::eCmdReserve6); EXPECT_FALSE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false)) @@ -139,14 +113,7 @@ TEST(PrimaryDeviceTest, ReserveReleaseUnit) TEST(PrimaryDeviceTest, DiscardReservation) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); - - controller->AddDevice(device); + auto [controller, device] = CreatePrimaryDevice(); device->Dispatch(scsi_command::eCmdReserve6); EXPECT_FALSE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false)) @@ -158,73 +125,61 @@ TEST(PrimaryDeviceTest, DiscardReservation) TEST(PrimaryDeviceTest, TestUnitReady) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); - - controller->AddDevice(device); + auto [controller, device] = CreatePrimaryDevice(); + // Required by the bullseye clang++ compiler + auto d = device; device->SetReset(true); device->SetAttn(true); device->SetReady(false); EXPECT_CALL(*controller, DataIn).Times(0); - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::UNIT_ATTENTION), - Property(&scsi_exception::get_asc, asc::POWER_ON_OR_RESET)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::unit_attention), + Property(&scsi_exception::get_asc, asc::power_on_or_reset)))); device->SetReset(false); EXPECT_CALL(*controller, DataIn).Times(0); - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::UNIT_ATTENTION), - Property(&scsi_exception::get_asc, asc::NOT_READY_TO_READY_CHANGE)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::unit_attention), + Property(&scsi_exception::get_asc, asc::not_ready_to_ready_change)))); device->SetReset(true); device->SetAttn(false); EXPECT_CALL(*controller, DataIn).Times(0); - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::UNIT_ATTENTION), - Property(&scsi_exception::get_asc, asc::POWER_ON_OR_RESET)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::unit_attention), + Property(&scsi_exception::get_asc, asc::power_on_or_reset)))); device->SetReset(false); device->SetAttn(true); EXPECT_CALL(*controller, DataIn).Times(0); - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::UNIT_ATTENTION), - Property(&scsi_exception::get_asc, asc::NOT_READY_TO_READY_CHANGE)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::unit_attention), + Property(&scsi_exception::get_asc, asc::not_ready_to_ready_change)))); device->SetAttn(false); EXPECT_CALL(*controller, DataIn).Times(0); - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))); device->SetReady(true); EXPECT_CALL(*controller, Status); device->Dispatch(scsi_command::eCmdTestUnitReady); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(PrimaryDeviceTest, Inquiry) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); - - controller->AddDevice(device); - - auto& cmd = controller->GetCmd(); + auto [controller, device] = CreatePrimaryDevice(); + // Required by the bullseye clang++ compiler + auto d = device; // ALLOCATION LENGTH - cmd[4] = 255; + controller->SetCmdByte(4, 255); - ON_CALL(*device, InquiryInternal()).WillByDefault([&device]() { - return device->HandleInquiry(device_type::PROCESSOR, scsi_level::SPC_3, false); + ON_CALL(*d, InquiryInternal()).WillByDefault([&d]() { + return d->HandleInquiry(device_type::processor, scsi_level::spc_3, false); }); EXPECT_CALL(*device, InquiryInternal); EXPECT_CALL(*controller, DataIn); @@ -237,42 +192,42 @@ TEST(PrimaryDeviceTest, Inquiry) EXPECT_CALL(*device, InquiryInternal); EXPECT_CALL(*controller, DataIn); device->Dispatch(scsi_command::eCmdInquiry); - EXPECT_EQ(device_type::PROCESSOR, (device_type)controller->GetBuffer()[0]); + EXPECT_EQ(device_type::processor, (device_type)controller->GetBuffer()[0]); EXPECT_EQ(0x00, controller->GetBuffer()[1]) << "Device was not reported as non-removable"; - EXPECT_EQ(scsi_level::SPC_3, (scsi_level)controller->GetBuffer()[2]) << "Wrong SCSI level"; - EXPECT_EQ(scsi_level::SCSI_2, (scsi_level)controller->GetBuffer()[3]) << "Wrong response level"; + EXPECT_EQ(scsi_level::spc_3, (scsi_level)controller->GetBuffer()[2]) << "Wrong SCSI level"; + EXPECT_EQ(scsi_level::scsi_2, (scsi_level)controller->GetBuffer()[3]) << "Wrong response level"; EXPECT_EQ(0x1f, controller->GetBuffer()[4]) << "Wrong additional data size"; - ON_CALL(*device, InquiryInternal()).WillByDefault([&device]() { - return device->HandleInquiry(device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, true); + ON_CALL(*device, InquiryInternal()).WillByDefault([&d]() { + return d->HandleInquiry(device_type::direct_access, scsi_level::scsi_1_ccs, true); }); EXPECT_CALL(*device, InquiryInternal); EXPECT_CALL(*controller, DataIn); device->Dispatch(scsi_command::eCmdInquiry); - EXPECT_EQ(device_type::DIRECT_ACCESS, (device_type)controller->GetBuffer()[0]); + EXPECT_EQ(device_type::direct_access, (device_type)controller->GetBuffer()[0]); EXPECT_EQ(0x80, controller->GetBuffer()[1]) << "Device was not reported as removable"; - EXPECT_EQ(scsi_level::SCSI_1_CCS, (scsi_level)controller->GetBuffer()[2]) << "Wrong SCSI level"; - EXPECT_EQ(scsi_level::SCSI_1_CCS, (scsi_level)controller->GetBuffer()[3]) << "Wrong response level"; + EXPECT_EQ(scsi_level::scsi_1_ccs, (scsi_level)controller->GetBuffer()[2]) << "Wrong SCSI level"; + EXPECT_EQ(scsi_level::scsi_1_ccs, (scsi_level)controller->GetBuffer()[3]) << "Wrong response level"; EXPECT_EQ(0x1f, controller->GetBuffer()[4]) << "Wrong additional data size"; - cmd[1] = 0x01; + controller->SetCmdByte(1, 0x01); EXPECT_CALL(*controller, DataIn).Times(0); - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdInquiry); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdInquiry); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "EVPD bit is not supported"; - cmd[2] = 0x01; + controller->SetCmdByte(2, 0x01); EXPECT_CALL(*controller, DataIn).Times(0); - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdInquiry); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + EXPECT_THAT([&d] { d->Dispatch(scsi_command::eCmdInquiry); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "PAGE CODE field is not supported"; - cmd[1] = 0x00; - cmd[2] = 0x00; + controller->SetCmdByte(1, 0); + controller->SetCmdByte(2, 0); // ALLOCATION LENGTH - cmd[4] = 1; + controller->SetCmdByte(4, 1); EXPECT_CALL(*device, InquiryInternal); EXPECT_CALL(*controller, DataIn); device->Dispatch(scsi_command::eCmdInquiry); @@ -282,64 +237,51 @@ TEST(PrimaryDeviceTest, Inquiry) TEST(PrimaryDeviceTest, RequestSense) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); + auto [controller, device] = CreatePrimaryDevice(); + // Required by the bullseye clang++ compiler + auto d = device; - controller->AddDevice(device); - - auto& cmd = controller->GetCmd(); // ALLOCATION LENGTH - cmd[4] = 255; + controller->SetCmdByte(4, 255); device->SetReady(false); - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdRequestSense); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdRequestSense); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))); device->SetReady(true); EXPECT_CALL(*controller, DataIn); device->Dispatch(scsi_command::eCmdRequestSense); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(PrimaryDeviceTest, SendDiagnostic) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); - - controller->AddDevice(device); - - auto& cmd = controller->GetCmd(); + auto [controller, device] = CreatePrimaryDevice(); + // Required by the bullseye clang++ compiler + auto d = device; EXPECT_CALL(*controller, Status); device->Dispatch(scsi_command::eCmdSendDiagnostic); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); - cmd[1] = 0x10; - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdSendDiagnostic); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(1, 0x10); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSendDiagnostic); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "SEND DIAGNOSTIC must fail because PF bit is not supported"; - cmd[1] = 0; + controller->SetCmdByte(1, 0); - cmd[3] = 1; - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdSendDiagnostic); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(3, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSendDiagnostic); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "SEND DIAGNOSTIC must fail because parameter list is not supported"; - cmd[3] = 0; - cmd[4] = 1; - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdSendDiagnostic); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(3, 0); + controller->SetCmdByte(4, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSendDiagnostic); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "SEND DIAGNOSTIC must fail because parameter list is not supported"; } @@ -348,23 +290,19 @@ TEST(PrimaryDeviceTest, ReportLuns) const int LUN1 = 1; const int LUN2 = 4; - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); + auto controller = make_shared(0); auto device1 = make_shared(LUN1); auto device2 = make_shared(LUN2); - const unordered_map params; - device1->Init(params); - device2->Init(params); + EXPECT_TRUE(device1->Init({})); + EXPECT_TRUE(device2->Init({})); controller->AddDevice(device1); EXPECT_TRUE(controller->HasDeviceForLun(LUN1)); controller->AddDevice(device2); EXPECT_TRUE(controller->HasDeviceForLun(LUN2)); - auto& cmd = controller->GetCmd(); // ALLOCATION LENGTH - cmd[9] = 255; + controller->SetCmdByte(9, 255); EXPECT_CALL(*controller, DataIn); device1->Dispatch(scsi_command::eCmdReportLuns); @@ -380,33 +318,25 @@ TEST(PrimaryDeviceTest, ReportLuns) EXPECT_EQ(0, GetInt16(buffer, 20)) << "Wrong LUN2 number"; EXPECT_EQ(LUN2, GetInt16(buffer, 22)) << "Wrong LUN2 number"; - cmd[2] = 0x01; + controller->SetCmdByte(2, 0x01); EXPECT_THAT([&] { device1->Dispatch(scsi_command::eCmdReportLuns); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Only SELECT REPORT mode 0 is supported"; } TEST(PrimaryDeviceTest, Dispatch) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); - - controller->AddDevice(device); + auto [controller, device] = CreatePrimaryDevice(); EXPECT_THROW(device->Dispatch(static_cast(0x1f)), scsi_exception) << "Unknown command"; } TEST(PrimaryDeviceTest, WriteByteSequence) { - vector data; - MockPrimaryDevice device(0); + auto [controller, device] = CreatePrimaryDevice(); - EXPECT_FALSE(device.WriteByteSequence(data, 0)) << "Primary device does not support writing byte sequences"; + EXPECT_FALSE(device->WriteByteSequence({})) << "Primary device does not support writing byte sequences"; } TEST(PrimaryDeviceTest, GetSetSendDelay) @@ -420,16 +350,8 @@ TEST(PrimaryDeviceTest, GetSetSendDelay) TEST(PrimaryDeviceTest, Init) { - unordered_map params; + param_map params; MockPrimaryDevice device(0); EXPECT_TRUE(device.Init(params)) << "Initialization of primary device must not fail"; } - -TEST(PrimaryDeviceTest, FlushCache) -{ - MockPrimaryDevice device(0); - - // Method must be present - device.FlushCache(); -} diff --git a/cpp/test/protobuf_serializer_test.cpp b/cpp/test/protobuf_serializer_test.cpp deleted file mode 100644 index 6cc4e671..00000000 --- a/cpp/test/protobuf_serializer_test.cpp +++ /dev/null @@ -1,98 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Copyright (C) 2022 Uwe Seimet -// -//--------------------------------------------------------------------------- - -#include "mocks.h" -#include "shared/protobuf_serializer.h" -#include "shared/piscsi_exceptions.h" -#include "generated/piscsi_interface.pb.h" -#include - -using namespace filesystem; -using namespace piscsi_interface; - -TEST(ProtobufSerializerTest, SerializeMessage) -{ - PbResult result; - ProtobufSerializer serializer; - - const int fd = open("/dev/null", O_WRONLY); - EXPECT_NE(-1, fd); - serializer.SerializeMessage(fd, result); - EXPECT_THROW(serializer.SerializeMessage(-1, result), io_exception) << "Writing a message must fail"; - close(fd); -} - -TEST(ProtobufSerializerTest, DeserializeMessage) -{ - PbResult result; - ProtobufSerializer serializer; - vector buf(1); - - int fd = open("/dev/null", O_RDONLY); - EXPECT_NE(-1, fd); - EXPECT_THROW(serializer.DeserializeMessage(fd, result), io_exception) << "Reading the message header must fail"; - close(fd); - - auto [fd1, filename1] = OpenTempFile(); - // Data size -1 - buf = { byte{0xff}, byte{0xff}, byte{0xff}, byte{0xff} }; - EXPECT_EQ(buf.size(), write(fd1, buf.data(), buf.size())); - close(fd1); - fd1 = open(filename1.c_str(), O_RDONLY); - EXPECT_NE(-1, fd1); - EXPECT_THROW(serializer.DeserializeMessage(fd1, result), io_exception) << "Invalid header was not rejected"; - remove(filename1); - - auto [fd2, filename2] = OpenTempFile(); - // Data size 2 - buf = { byte{0x02}, byte{0x00}, byte{0x00}, byte{0x00} }; - EXPECT_EQ(buf.size(), write(fd2, buf.data(), buf.size())); - close(fd2); - fd2 = open(filename2.c_str(), O_RDONLY); - EXPECT_NE(-1, fd2); - EXPECT_THROW(serializer.DeserializeMessage(fd2, result), io_exception) << "Invalid data were not rejected"; - remove(filename2); -} - -TEST(ProtobufSerializerTest, SerializeDeserializeMessage) -{ - PbResult result; - result.set_status(true); - ProtobufSerializer serializer; - - auto [fd, filename] = OpenTempFile(); - EXPECT_NE(-1, fd); - serializer.SerializeMessage(fd, result); - close(fd); - - result.set_status(false); - fd = open(filename.c_str(), O_RDONLY); - EXPECT_NE(-1, fd); - serializer.DeserializeMessage(fd, result); - close(fd); - remove(filename); - - EXPECT_TRUE(result.status()); -} - -TEST(ProtobufSerializerTest, ReadBytes) -{ - ProtobufSerializer serializer; - vector buf(1); - - int fd = open("/dev/null", O_RDONLY); - EXPECT_NE(-1, fd); - EXPECT_EQ(0, serializer.ReadBytes(fd, buf)); - close(fd); - - fd = open("/dev/zero", O_RDONLY); - EXPECT_NE(-1, fd); - EXPECT_EQ(1, serializer.ReadBytes(fd, buf)); - close(fd); -} diff --git a/cpp/test/protobuf_util_test.cpp b/cpp/test/protobuf_util_test.cpp index afd71148..c6c9d4d8 100644 --- a/cpp/test/protobuf_util_test.cpp +++ b/cpp/test/protobuf_util_test.cpp @@ -3,13 +3,15 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "mocks.h" #include "shared/protobuf_util.h" +#include "shared/piscsi_exceptions.h" #include "generated/piscsi_interface.pb.h" +#include using namespace piscsi_interface; using namespace protobuf_util; @@ -22,22 +24,14 @@ void TestSpecialDevice(const string& name) EXPECT_EQ("", GetParam(device, "interfaces")); } -TEST(ProtobufUtil, AddGetParam) +TEST(ProtobufUtil, GetSetParam) { + // The implementation is a function template, testing one possible T is sufficient PbCommand command; SetParam(command, "key", "value"); EXPECT_EQ("value", GetParam(command, "key")); EXPECT_EQ("", GetParam(command, "xyz")); - - PbDeviceDefinition definition; - SetParam(definition, "key", "value"); - EXPECT_EQ("value", GetParam(definition, "key")); - EXPECT_EQ("", GetParam(definition, "xyz")); - - PbDevice device; - SetParam(device, "key", "value"); - const auto& it = device.params().find("key"); - EXPECT_EQ("value", it->second); + EXPECT_EQ("", GetParam(command, "")); } TEST(ProtobufUtil, ParseParameters) @@ -59,32 +53,57 @@ TEST(ProtobufUtil, ParseParameters) TestSpecialDevice("services"); } -TEST(ProtobufUtil, SetPatternParams) +TEST(ProtobufUtil, SetCommandParams) { PbCommand command1; - SetPatternParams(command1, "file"); + SetCommandParams(command1, "file"); EXPECT_EQ("", GetParam(command1, "folder_pattern")); EXPECT_EQ("file", GetParam(command1, "file_pattern")); PbCommand command2; - SetPatternParams(command2, ":file"); + SetCommandParams(command2, ":file"); EXPECT_EQ("", GetParam(command2, "folder_pattern")); EXPECT_EQ("file", GetParam(command2, "file_pattern")); PbCommand command3; - SetPatternParams(command3, "folder:"); - EXPECT_EQ("folder", GetParam(command3, "folder_pattern")); - EXPECT_EQ("", GetParam(command3, "file_pattern")); + SetCommandParams(command3, "file:"); + EXPECT_EQ("file", GetParam(command3, "file_pattern")); + EXPECT_EQ("", GetParam(command3, "folder_pattern")); PbCommand command4; - SetPatternParams(command4, "folder:file"); + SetCommandParams(command4, "folder:file"); EXPECT_EQ("folder", GetParam(command4, "folder_pattern")); EXPECT_EQ("file", GetParam(command4, "file_pattern")); + + PbCommand command5; + SetCommandParams(command5, "folder:file:"); + EXPECT_EQ("folder", GetParam(command5, "folder_pattern")); + EXPECT_EQ("file", GetParam(command5, "file_pattern")); + + PbCommand command6; + SetCommandParams(command6, "folder:file:operations"); + EXPECT_EQ("folder", GetParam(command6, "folder_pattern")); + EXPECT_EQ("file", GetParam(command6, "file_pattern")); + EXPECT_EQ("operations", GetParam(command6, "operations")); +} + +TEST(ProtobufUtil, SetFromGenericParams) +{ + PbCommand command1; + EXPECT_TRUE(SetFromGenericParams(command1, "operations=mapping_info:folder_pattern=pattern").empty()); + EXPECT_EQ("mapping_info", GetParam(command1, "operations")); + EXPECT_EQ("pattern", GetParam(command1, "folder_pattern")); + + PbCommand command2; + EXPECT_FALSE(SetFromGenericParams(command2, "=mapping_info").empty()); + + PbCommand command3; + EXPECT_FALSE(SetFromGenericParams(command3, "=").empty()); } TEST(ProtobufUtil, ListDevices) { - list devices; + vector devices; EXPECT_FALSE(ListDevices(devices).empty()); @@ -136,10 +155,90 @@ TEST(ProtobufUtil, SetIdAndLun) { PbDeviceDefinition device; - EXPECT_NE("", SetIdAndLun(device, "", 32)); - EXPECT_EQ("", SetIdAndLun(device, "1", 32)); + EXPECT_NE("", SetIdAndLun(device, "")); + EXPECT_EQ("", SetIdAndLun(device, "1")); EXPECT_EQ(1, device.id()); - EXPECT_EQ("", SetIdAndLun(device, "2:0", 32)); + EXPECT_EQ("", SetIdAndLun(device, "2:0")); EXPECT_EQ(2, device.id()); EXPECT_EQ(0, device.unit()); } + +TEST(ProtobufUtil, SerializeMessage) +{ + PbResult result; + + const int fd = open("/dev/null", O_WRONLY); + ASSERT_NE(-1, fd); + SerializeMessage(fd, result); + close(fd); + EXPECT_THROW(SerializeMessage(-1, result), io_exception) << "Writing a message must fail"; +} + +TEST(ProtobufUtil, DeserializeMessage) +{ + PbResult result; + vector buf(1); + + int fd = open("/dev/null", O_RDONLY); + ASSERT_NE(-1, fd); + EXPECT_THROW(DeserializeMessage(fd, result), io_exception) << "Reading the message header must fail"; + close(fd); + + auto [fd1, filename1] = OpenTempFile(); + // Data size -1 + buf = { byte{0xff}, byte{0xff}, byte{0xff}, byte{0xff} }; + EXPECT_EQ(buf.size(), write(fd1, buf.data(), buf.size())); + close(fd1); + fd1 = open(filename1.c_str(), O_RDONLY); + ASSERT_NE(-1, fd1); + EXPECT_THROW(DeserializeMessage(fd1, result), io_exception) << "Invalid header was not rejected"; + remove(filename1); + + auto [fd2, filename2] = OpenTempFile(); + // Data size 2 + buf = { byte{0x02}, byte{0x00}, byte{0x00}, byte{0x00} }; + EXPECT_EQ(buf.size(), write(fd2, buf.data(), buf.size())); + close(fd2); + fd2 = open(filename2.c_str(), O_RDONLY); + EXPECT_NE(-1, fd2); + EXPECT_THROW(DeserializeMessage(fd2, result), io_exception) << "Invalid data were not rejected"; + remove(filename2); +} + +TEST(ProtobufUtil, SerializeDeserializeMessage) +{ + PbResult result; + result.set_status(true); + + auto [fd, filename] = OpenTempFile(); + ASSERT_NE(-1, fd); + SerializeMessage(fd, result); + close(fd); + + result.set_status(false); + fd = open(filename.c_str(), O_RDONLY); + ASSERT_NE(-1, fd); + DeserializeMessage(fd, result); + close(fd); + remove(filename); + + EXPECT_TRUE(result.status()); +} + +TEST(ProtobufUtil, ReadBytes) +{ + vector buf1(1); + vector buf2; + + int fd = open("/dev/null", O_RDONLY); + ASSERT_NE(-1, fd); + EXPECT_EQ(0, ReadBytes(fd, buf1)); + EXPECT_EQ(0, ReadBytes(fd, buf2)); + close(fd); + + fd = open("/dev/zero", O_RDONLY); + ASSERT_NE(-1, fd); + EXPECT_EQ(1, ReadBytes(fd, buf1)); + EXPECT_EQ(0, ReadBytes(fd, buf2)); + close(fd); +} diff --git a/cpp/test/scsi_command_util_test.cpp b/cpp/test/scsi_command_util_test.cpp index dbfdffa7..20afa389 100644 --- a/cpp/test/scsi_command_util_test.cpp +++ b/cpp/test/scsi_command_util_test.cpp @@ -10,7 +10,6 @@ #include "mocks.h" #include "shared/scsi.h" #include "shared/piscsi_exceptions.h" -#include "devices/device_logger.h" #include "devices/scsi_command_util.h" using namespace scsi_command_util; @@ -18,14 +17,13 @@ using namespace scsi_command_util; TEST(ScsiCommandUtilTest, ModeSelect6) { const int LENGTH = 26; - DeviceLogger logger; vector cdb(6); vector buf(LENGTH); // PF (vendor-specific parameter format) must not fail but be ignored cdb[1] = 0x00; - ModeSelect(logger, scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 0); + ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 0); cdb[0] = 0x15; // PF (standard parameter format) @@ -34,50 +32,49 @@ TEST(ScsiCommandUtilTest, ModeSelect6) buf[9] = 0x00; buf[10] = 0x02; buf[11] = 0x00; - EXPECT_THAT([&] { ModeSelect(logger, scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 256); }, + EXPECT_THAT([&] { ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 256); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_PARAMETER_LIST)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list)))) << "Requested sector size does not match current sector size"; // Page 0 buf[12] = 0x00; - EXPECT_THAT([&] { ModeSelect(logger, scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512); }, + EXPECT_THAT([&] { ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_PARAMETER_LIST)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list)))) << "Unsupported page 0 was not rejected"; // Page 3 (Format Device Page) buf[12] = 0x03; - EXPECT_THAT([&] { ModeSelect(logger, scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512); }, + EXPECT_THAT([&] { ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_PARAMETER_LIST)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list)))) << "Requested sector size does not match current sector size"; // Match the requested to the current sector size buf[24] = 0x02; - EXPECT_THAT([&] { ModeSelect(logger, scsi_command::eCmdModeSelect6, cdb, buf, LENGTH - 1, 512); }, + EXPECT_THAT([&] { ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH - 1, 512); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_PARAMETER_LIST)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list)))) << "Not enough command parameters"; - ModeSelect(logger, scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512); + EXPECT_FALSE(ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512).empty()); } TEST(ScsiCommandUtilTest, ModeSelect10) { const int LENGTH = 30; - DeviceLogger logger; vector cdb(10); vector buf(LENGTH); // PF (vendor-specific parameter format) must not fail but be ignored cdb[1] = 0x00; - ModeSelect(logger, scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 0); + ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 0); // PF (standard parameter format) cdb[1] = 0x10; @@ -85,37 +82,37 @@ TEST(ScsiCommandUtilTest, ModeSelect10) buf[13] = 0x00; buf[14] = 0x02; buf[15] = 0x00; - EXPECT_THAT([&] { ModeSelect(logger, scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 256); }, + EXPECT_THAT([&] { ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 256); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_PARAMETER_LIST)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list)))) << "Requested sector size does not match current sector size"; // Page 0 buf[16] = 0x00; - EXPECT_THAT([&] { ModeSelect(logger, scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 512); }, + EXPECT_THAT([&] { ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 512); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_PARAMETER_LIST)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list)))) << "Unsupported page 0 was not rejected"; // Page 3 (Format Device Page) buf[16] = 0x03; - EXPECT_THAT([&] { ModeSelect(logger, scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 512); }, + EXPECT_THAT([&] { ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 512); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_PARAMETER_LIST)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list)))) << "Requested sector size does not match current sector size"; // Match the requested to the current sector size buf[28] = 0x02; - EXPECT_THAT([&] { ModeSelect(logger, scsi_command::eCmdModeSelect10, cdb, buf, LENGTH - 1, 512); }, + EXPECT_THAT([&] { ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH - 1, 512); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_PARAMETER_LIST)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list)))) << "Not enough command parameters"; - ModeSelect(logger, scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 512); + EXPECT_FALSE(ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 512).empty()); } TEST(ScsiCommandUtilTest, EnrichFormatPage) diff --git a/cpp/test/scsi_controller_test.cpp b/cpp/test/scsi_controller_test.cpp index 60a2623f..ff726479 100644 --- a/cpp/test/scsi_controller_test.cpp +++ b/cpp/test/scsi_controller_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -19,8 +19,7 @@ TEST(ScsiControllerTest, GetInitiatorId) const int ID = 2; auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - MockScsiController controller(controller_manager, 0); + MockScsiController controller(bus, 0); controller.Process(ID); EXPECT_EQ(ID, controller.GetInitiatorId()); @@ -31,46 +30,43 @@ TEST(ScsiControllerTest, GetInitiatorId) TEST(ScsiControllerTest, Process) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - MockScsiController controller(controller_manager, 0); + MockScsiController controller(bus, 0); + controller.Init(); controller.SetPhase(phase_t::reserved); ON_CALL(*bus, GetRST).WillByDefault(Return(true)); EXPECT_CALL(*bus, Acquire); EXPECT_CALL(*bus, GetRST); - EXPECT_CALL(*bus, Reset); EXPECT_CALL(controller, Reset); - EXPECT_EQ(phase_t::reserved, controller.Process(0)); + EXPECT_FALSE(controller.Process(0)); controller.SetPhase(phase_t::busfree); ON_CALL(*bus, GetRST).WillByDefault(Return(false)); EXPECT_CALL(*bus, Acquire); EXPECT_CALL(*bus, GetRST); - EXPECT_EQ(phase_t::busfree, controller.Process(0)); + EXPECT_FALSE(controller.Process(0)); controller.SetPhase(phase_t::reserved); EXPECT_CALL(*bus, Acquire); EXPECT_CALL(*bus, GetRST); - EXPECT_CALL(*bus, Reset); EXPECT_CALL(controller, Reset); - EXPECT_EQ(phase_t::busfree, controller.Process(0)); + EXPECT_FALSE(controller.Process(0)); } TEST(ScsiControllerTest, BusFree) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - MockScsiController controller(controller_manager, 0); + MockScsiController controller(bus, 0); controller.SetPhase(phase_t::busfree); controller.BusFree(); EXPECT_EQ(phase_t::busfree, controller.GetPhase()); - controller.SetStatus(status::CHECK_CONDITION); + controller.SetStatus(status::check_condition); controller.SetPhase(phase_t::reserved); controller.BusFree(); EXPECT_EQ(phase_t::busfree, controller.GetPhase()); - EXPECT_EQ(status::GOOD, controller.GetStatus()); + EXPECT_EQ(status::good, controller.GetStatus()); controller.ScheduleShutdown(AbstractController::piscsi_shutdown_mode::NONE); controller.SetPhase(phase_t::reserved); @@ -86,14 +82,13 @@ TEST(ScsiControllerTest, BusFree) controller.ScheduleShutdown(AbstractController::piscsi_shutdown_mode::STOP_PISCSI); controller.SetPhase(phase_t::reserved); - EXPECT_EXIT(controller.BusFree(), ExitedWithCode(EXIT_SUCCESS), ""); + controller.BusFree(); } TEST(ScsiControllerTest, Selection) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); + auto controller = make_shared(bus, 0); controller->SetPhase(phase_t::selection); ON_CALL(*bus, GetSEL).WillByDefault(Return(true)); @@ -130,17 +125,7 @@ TEST(ScsiControllerTest, Selection) controller->Selection(); EXPECT_EQ(phase_t::msgout, controller->GetPhase()); - controller->SetPhase(phase_t::reserved); - ON_CALL(*bus, GetDAT).WillByDefault(Return(0)); - controller->Selection(); - EXPECT_EQ(phase_t::reserved, controller->GetPhase()); - ON_CALL(*bus, GetDAT).WillByDefault(Return(1)); - controller->Selection(); - EXPECT_EQ(phase_t::reserved, controller->GetPhase()) << "There is no device that can be selected"; - - auto device = make_shared(0); - controller->AddDevice(device); EXPECT_CALL(*bus, SetBSY(true)); controller->Selection(); EXPECT_EQ(phase_t::selection, controller->GetPhase()); @@ -149,8 +134,7 @@ TEST(ScsiControllerTest, Selection) TEST(ScsiControllerTest, Command) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - MockScsiController controller(controller_manager, 0); + MockScsiController controller(bus, 0); controller.SetPhase(phase_t::command); EXPECT_CALL(controller, Status); @@ -177,8 +161,7 @@ TEST(ScsiControllerTest, Command) TEST(ScsiControllerTest, MsgIn) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - MockScsiController controller(controller_manager, 0); + MockScsiController controller(bus, 0); controller.SetPhase(phase_t::reserved); EXPECT_CALL(*bus, SetMSG(true)); @@ -193,8 +176,7 @@ TEST(ScsiControllerTest, MsgIn) TEST(ScsiControllerTest, MsgOut) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - MockScsiController controller(controller_manager, 0); + MockScsiController controller(bus, 0); controller.SetPhase(phase_t::reserved); EXPECT_CALL(*bus, SetMSG(true)); @@ -209,8 +191,7 @@ TEST(ScsiControllerTest, MsgOut) TEST(ScsiControllerTest, DataIn) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - MockScsiController controller(controller_manager, 0); + MockScsiController controller(bus, 0); controller.SetPhase(phase_t::reserved); controller.SetLength(0); @@ -230,8 +211,7 @@ TEST(ScsiControllerTest, DataIn) TEST(ScsiControllerTest, DataOut) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - MockScsiController controller(controller_manager, 0); + MockScsiController controller(bus, 0); controller.SetPhase(phase_t::reserved); controller.SetLength(0); @@ -251,66 +231,58 @@ TEST(ScsiControllerTest, DataOut) TEST(ScsiControllerTest, Error) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - MockScsiController controller(controller_manager, 0); + MockScsiController controller(bus, 0); ON_CALL(*bus, GetRST).WillByDefault(Return(true)); controller.SetPhase(phase_t::reserved); EXPECT_CALL(*bus, Acquire); EXPECT_CALL(*bus, GetRST()); - EXPECT_CALL(*bus, Reset); EXPECT_CALL(controller, Reset); - controller.Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT); - EXPECT_EQ(status::GOOD, controller.GetStatus()); + controller.Error(sense_key::aborted_command, asc::no_additional_sense_information, status::reservation_conflict); + EXPECT_EQ(status::good, controller.GetStatus()); EXPECT_EQ(phase_t::reserved, controller.GetPhase()); ON_CALL(*bus, GetRST).WillByDefault(Return(false)); controller.SetPhase(phase_t::status); EXPECT_CALL(*bus, Acquire); EXPECT_CALL(*bus, GetRST()); - EXPECT_CALL(*bus, Reset).Times(0); EXPECT_CALL(controller, Reset).Times(0); - controller.Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT); + controller.Error(sense_key::aborted_command, asc::no_additional_sense_information, status::reservation_conflict); EXPECT_EQ(phase_t::busfree, controller.GetPhase()); controller.SetPhase(phase_t::msgin); EXPECT_CALL(*bus, Acquire); EXPECT_CALL(*bus, GetRST()); - EXPECT_CALL(*bus, Reset).Times(0); EXPECT_CALL(controller, Reset).Times(0); - controller.Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT); + controller.Error(sense_key::aborted_command, asc::no_additional_sense_information, status::reservation_conflict); EXPECT_EQ(phase_t::busfree, controller.GetPhase()); controller.SetPhase(phase_t::reserved); EXPECT_CALL(*bus, Acquire); EXPECT_CALL(*bus, GetRST()); - EXPECT_CALL(*bus, Reset).Times(0); EXPECT_CALL(controller, Reset).Times(0); EXPECT_CALL(controller, Status); - controller.Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT); - EXPECT_EQ(status::RESERVATION_CONFLICT, controller.GetStatus()); + controller.Error(sense_key::aborted_command, asc::no_additional_sense_information, status::reservation_conflict); + EXPECT_EQ(status::reservation_conflict, controller.GetStatus()); EXPECT_EQ(phase_t::reserved, controller.GetPhase()); } TEST(ScsiControllerTest, RequestSense) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); + auto controller = make_shared(bus, 0); auto device = make_shared(0); - const unordered_map params; - device->Init(params); + EXPECT_TRUE(device->Init({})); controller->AddDevice(device); - auto& cmd = controller->GetCmd(); // ALLOCATION LENGTH - cmd[4] = 255; + controller->SetCmdByte(4, 255); // Non-existing LUN - cmd[1] = 0x20; + controller->SetCmdByte(1, 0x20); device->SetReady(true); EXPECT_CALL(*controller, Status); device->Dispatch(scsi_command::eCmdRequestSense); - EXPECT_EQ(status::GOOD, controller->GetStatus()) << "Wrong CHECK CONDITION for non-existing LUN"; + EXPECT_EQ(status::good, controller->GetStatus()) << "Wrong CHECK CONDITION for non-existing LUN"; } diff --git a/cpp/test/scsi_daynaport_test.cpp b/cpp/test/scsi_daynaport_test.cpp index 122ff69b..712b3b38 100644 --- a/cpp/test/scsi_daynaport_test.cpp +++ b/cpp/test/scsi_daynaport_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -11,204 +11,177 @@ #include "shared/piscsi_exceptions.h" #include "devices/scsi_daynaport.h" +TEST(ScsiDaynaportTest, GetDefaultParams) +{ + const auto [controller, daynaport] = CreateDevice(SCDP); + const auto params = daynaport->GetDefaultParams(); + EXPECT_EQ(2, params.size()); +} + TEST(ScsiDaynaportTest, Inquiry) { - TestInquiry(SCDP, device_type::PROCESSOR, scsi_level::SCSI_2, "Dayna SCSI/Link 1.4a", 0x20, false); + TestInquiry::Inquiry(SCDP, device_type::processor, scsi_level::scsi_2, "Dayna SCSI/Link 1.4a", 0x20, false); } TEST(ScsiDaynaportTest, TestUnitReady) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto daynaport = CreateDevice(SCDP, *controller); + auto [controller, daynaport] = CreateDevice(SCDP); EXPECT_CALL(*controller, Status()); daynaport->Dispatch(scsi_command::eCmdTestUnitReady); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(ScsiDaynaportTest, Read) { - vector buf(0); - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto daynaport = dynamic_pointer_cast(CreateDevice(SCDP, *controller)); - - auto& cmd = controller->GetCmd(); + auto [controller, daynaport] = CreateDevice(SCDP); // ALLOCATION LENGTH - cmd[4] = 1; - EXPECT_EQ(0, daynaport->Read(cmd, buf, 0)) << "Trying to read the root sector must fail"; + controller->SetCmdByte(4, 1); + vector buf(0); + EXPECT_EQ(0, dynamic_pointer_cast(daynaport)->Read(controller->GetCmd(), buf, 0)) << "Trying to read the root sector must fail"; } -TEST(ScsiDaynaportTest, WriteBytes) +TEST(ScsiDaynaportTest, Write) { - vector buf(0); - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto daynaport = dynamic_pointer_cast(CreateDevice(SCDP, *controller)); - - auto& cmd = controller->GetCmd(); + auto [controller, daynaport] = CreateDevice(SCDP); // Unknown data format - cmd[5] = 0xff; - EXPECT_TRUE(daynaport->WriteBytes(cmd, buf, 0)); + controller->SetCmdByte(5, 0xff); + vector buf(0); + EXPECT_TRUE(dynamic_pointer_cast(daynaport)->Write(controller->GetCmd(), buf)); } TEST(ScsiDaynaportTest, Read6) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto daynaport = CreateDevice(SCDP, *controller); + auto [controller, daynaport] = CreateDevice(SCDP); + // Required by the bullseye clang++ compiler + auto d = daynaport; - auto& cmd = controller->GetCmd(); - - cmd[5] = 0xff; - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdRead6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(5, 0xff); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdRead6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Invalid data format"; } TEST(ScsiDaynaportTest, Write6) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto daynaport = CreateDevice(SCDP, *controller); + auto [controller, daynaport] = CreateDevice(SCDP); + // Required by the bullseye clang++ compiler + auto d = daynaport; - auto& cmd = controller->GetCmd(); - - cmd[5] = 0x00; - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(5, 0x00); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Invalid transfer length"; - cmd[3] = -1; - cmd[4] = -8; - cmd[5] = 0x80; - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(3, -1); + controller->SetCmdByte(4, -8); + controller->SetCmdByte(5, 0x08); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Invalid transfer length"; - cmd[3] = 0; - cmd[4] = 0; - cmd[5] = 0xff; - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(3, 0); + controller->SetCmdByte(4, 0); + controller->SetCmdByte(5, 0xff); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Invalid transfer length"; } TEST(ScsiDaynaportTest, TestRetrieveStats) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto daynaport = CreateDevice(SCDP, *controller); - - auto& cmd = controller->GetCmd(); + auto [controller, daynaport] = CreateDevice(SCDP); // ALLOCATION LENGTH - cmd[4] = 255; + controller->SetCmdByte(4, 255); EXPECT_CALL(*controller, DataIn()); daynaport->Dispatch(scsi_command::eCmdRetrieveStats); } TEST(ScsiDaynaportTest, SetInterfaceMode) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto daynaport = CreateDevice(SCDP, *controller); - - auto& cmd = controller->GetCmd(); + auto [controller, daynaport] = CreateDevice(SCDP); + // Required by the bullseye clang++ compiler + auto d = daynaport; // Unknown interface command - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdSetIfaceMode); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_COMMAND_OPERATION_CODE)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSetIfaceMode); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_command_operation_code)))); // Not implemented, do nothing - cmd[5] = SCSIDaynaPort::CMD_SCSILINK_SETMODE; + controller->SetCmdByte(5, SCSIDaynaPort::CMD_SCSILINK_SETMODE); EXPECT_CALL(*controller, Status()); daynaport->Dispatch(scsi_command::eCmdSetIfaceMode); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); - cmd[5] = SCSIDaynaPort::CMD_SCSILINK_SETMAC; + controller->SetCmdByte(5, SCSIDaynaPort::CMD_SCSILINK_SETMAC); EXPECT_CALL(*controller, DataOut()); daynaport->Dispatch(scsi_command::eCmdSetIfaceMode); // Not implemented - cmd[5] = SCSIDaynaPort::CMD_SCSILINK_STATS; - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdSetIfaceMode); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_COMMAND_OPERATION_CODE)))); + controller->SetCmdByte(5, SCSIDaynaPort::CMD_SCSILINK_STATS); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSetIfaceMode); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_command_operation_code)))); // Not implemented - cmd[5] = SCSIDaynaPort::CMD_SCSILINK_ENABLE; - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdSetIfaceMode); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_COMMAND_OPERATION_CODE)))); + controller->SetCmdByte(5, SCSIDaynaPort::CMD_SCSILINK_ENABLE); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSetIfaceMode); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_command_operation_code)))); // Not implemented - cmd[5] = SCSIDaynaPort::CMD_SCSILINK_SET; - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdSetIfaceMode); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_COMMAND_OPERATION_CODE)))); + controller->SetCmdByte(5, SCSIDaynaPort::CMD_SCSILINK_SET); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSetIfaceMode); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_command_operation_code)))); } TEST(ScsiDaynaportTest, SetMcastAddr) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto daynaport = CreateDevice(SCDP, *controller); + auto [controller, daynaport] = CreateDevice(SCDP); + // Required by the bullseye clang++ compiler + auto d = daynaport; - auto& cmd = controller->GetCmd(); - - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdSetMcastAddr); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSetMcastAddr); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Length of 0 is not supported"; - cmd[4] = 1; + controller->SetCmdByte(4, 1); EXPECT_CALL(*controller, DataOut()); daynaport->Dispatch(scsi_command::eCmdSetMcastAddr); } TEST(ScsiDaynaportTest, EnableInterface) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto daynaport = CreateDevice(SCDP, *controller); - - auto& cmd = controller->GetCmd(); + auto [controller, daynaport] = CreateDevice(SCDP); + // Required by the bullseye clang++ compiler + auto d = daynaport; // Enable - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdEnableInterface); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ABORTED_COMMAND), - Property(&scsi_exception::get_asc, asc::NO_ADDITIONAL_SENSE_INFORMATION)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdEnableInterface); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::aborted_command), + Property(&scsi_exception::get_asc, asc::no_additional_sense_information)))); // Disable - cmd[5] = 0x80; - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdEnableInterface); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ABORTED_COMMAND), - Property(&scsi_exception::get_asc, asc::NO_ADDITIONAL_SENSE_INFORMATION)))); + controller->SetCmdByte(5, 0x00); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdEnableInterface); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::aborted_command), + Property(&scsi_exception::get_asc, asc::no_additional_sense_information)))); } TEST(ScsiDaynaportTest, GetSendDelay) { SCSIDaynaPort daynaport(0); - const unordered_map params; - daynaport.Init(params); + daynaport.Init({}); EXPECT_EQ(6, daynaport.GetSendDelay()); } diff --git a/cpp/test/scsi_host_bridge_test.cpp b/cpp/test/scsi_host_bridge_test.cpp index 42a584da..117ab519 100644 --- a/cpp/test/scsi_host_bridge_test.cpp +++ b/cpp/test/scsi_host_bridge_test.cpp @@ -3,13 +3,20 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "mocks.h" +TEST(ScsiHostBridgeTest, GetDefaultParams) +{ + const auto [controller, bridge] = CreateDevice(SCBR); + const auto params = bridge->GetDefaultParams(); + EXPECT_EQ(2, params.size()); +} + TEST(ScsiHostBridgeTest, Inquiry) { - TestInquiry(SCBR, device_type::COMMUNICATIONS, scsi_level::SCSI_2, "PiSCSI RASCSI BRIDGE ", 0x27, false); + TestInquiry::Inquiry(SCBR, device_type::communications, scsi_level::scsi_2, "PiSCSI RASCSI BRIDGE ", 0x27, false); } diff --git a/cpp/test/scsi_printer_test.cpp b/cpp/test/scsi_printer_test.cpp index 821d5c5a..36bfcd79 100644 --- a/cpp/test/scsi_printer_test.cpp +++ b/cpp/test/scsi_printer_test.cpp @@ -3,27 +3,29 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "mocks.h" #include "shared/piscsi_exceptions.h" -#include "controllers/controller_manager.h" #include "devices/scsi_printer.h" using namespace std; +TEST(ScsiPrinterTest, GetDefaultParams) +{ + const auto [controller, printer] = CreateDevice(SCLP); + const auto params = printer->GetDefaultParams(); + EXPECT_EQ(1, params.size()); +} + TEST(ScsiPrinterTest, Init) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto printer = CreateDevice(SCLP, *controller); - - unordered_map params; - EXPECT_TRUE(printer->Init(params)); + auto [controller, printer] = CreateDevice(SCLP); + EXPECT_TRUE(printer->Init({})); + param_map params; params["cmd"] = "missing_filename_specifier"; EXPECT_FALSE(printer->Init(params)); @@ -33,99 +35,80 @@ TEST(ScsiPrinterTest, Init) TEST(ScsiPrinterTest, TestUnitReady) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto printer = CreateDevice(SCLP, *controller); + auto [controller, printer] = CreateDevice(SCLP); EXPECT_CALL(*controller, Status()); printer->Dispatch(scsi_command::eCmdTestUnitReady); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(ScsiPrinterTest, Inquiry) { - TestInquiry(SCLP, device_type::PRINTER, scsi_level::SCSI_2, "PiSCSI SCSI PRINTER ", 0x1f, false); + TestInquiry::Inquiry(SCLP, device_type::printer, scsi_level::scsi_2, "PiSCSI SCSI PRINTER ", 0x1f, false); } TEST(ScsiPrinterTest, ReserveUnit) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto printer = CreateDevice(SCLP, *controller); + auto [controller, printer] = CreateDevice(SCLP); EXPECT_CALL(*controller, Status()).Times(1); printer->Dispatch(scsi_command::eCmdReserve6); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(ScsiPrinterTest, ReleaseUnit) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto printer = CreateDevice(SCLP, *controller); + auto [controller, printer] = CreateDevice(SCLP); EXPECT_CALL(*controller, Status()).Times(1); printer->Dispatch(scsi_command::eCmdRelease6); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(ScsiPrinterTest, SendDiagnostic) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto printer = CreateDevice(SCLP, *controller); + auto [controller, printer] = CreateDevice(SCLP); EXPECT_CALL(*controller, Status()).Times(1); printer->Dispatch(scsi_command::eCmdSendDiagnostic); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(ScsiPrinterTest, Print) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto printer = CreateDevice(SCLP, *controller); - - auto& cmd = controller->GetCmd(); + auto [controller, printer] = CreateDevice(SCLP); + // Required by the bullseye clang++ compiler + auto p = printer; EXPECT_CALL(*controller, DataOut()); printer->Dispatch(scsi_command::eCmdPrint); - cmd[3] = 0xff; - cmd[4] = 0xff; - EXPECT_THAT([&] { printer->Dispatch(scsi_command::eCmdPrint); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(3, 0xff); + controller->SetCmdByte(4, 0xff); + EXPECT_THAT([&] { p->Dispatch(scsi_command::eCmdPrint); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Buffer overflow was not reported"; } TEST(ScsiPrinterTest, StopPrint) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto printer = CreateDevice(SCLP, *controller); + auto [controller, printer] = CreateDevice(SCLP); EXPECT_CALL(*controller, Status()); printer->Dispatch(scsi_command::eCmdStopPrint); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(ScsiPrinterTest, SynchronizeBuffer) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto printer = CreateDevice(SCLP, *controller); + auto [controller, printer] = CreateDevice(SCLP); + // Required by the bullseye clang++ compiler + auto p = printer; - EXPECT_THAT([&] { printer->Dispatch(scsi_command::eCmdSynchronizeBuffer); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ABORTED_COMMAND), - Property(&scsi_exception::get_asc, asc::NO_ADDITIONAL_SENSE_INFORMATION)))) + EXPECT_THAT([&] { p->Dispatch(scsi_command::eCmdSynchronizeBuffer); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::aborted_command), + Property(&scsi_exception::get_asc, asc::no_additional_sense_information)))) << "Nothing to print"; // Further testing would use the printing system @@ -133,11 +116,8 @@ TEST(ScsiPrinterTest, SynchronizeBuffer) TEST(ScsiPrinterTest, WriteByteSequence) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto printer = CreateDevice(SCLP, *controller); + auto [controller, printer] = CreateDevice(SCLP); - vector buf(1); - EXPECT_TRUE(printer->WriteByteSequence(buf, buf.size())); + const vector buf(1); + EXPECT_TRUE(printer->WriteByteSequence(buf)); } diff --git a/cpp/test/scsicd_test.cpp b/cpp/test/scsicd_test.cpp index 925ad06d..fa5d8743 100644 --- a/cpp/test/scsicd_test.cpp +++ b/cpp/test/scsicd_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -29,16 +29,15 @@ void ScsiCdTest_SetUpModePages(map>& pages) TEST(ScsiCdTest, Inquiry) { - TestInquiry(SCCD, device_type::CD_ROM, scsi_level::SCSI_2, "PiSCSI SCSI CD-ROM ", 0x1f, true); + TestInquiry::Inquiry(SCCD, device_type::cd_rom, scsi_level::scsi_2, "PiSCSI SCSI CD-ROM ", 0x1f, true); - TestInquiry(SCCD, device_type::CD_ROM, scsi_level::SCSI_1_CCS, "PiSCSI SCSI CD-ROM ", 0x1f, true, ".is1"); + TestInquiry::Inquiry(SCCD, device_type::cd_rom, scsi_level::scsi_1_ccs, "PiSCSI SCSI CD-ROM ", 0x1f, true, "file.is1"); } TEST(ScsiCdTest, SetUpModePages) { map> pages; - const unordered_set sector_sizes; - MockSCSICD cd(0, sector_sizes); + MockSCSICD cd(0, {}); // Non changeable cd.SetUpModePages(pages, 0x3f, false); @@ -52,11 +51,10 @@ TEST(ScsiCdTest, SetUpModePages) TEST(ScsiCdTest, Open) { - const unordered_set sector_sizes; - MockSCSICD cd_iso(0, sector_sizes); - MockSCSICD cd_cue(0, sector_sizes); - MockSCSICD cd_raw(0, sector_sizes); - MockSCSICD cd_physical(0, sector_sizes); + MockSCSICD cd_iso(0, {}); + MockSCSICD cd_cue(0, {}); + MockSCSICD cd_raw(0, {}); + MockSCSICD cd_physical(0, {}); EXPECT_THROW(cd_iso.Open(), io_exception) << "Missing filename"; @@ -112,19 +110,16 @@ TEST(ScsiCdTest, Open) TEST(ScsiCdTest, ReadToc) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); + auto controller = make_shared(); const unordered_set sector_sizes; auto cd = make_shared(0, sector_sizes); - const unordered_map params; - cd->Init(params); + EXPECT_TRUE(cd->Init({})); controller->AddDevice(cd); EXPECT_THAT([&] { cd->Dispatch(scsi_command::eCmdReadToc); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))); + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))); // Further testing requires filesystem access } diff --git a/cpp/test/scsictl_commands_test.cpp b/cpp/test/scsictl_commands_test.cpp index b9394dd1..036f464c 100644 --- a/cpp/test/scsictl_commands_test.cpp +++ b/cpp/test/scsictl_commands_test.cpp @@ -92,8 +92,11 @@ TEST(ScsictlCommandsTest, Execute) command.set_operation(OPERATION_INFO); EXPECT_THROW(commands.Execute("", "", "", "", ""), io_exception); - command.set_operation(NO_OPERATION); + command.set_operation(DETACH_ALL); EXPECT_THROW(commands.Execute("", "", "", "", ""), io_exception); + + command.set_operation(NO_OPERATION); + EXPECT_FALSE(commands.Execute("", "", "", "", "")); } TEST(ScsictlCommandsTest, CommandDevicesInfo) diff --git a/cpp/test/scsictl_display_test.cpp b/cpp/test/scsictl_display_test.cpp index 4fd8f71c..e5c53269 100644 --- a/cpp/test/scsictl_display_test.cpp +++ b/cpp/test/scsictl_display_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // These tests only test key aspects of the expected output, because the output may change over time. // @@ -51,10 +51,9 @@ TEST(ScsictlDisplayTest, DisplayDeviceInfo) EXPECT_NE(string::npos, s.find(to_string(1234 *4321))); device.mutable_properties()->set_supports_file(true); - auto file = make_unique(); + auto file = device.mutable_file(); file->set_name("filename"); - device.set_allocated_file(file.release()); - s = display.DisplayDeviceInfo(device); //NOSONAR The allocated memory is managed by protobuf + s = display.DisplayDeviceInfo(device); EXPECT_FALSE(s.empty()); EXPECT_NE(string::npos, s.find("filename")); @@ -154,7 +153,7 @@ TEST(ScsictlDisplayTest, DisplayReservedIdsInfo) PbReservedIdsInfo info; string s = display.DisplayReservedIdsInfo(info); - EXPECT_FALSE(s.empty()); + EXPECT_TRUE(s.empty()); info.mutable_ids()->Add(5); s = display.DisplayReservedIdsInfo(info); diff --git a/cpp/test/scsidump_test.cpp b/cpp/test/scsidump_test.cpp index 473fc7ac..2e055157 100644 --- a/cpp/test/scsidump_test.cpp +++ b/cpp/test/scsidump_test.cpp @@ -1,72 +1,66 @@ - //--------------------------------------------------------------------------- // // SCSI Target Emulator PiSCSI // for Raspberry Pi // // Copyright (C) 2022 akuker +// Copyright (C) 2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "mocks.h" +#include + #include "scsidump/scsidump_core.h" #include "test/test_shared.h" -#include -#include using namespace std; using namespace filesystem; -class TestableScsidump : public ScsiDump -{ - public: - static void PublicGeneratePropertiesFile(const string& filename, const inquiry_info_t& inq_info) - { - ScsiDump::GeneratePropertiesFile(filename, inq_info); - } -}; - TEST(ScsiDumpTest, GeneratePropertiesFile) { - // Basic test - const string prop_file_name = "test.properties"; - ScsiDump::inquiry_info_t test_data = { + // Basic test + auto filename = CreateTempFile(0); + ScsiDump::inquiry_info_t test_data = { .vendor = "PISCSI", .product = "TEST PRODUCT", .revision = "REV1", .sector_size = 1000, .capacity = 100}; - TestableScsidump::PublicGeneratePropertiesFile("test", test_data); + test_data.GeneratePropertiesFile(filename); string expected_str = "{\n" " \"vendor\": \"PISCSI\",\n" " \"product\": \"TEST PRODUCT\",\n" " \"revision\": \"REV1\",\n" - " \"block_size\": \"1000\",\n}" + " \"block_size\": \"1000\"\n}" "\n"; - EXPECT_EQ(ReadTempFileToString(prop_file_name), expected_str); + EXPECT_EQ(expected_str, ReadTempFileToString(filename)); // Long string test + filename = CreateTempFile(0); test_data = {.vendor = "01234567", .product = "0123456789ABCDEF", .revision = "0123", .sector_size = UINT32_MAX, .capacity = UINT64_MAX}; - TestableScsidump::PublicGeneratePropertiesFile("test", test_data); + test_data.GeneratePropertiesFile(filename); expected_str = "{\n" " \"vendor\": \"01234567\",\n" " \"product\": \"0123456789ABCDEF\",\n" " \"revision\": \"0123\",\n" - " \"block_size\": \"4294967295\",\n" + " \"block_size\": \"4294967295\"\n" "}\n"; - EXPECT_EQ(ReadTempFileToString(prop_file_name), expected_str); + EXPECT_EQ(expected_str, ReadTempFileToString(filename)); + remove(filename); // Empty data test + filename = CreateTempFile(0); test_data = {.vendor = "", .product = "", .revision = "", .sector_size = 0, .capacity = 0}; - TestableScsidump::PublicGeneratePropertiesFile("test", test_data); + test_data.GeneratePropertiesFile(filename); expected_str = "{\n" " \"vendor\": \"\",\n" " \"product\": \"\",\n" " \"revision\": \"\",\n" - " \"block_size\": \"0\",\n" + " \"block_size\": \"0\"\n" "}\n"; - EXPECT_EQ(ReadTempFileToString(prop_file_name), expected_str); + EXPECT_EQ(expected_str, ReadTempFileToString(filename)); + remove(filename); } diff --git a/cpp/test/scsihd_nec_test.cpp b/cpp/test/scsihd_nec_test.cpp index cfff8485..6b5cbd7c 100644 --- a/cpp/test/scsihd_nec_test.cpp +++ b/cpp/test/scsihd_nec_test.cpp @@ -9,7 +9,6 @@ #include "mocks.h" #include "shared/piscsi_exceptions.h" -#include "controllers/controller_manager.h" #include "devices/scsihd_nec.h" #include #include @@ -29,7 +28,7 @@ void ScsiHdNecTest_SetUpModePages(map>& pages) TEST(ScsiHdNecTest, Inquiry) { - TestInquiry(SCHD, device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, "PiSCSI ", 0x1f, false, ".hdn"); + TestInquiry::Inquiry(SCHD, device_type::direct_access, scsi_level::scsi_1_ccs, "PiSCSI ", 0x1f, false, "file.hdn"); } TEST(ScsiHdNecTest, SetUpModePages) @@ -134,19 +133,19 @@ TEST(ScsiHdNecTest, SetParameters) ofstream out; out.open(hdi); - const array cylinders1 = { 1, 0, 0, 0 }; + const array cylinders1 = { 1, 0, 0, 0 }; out.seekp(28); out.write(cylinders1.data(), cylinders1.size()); - const array heads1 = { 1, 0, 0, 0 }; + const array heads1 = { 1, 0, 0, 0 }; out.seekp(24); out.write(heads1.data(), heads1.size()); - const array sectors1 = { 1, 0, 0, 0 }; + const array sectors1 = { 1, 0, 0, 0 }; out.seekp(20); out.write(sectors1.data(), sectors1.size()); - const array sector_size1 = { 0, 2, 0, 0 }; + const array sector_size1 = { 0, 2, 0, 0 }; out.seekp(16); out.write(sector_size1.data(), sector_size1.size()); - const array image_size = { 0, 2, 0, 0 }; + const array image_size = { 0, 2, 0, 0 }; out.seekp(12); out.write(image_size.data(), image_size.size()); out.close(); @@ -179,18 +178,18 @@ TEST(ScsiHdNecTest, SetParameters) out.open(nhd); out << "T98HDDIMAGE.R0"; - const array cylinders2 = { 1, 0 }; + const array cylinders2 = { 1, 0 }; out.seekp(0x114); out.write(cylinders2.data(), cylinders2.size()); - const array heads2 = { 1, 0 }; + const array heads2 = { 1, 0 }; out.seekp(0x118); out.write(heads2.data(), heads2.size()); - const array sectors2 = { 1, 0 }; + const array sectors2 = { 1, 0 }; out.seekp(0x11a); out.write(sectors2.data(), sectors2.size()); out.seekp(0x11c); out.write(sector_size2.data(), sector_size2.size()); - const array image_offset = { 1, 0, 0, 0 }; + const array image_offset = { 1, 0, 0, 0 }; out.seekp(0x110); out.write(image_offset.data(), image_offset.size()); out.close(); diff --git a/cpp/test/scsihd_test.cpp b/cpp/test/scsihd_test.cpp index 179c7170..1cdacad0 100644 --- a/cpp/test/scsihd_test.cpp +++ b/cpp/test/scsihd_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -23,23 +23,21 @@ void ScsiHdTest_SetUpModePages(map>& pages) TEST(ScsiHdTest, Inquiry) { - TestInquiry(SCHD, device_type::DIRECT_ACCESS, scsi_level::SCSI_2, "PiSCSI ", 0x1f, false); + TestInquiry::Inquiry(SCHD, device_type::direct_access, scsi_level::scsi_2, "PiSCSI ", 0x1f, false); - TestInquiry(SCHD, device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, "PiSCSI ", 0x1f, false, ".hd1"); + TestInquiry::Inquiry(SCHD, device_type::direct_access, scsi_level::scsi_1_ccs, "PiSCSI ", 0x1f, false, "file.hd1"); } TEST(ScsiHdTest, SupportsSaveParameters) { - const unordered_set sector_sizes; - MockSCSIHD hd(0, sector_sizes, false); + MockSCSIHD hd(0, {}, false); EXPECT_TRUE(hd.SupportsSaveParameters()); } TEST(ScsiHdTest, FinalizeSetup) { - const unordered_set sector_sizes; - MockSCSIHD hd(0, sector_sizes, false); + MockSCSIHD hd(0, {}, false); hd.SetSectorSizeInBytes(1024); EXPECT_THROW(hd.FinalizeSetup(0), io_exception) << "Device has 0 blocks"; @@ -47,10 +45,9 @@ TEST(ScsiHdTest, FinalizeSetup) TEST(ScsiHdTest, GetProductData) { - const unordered_set sector_sizes; - MockSCSIHD hd_kb(0, sector_sizes, false); - MockSCSIHD hd_mb(0, sector_sizes, false); - MockSCSIHD hd_gb(0, sector_sizes, false); + MockSCSIHD hd_kb(0, {}, false); + MockSCSIHD hd_mb(0, {}, false); + MockSCSIHD hd_gb(0, {}, false); const path filename = CreateTempFile(1); hd_kb.SetFilename(string(filename)); @@ -79,8 +76,7 @@ TEST(ScsiHdTest, GetProductData) TEST(ScsiHdTest, SetUpModePages) { map> pages; - const unordered_set sector_sizes; - MockSCSIHD hd(0, sector_sizes, false); + MockSCSIHD hd(0, {}, false); // Non changeable hd.SetUpModePages(pages, 0x3f, false); @@ -94,8 +90,7 @@ TEST(ScsiHdTest, SetUpModePages) TEST(ScsiHdTest, ModeSelect) { - const unordered_set sector_sizes = { 512 }; - MockSCSIHD hd(0, sector_sizes, false); + MockSCSIHD hd(0, { 512 }, false); vector cmd(10); vector buf(255); diff --git a/cpp/test/scsimo_test.cpp b/cpp/test/scsimo_test.cpp index 21447696..2422c479 100644 --- a/cpp/test/scsimo_test.cpp +++ b/cpp/test/scsimo_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -22,14 +22,13 @@ void ScsiMo_SetUpModePages(map>& pages) TEST(ScsiMoTest, Inquiry) { - TestInquiry(SCMO, device_type::OPTICAL_MEMORY, scsi_level::SCSI_2, "PiSCSI SCSI MO ", 0x1f, true); + TestInquiry::Inquiry(SCMO, device_type::optical_memory, scsi_level::scsi_2, "PiSCSI SCSI MO ", 0x1f, true); } TEST(ScsiMoTest, SupportsSaveParameters) { map> pages; - const unordered_set sector_sizes; - MockSCSIMO mo(0, sector_sizes); + MockSCSIMO mo(0, {}); EXPECT_TRUE(mo.SupportsSaveParameters()); } @@ -37,8 +36,7 @@ TEST(ScsiMoTest, SupportsSaveParameters) TEST(ScsiMoTest, SetUpModePages) { map> pages; - const unordered_set sector_sizes; - MockSCSIMO mo(0, sector_sizes); + MockSCSIMO mo(0, {}); // Non changeable mo.SetUpModePages(pages, 0x3f, false); @@ -53,8 +51,7 @@ TEST(ScsiMoTest, SetUpModePages) TEST(ScsiMoTest, TestAddVendorPage) { map> pages; - const unordered_set sector_sizes; - MockSCSIMO mo(0, sector_sizes); + MockSCSIMO mo(0, {}); mo.SetReady(true); mo.SetUpModePages(pages, 0x21, false); @@ -125,8 +122,7 @@ TEST(ScsiMoTest, TestAddVendorPage) TEST(ScsiMoTest, ModeSelect) { - const unordered_set sector_sizes = { 1024, 2048 }; - MockSCSIMO mo(0, sector_sizes); + MockSCSIMO mo(0, { 1024, 2048 }); vector cmd(10); vector buf(255); diff --git a/cpp/test/storage_device_test.cpp b/cpp/test/storage_device_test.cpp index 9fbed528..033766f4 100644 --- a/cpp/test/storage_device_test.cpp +++ b/cpp/test/storage_device_test.cpp @@ -3,18 +3,19 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "mocks.h" +#include "shared/piscsi_util.h" #include "shared/piscsi_exceptions.h" #include "devices/storage_device.h" #include using namespace filesystem; -TEST(StorageDeviceTest, Filename) +TEST(StorageDeviceTest, SetGetFilename) { MockStorageDevice device; @@ -62,6 +63,8 @@ TEST(StorageDeviceTest, MediumChanged) { MockStorageDevice device; + EXPECT_FALSE(device.IsMediumChanged()); + device.SetMediumChanged(true); EXPECT_TRUE(device.IsMediumChanged()); @@ -72,22 +75,26 @@ TEST(StorageDeviceTest, MediumChanged) TEST(StorageDeviceTest, GetIdsForReservedFile) { const int ID = 1; - const int LUN = 2; + const int LUN = 0; + auto bus = make_shared(); + ControllerManager controller_manager; + MockAbstractController controller(bus, ID); + auto device = make_shared(LUN); + device->SetFilename("filename"); StorageDevice::UnreserveAll(); - MockStorageDevice device; - device.SetFilename("filename"); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device)); const auto [id1, lun1] = StorageDevice::GetIdsForReservedFile("filename"); EXPECT_EQ(-1, id1); EXPECT_EQ(-1, lun1); - device.ReserveFile("filename", ID, LUN); + device->ReserveFile(); const auto [id2, lun2] = StorageDevice::GetIdsForReservedFile("filename"); EXPECT_EQ(ID, id2); EXPECT_EQ(LUN, lun2); - device.UnreserveFile(); + device->UnreserveFile(); const auto [id3, lun3] = StorageDevice::GetIdsForReservedFile("filename"); EXPECT_EQ(-1, id3); EXPECT_EQ(-1, lun3); @@ -95,9 +102,17 @@ TEST(StorageDeviceTest, GetIdsForReservedFile) TEST(StorageDeviceTest, UnreserveAll) { - MockStorageDevice device; - device.ReserveFile("filename", 2, 31); + const int ID = 1; + const int LUN = 0; + auto bus = make_shared(); + ControllerManager controller_manager; + MockAbstractController controller(bus, ID); + auto device = make_shared(LUN); + device->SetFilename("filename"); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device)); + + device->ReserveFile(); StorageDevice::UnreserveAll(); const auto [id, lun] = StorageDevice::GetIdsForReservedFile("filename"); EXPECT_EQ(-1, id); @@ -107,18 +122,24 @@ TEST(StorageDeviceTest, UnreserveAll) TEST(StorageDeviceTest, GetSetReservedFiles) { const int ID = 1; - const int LUN = 16; + const int LUN = 0; + auto bus = make_shared(); + ControllerManager controller_manager; + MockAbstractController controller(bus, ID); + auto device = make_shared(LUN); + device->SetFilename("filename"); - MockStorageDevice device; - device.ReserveFile("filename", ID, LUN); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device)); - const unordered_map reserved_files = StorageDevice::GetReservedFiles(); + device->ReserveFile(); + + const auto& reserved_files = StorageDevice::GetReservedFiles(); EXPECT_EQ(1, reserved_files.size()); - EXPECT_NE(reserved_files.end(), reserved_files.find("filename")); + EXPECT_TRUE(reserved_files.contains("filename")); StorageDevice::SetReservedFiles(reserved_files); EXPECT_EQ(1, reserved_files.size()); - EXPECT_NE(reserved_files.end(), reserved_files.find("filename")); + EXPECT_TRUE(reserved_files.contains("filename")); } TEST(StorageDeviceTest, FileExists) @@ -127,17 +148,6 @@ TEST(StorageDeviceTest, FileExists) EXPECT_TRUE(StorageDevice::FileExists("/dev/null")); } -TEST(StorageDeviceTest, IsReadOnlyFile) -{ - MockStorageDevice device; - - device.SetFilename("/dev/null"); - EXPECT_FALSE(device.IsReadOnlyFile()); - - device.SetFilename("/dev/mem"); - EXPECT_TRUE(device.IsReadOnlyFile()); -} - TEST(StorageDeviceTest, GetFileSize) { MockStorageDevice device; diff --git a/cpp/test/test_setup.cpp b/cpp/test/test_setup.cpp index cfa0c20d..63dc4e29 100644 --- a/cpp/test/test_setup.cpp +++ b/cpp/test/test_setup.cpp @@ -3,35 +3,34 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include -#include "spdlog/spdlog.h" - -// Also used by the PiscsiExecutor tests -bool enable_logging; //NOSONAR Must be global in order to be shared with the tests - -class Environment final : public ::testing::Environment -{ -public: - - Environment() = default; - ~Environment() override = default; - - void SetUp() override { spdlog::set_level(enable_logging ? spdlog::level::trace : spdlog::level::off); } -}; +#include int main(int argc, char *[]) { - // If any argument is provided the log level is set to trace - enable_logging = argc > 1; + const bool disable_logging = argc <= 1; - testing::AddGlobalTestEnvironment(new Environment()); + // If any argument is provided the log level is set to trace + spdlog::set_level(disable_logging ? spdlog::level::off : spdlog::level::trace); + + int fd = -1; + if (disable_logging) { + fd = open("/dev/null", O_WRONLY); + dup2(fd, STDERR_FILENO); + } testing::InitGoogleTest(); - return RUN_ALL_TESTS(); + const int result = RUN_ALL_TESTS(); + + if (fd != -1) { + close(fd); + } + + return result; } diff --git a/cpp/test/test_shared.cpp b/cpp/test/test_shared.cpp index bcce2600..3ab8ad29 100644 --- a/cpp/test/test_shared.cpp +++ b/cpp/test/test_shared.cpp @@ -8,11 +8,9 @@ //--------------------------------------------------------------------------- #include "test_shared.h" -#include "controllers/controller_manager.h" #include "mocks.h" #include "shared/piscsi_exceptions.h" #include "shared/piscsi_version.h" -#include #include #include #include @@ -28,38 +26,33 @@ const path test_data_temp_path(temp_directory_path() / path(fmt::format("piscsi-test-{}", getpid()))); // NOSONAR Publicly writable directory is fine here -shared_ptr CreateDevice(PbDeviceType type, MockAbstractController& controller, const string& extension) +pair, shared_ptr> CreateDevice(PbDeviceType type, const string& extension) { DeviceFactory device_factory; + auto controller = make_shared>(0); auto device = device_factory.CreateDevice(type, 0, extension); - unordered_map params; - device->Init(params); + device->Init({}); - controller.AddDevice(device); + EXPECT_TRUE(controller->AddDevice(device)); - return device; + return { controller, device }; } -void TestInquiry(PbDeviceType type, device_type t, scsi_level l, const string& ident, int additional_length, - bool removable, const string& extension) +void TestInquiry::Inquiry(PbDeviceType type, device_type t, scsi_level l, const string& ident, int additional_length, + bool removable, const string& extension) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto device = CreateDevice(type, *controller, extension); - - auto& cmd = controller->GetCmd(); + auto [controller, device] = CreateDevice(type, extension); // ALLOCATION LENGTH - cmd[4] = 255; + controller->SetCmdByte(4, 255); EXPECT_CALL(*controller, DataIn()); device->Dispatch(scsi_command::eCmdInquiry); const vector& buffer = controller->GetBuffer(); EXPECT_EQ(t, static_cast(buffer[0])); EXPECT_EQ(removable ? 0x80 : 0x00, buffer[1]); EXPECT_EQ(l, static_cast(buffer[2])); - EXPECT_EQ(l > scsi_level::SCSI_2 ? scsi_level::SCSI_2 : l, static_cast(buffer[3])); + EXPECT_EQ(l > scsi_level::scsi_2 ? scsi_level::scsi_2 : l, static_cast(buffer[3])); EXPECT_EQ(additional_length, buffer[4]); string product_data; if (ident.size() == 24) { @@ -88,13 +81,18 @@ pair OpenTempFile() } path CreateTempFile(int size) +{ + const auto data = vector(size); + return CreateTempFileWithData(data); +} + +path CreateTempFileWithData(const span data) { const auto [fd, filename] = OpenTempFile(); - vector data(size); const size_t count = write(fd, data.data(), data.size()); - close(fd); EXPECT_EQ(count, data.size()) << "Couldn't create temporary file '" << string(filename) << "'"; + close(fd); return path(filename); } @@ -110,36 +108,32 @@ void CreateTempFileWithData(const string& filename, vector& data) FILE* fp = fopen(new_filename.c_str(), "wb"); if (fp == nullptr) { - printf("ERROR: Unable to open file %s\n", new_filename.c_str()); + cerr << "ERROR: Unable to open file '" << new_filename << "'"; return; } if (const size_t size_written = fwrite(&data[0], sizeof(uint8_t), data.size(), fp); size_written != sizeof(vector::value_type) * data.size()) { - printf("Expected to write %zu bytes, but only wrote %zu to %s", size_written, - sizeof(vector::value_type) * data.size(), filename.c_str()); + cerr << "ERROR: Expected to write " << sizeof(vector::value_type) * data.size() << " bytes" + << ", but only wrote " << data.size() << " to '" << filename << "'"; } fclose(fp); } +// TODO Move this code, it is not shared void DeleteTempFile(const string& filename) { - path temp_file = test_data_temp_path; - temp_file += path(filename); - remove(temp_file); -} - -void CleanupAllTempFiles() -{ - remove_all(test_data_temp_path); + path temp_file = test_data_temp_path; + temp_file += path(filename); + remove(temp_file); } string ReadTempFileToString(const string& filename) { - path temp_file = test_data_temp_path / path(filename); - ifstream in_fs(temp_file); + const path temp_file = test_data_temp_path / path(filename); + ifstream in(temp_file); stringstream buffer; - buffer << in_fs.rdbuf(); + buffer << in.rdbuf(); return buffer.str(); } diff --git a/cpp/test/test_shared.h b/cpp/test/test_shared.h index 0c4b7c6a..8c87f294 100644 --- a/cpp/test/test_shared.h +++ b/cpp/test/test_shared.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -13,6 +13,7 @@ #include "shared/scsi.h" #include #include +#include #include using namespace std; @@ -24,22 +25,25 @@ class MockAbstractController; extern const path test_data_temp_path; -shared_ptr CreateDevice(PbDeviceType, MockAbstractController&, const string& = ""); - -void TestInquiry(PbDeviceType, scsi_defs::device_type, scsi_defs::scsi_level, const string&, int, bool, - const string& = ""); +pair, shared_ptr> CreateDevice(PbDeviceType, const string& = ""); pair OpenTempFile(); path CreateTempFile(int); +path CreateTempFileWithData(span); // create a file with the specified data void CreateTempFileWithData(const string&, vector&); void DeleteTempFile(const string&); -// Call this at the end of every test case to make sure things are cleaned up -void CleanupAllTempFiles(); string ReadTempFileToString(const string& filename); int GetInt16(const vector&, int); uint32_t GetInt32(const vector&, int); + +// This class is needed in order to be declared as friend, required to have access to AbstractController::SetCmdByte +class TestInquiry { +public: + static void Inquiry(PbDeviceType, scsi_defs::device_type, scsi_defs::scsi_level, const string&, int, bool, + const string& = ""); +}; diff --git a/doc/piscsi.1 b/doc/piscsi.1 index a40418e2..5bc58f0f 100644 --- a/doc/piscsi.1 +++ b/doc/piscsi.1 @@ -56,7 +56,7 @@ The optional block size, either 512, 1024, 2048 or 4096 bytes. Default size is 5 The default folder for image files. For files in this folder no absolute path needs to be specified. The initial default folder is '~/images'. .TP .BR \-L\fI " " \fILOG_LEVEL[:ID:[LUN]] -The piscsi log level (trace, debug, info, warn, err, off). The default log level is 'info' for all devices unless a particular device ID and an optional LUN was provided. +The piscsi log level (trace, debug, info, warning, error, off). The default log level is 'info' for all devices unless a particular device ID and an optional LUN was provided. .TP .BR \-P\fI " " \fIACCESS_TOKEN_FILE Enable authentication and read the access token from the specified file. The access token file must be owned by root and must be readable by root only. diff --git a/doc/piscsi_man_page.txt b/doc/piscsi_man_page.txt index 727a4982..4816dfb7 100644 --- a/doc/piscsi_man_page.txt +++ b/doc/piscsi_man_page.txt @@ -1,96 +1,125 @@ !! ------ THIS FILE IS AUTO_GENERATED! DO NOT MANUALLY UPDATE!!! -!! ------ The native file is piscsi.1. Re-run 'make docs' after updating - - -piscsi(1) General Commands Manual piscsi(1) +!! ------ The native file is piscsi.1. Re-run 'make docs' after updating\n\n +piscsi(1) General Commands Manual piscsi(1) NAME piscsi - Emulates SCSI devices using the Raspberry Pi GPIO pins SYNOPSIS - piscsi [-F FOLDER] [-L LOG_LEVEL[:ID:[LUN]]] [-P ACCESS_TOKEN_FILE] [-R SCAN_DEPTH] [-h] [-n VENDOR:PRODUCT:REVISION] [-p - PORT] [-r RESERVED_IDS] [-n TYPE] [-v] [-z LOCALE] [-IDn:[u] FILE] [-HDn[:u] FILE]... + piscsi [-F FOLDER] [-L LOG_LEVEL[:ID:[LUN]]] [-P ACCESS_TOKEN_FILE] [-R + SCAN_DEPTH] [-h] [-n VENDOR:PRODUCT:REVISION] [-p PORT] [-r RE‐ + SERVED_IDS] [-n TYPE] [-v] [-z LOCALE] [-IDn:[u] FILE] [-HDn[:u] + FILE]... DESCRIPTION piscsi emulates SCSI devices using the Raspberry Pi GPIO pins. - In the arguments to PiSCSI, one or more SCSI (-IDn[:u]) devices can be specified. The number (n) after the ID or HD iden‐ - tifier specifies the ID number for that device. The optional number (u) specifies the LUN (logical unit) for that device. - The default LUN is 0. For SCSI: The ID is limited from 0-7. However, typically SCSI ID 7 is reserved for the "initiator" - (the host computer). The LUN is limited from 0-31. + In the arguments to PiSCSI, one or more SCSI (-IDn[:u]) devices can be + specified. The number (n) after the ID or HD identifier specifies the + ID number for that device. The optional number (u) specifies the LUN + (logical unit) for that device. The default LUN is 0. For SCSI: The ID + is limited from 0-7. However, typically SCSI ID 7 is reserved for the + "initiator" (the host computer). The LUN is limited from 0-31. - PiSCSI will determine the type of device based upon the file extension of the FILE argument. + PiSCSI will determine the type of device based upon the file extension + of the FILE argument. hd1: SCSI Hard Disk image (generic, non-removable, SCSI-1) hds: SCSI Hard Disk image (generic, non-removable) hdr: SCSI Hard Disk image (generic, removable) - hdn: SCSI Hard Disk image (NEC compatible - only used with PC-98 computers) - hdi: SCSI Hard Disk image (Anex86 proprietary - only used with PC-98 computers) - nhd: SCSI Hard Disk image (T98Next proprietary - only used with PC-98 computers) - hda: SCSI Hard Disk image (Apple compatible - typically used with Macintosh computers) - mos: SCSI Magneto-Optical image (generic - typically used with NeXT, X68000, etc.) + hdn: SCSI Hard Disk image (NEC compatible - only used with PC-98 + computers) + hdi: SCSI Hard Disk image (Anex86 proprietary - only used with + PC-98 computers) + nhd: SCSI Hard Disk image (T98Next proprietary - only used with + PC-98 computers) + hda: SCSI Hard Disk image (Apple compatible - typically used with + Macintosh computers) + mos: SCSI Magneto-Optical image (generic - typically used with + NeXT, X68000, etc.) iso: SCSI CD-ROM or DVD-ROM image (ISO 9660 image) is1: SCSI CD-ROM or DVD-ROM image (ISO 9660 image, SCSI-1) - For example, if you want to specify an Apple-compatible HD image on ID 0, you can use the following command: + For example, if you want to specify an Apple-compatible HD image on ID + 0, you can use the following command: sudo piscsi -ID0 /path/to/drive/hdimage.hda - Note: PiSCSI is a fork of RaSCSI. The two cannot be run in parallel on the same system. + Note: PiSCSI is a fork of RaSCSI. The two cannot be run in parallel on + the same system. - Once PiSCSI starts, it will open a socket (default port is 6868) to allow external management commands. If another process - is using this port, PiSCSI will terminate, since it is likely another instance of PiSCSI or RaSCSI. Once PiSCSI has ini‐ - tialized, the scsictl utility can be used to send commands. + Once PiSCSI starts, it will open a socket (default port is 6868) to al‐ + low external management commands. If another process is using this + port, PiSCSI will terminate, since it is likely another instance of + PiSCSI or RaSCSI. Once PiSCSI has initialized, the scsictl utility can + be used to send commands. - To quit PiSCSI, press Control + C. If it is running in the background, you can kill it using an INT signal. + To quit PiSCSI, press Control + C. If it is running in the background, + you can kill it using an INT signal. OPTIONS -b BLOCK_SIZE - The optional block size, either 512, 1024, 2048 or 4096 bytes. Default size is 512 bytes. + The optional block size, either 512, 1024, 2048 or 4096 bytes. + Default size is 512 bytes. -F FOLDER - The default folder for image files. For files in this folder no absolute path needs to be specified. The initial de‐ - fault folder is '~/images'. + The default folder for image files. For files in this folder no + absolute path needs to be specified. The initial default folder + is '~/images'. -L LOG_LEVEL[:ID:[LUN]] - The piscsi log level (trace, debug, info, warn, err, off). The default log level is 'info' for all devices unless a - particular device ID and an optional LUN was provided. + The piscsi log level (trace, debug, info, warning, error, off). + The default log level is 'info' for all devices unless a partic‐ + ular device ID and an optional LUN was provided. -P ACCESS_TOKEN_FILE - Enable authentication and read the access token from the specified file. The access token file must be owned by root - and must be readable by root only. + Enable authentication and read the access token from the speci‐ + fied file. The access token file must be owned by root and must + be readable by root only. -R SCAN_DEPTH - Scan for image files recursively, up to a depth of SCAN_DEPTH. Depth 0 means to ignore any folders within the de‐ - fault image filder. Be careful when using this option with many sub-folders in the default image folder. The default - depth is 1. + Scan for image files recursively, up to a depth of SCAN_DEPTH. + Depth 0 means to ignore any folders within the default image + filder. Be careful when using this option with many sub-folders + in the default image folder. The default depth is 1. -h Show a help page. -n VENDOR:PRODUCT:REVISION - Set the vendor, product and revision for the device, to be returned with the INQUIRY data. A complete set of name - components must be provided. VENDOR may have up to 8, PRODUCT up to 16, REVISION up to 4 characters. Padding with - blanks to the maxium length is automatically applied. Once set the name of a device cannot be changed. + Set the vendor, product and revision for the device, to be re‐ + turned with the INQUIRY data. A complete set of name components + must be provided. VENDOR may have up to 8, PRODUCT up to 16, RE‐ + VISION up to 4 characters. Padding with blanks to the maxium + length is automatically applied. Once set the name of a device + cannot be changed. -p PORT The piscsi server port, default is 6868. -r RESERVED_IDS - Comma-separated list of IDs to reserve. Pass an empty list in order to not reserve anything. -p TYPE The optional - case-insensitive device type (SAHD, SCHD, SCRM, SCCD, SCMO, SCBR, SCDP, SCLP, SCHS). If no type is specified for de‐ - vices that support an image file, piscsi tries to derive the type from the file extension. + Comma-separated list of IDs to reserve. Pass an empty list in + order to not reserve anything. -p TYPE The optional case-insen‐ + sitive device type (SAHD, SCHD, SCRM, SCCD, SCMO, SCBR, SCDP, + SCLP, SCHS). If no type is specified for devices that support an + image file, piscsi tries to derive the type from the file exten‐ + sion. -v Display the piscsi version. -z LOCALE - Overrides the default locale for client-faces error messages. The client can override the locale. + Overrides the default locale for client-faces error messages. + The client can override the locale. -IDn[:u] FILE - n is the SCSI ID number (0-7). u (0-31) is the optional LUN (logical unit). The default LUN is 0. + n is the SCSI ID number (0-7). u (0-31) is the optional LUN + (logical unit). The default LUN is 0. - FILE is the name of the image file to use for the SCSI device. For devices that do not support an image file (SCBR, - SCDP, SCLP, SCHS) the filename may have a special meaning or a dummy name can be provided. For SCBR and SCDP it is - an optioinal prioritized list of network interfaces, an optional IP address and netmask, e.g. "inter‐ - face=eth0,eth1,wlan0:inet=10.10.20.1/24". For SCLP it is the print command to be used and a reservation timeout in - seconds, e.g. "cmd=lp -oraw %f:timeout=60". + FILE is the name of the image file to use for the SCSI device. + For devices that do not support an image file (SCBR, SCDP, SCLP, + SCHS) the filename may have a special meaning or a dummy name + can be provided. For SCBR and SCDP it is an optioinal priori‐ + tized list of network interfaces, an optional IP address and + netmask, e.g. "interface=eth0,eth1,wlan0:inet=10.10.20.1/24". + For SCLP it is the print command to be used and a reservation + timeout in seconds, e.g. "cmd=lp -oraw %f:timeout=60". FILE is the name of the image file to use for the SCSI device. @@ -98,17 +127,20 @@ EXAMPLES Launch PiSCSI with no emulated drives attached: piscsi - Launch PiSCSI with an Apple hard drive image as ID 0 and a CD-ROM as ID 2 + Launch PiSCSI with an Apple hard drive image as ID 0 and a CD-ROM as ID + 2 piscsi -ID0 /path/to/harddrive.hda -ID2 /path/to/cdimage.iso - Launch PiSCSI with a removable SCSI drive image as ID 0 and the raw device file /dev/hdb (e.g. a USB stick) and a DaynaPort - network adapter as ID 6: + Launch PiSCSI with a removable SCSI drive image as ID 0 and the raw de‐ + vice file /dev/hdb (e.g. a USB stick) and a DaynaPort network adapter + as ID 6: piscsi -ID0 -t scrm /dev/hdb -ID6 -t scdp daynaport To create an empty, 100MiB HD image, use the following command: dd if=/dev/zero of=/path/to/newimage.hda bs=512 count=204800 - In case the fallocate command is available a much faster alternative to the dd command is: + In case the fallocate command is available a much faster alternative to + the dd command is: fallocate -l 104857600 /path/to/newimage.hda SEE ALSO @@ -116,4 +148,4 @@ SEE ALSO Full documentation is available at: - piscsi(1) + piscsi(1) diff --git a/doc/scsictl.1 b/doc/scsictl.1 index 98a12f05..3df74fba 100644 --- a/doc/scsictl.1 +++ b/doc/scsictl.1 @@ -7,30 +7,31 @@ scsictl \- Sends management commands to the piscsi process \fB\-l\fR | \fB\-m\fR | \fB\-o\fR | -\fB\-s\fR | \fB\-v\fR | \fB\-D\fR | \fB\-I\fR | \fB\-L\fR | \fB\-O\fR | \fB\-P\fR | +\fB\-S\fR | \fB\-T\fR | \fB\-V\fR | \fB\-X\fR | -[\fB\-C\fR \fIFILENAME:FILESIZE\fR] -[\fB\-E\fR \fIFILENAME\fR] -[\fB\-F\fR \fIIMAGE_FOLDER\fR] -[\fB\-R\fR \fICURRENT_NAME:NEW_NAME\fR] -[\fB\-c\fR \fICMD\fR] -[\fB\-f\fR \fIFILE|PARAM\fR] -[\fB\-g\fR \fILOG_LEVEL\fR] -[\fB\-h\fR \fIHOST\fR] -[\fB\-i\fR \fIID[:LUN]\fR -[\fB\-n\fR \fINAME\fR] -[\fB\-p\fR \fIPORT\fR] -[\fB\-r\fR \fIRESERVED_IDS\fR] -[\fB\-t\fR \fITYPE\fR] -[\fB\-x\fR \fICURRENT_NAME:NEW_NAME\fR] +[\fB\-C\fR \fIFILENAME:FILESIZE\fR] | +[\fB\-E\fR \fIFILENAME\fR] | +[\fB\-F\fR \fIIMAGE_FOLDER\fR] | +[\fB\-R\fR \fICURRENT_NAME:NEW_NAME\fR] | +[\fB\-c\fR \fICMD\fR] | +[\fB\-f\fR \fIFILE|PARAM\fR] | +[\fB\-g\fR \fILOG_LEVEL\fR] | +[\fB\-h\fR \fIHOST\fR] | +[\fB\-i\fR \fIID[:LUN]\fR] | +[\fB\-n\fR \fINAME\fR] | +[\fB\-p\fR \fIPORT\fR] | +[\fB\-r\fR \fIRESERVED_IDS\fR] | +[\fB\-s\fR \fI[FOLDER_PATTERN:FILE_PATTERN:OPERATIONS]\fR] | +[\fB\-t\fR \fITYPE\fR] | +[\fB\-x\fR \fICURRENT_NAME:NEW_NAME\fR] | [\fB\-z\fR \fILOCALE\fR] .SH DESCRIPTION .B scsictl @@ -38,7 +39,7 @@ sends commands to the piscsi process to make configuration adjustments at runtim Either the -i or -l option should be specified at one time. Not both. -You do NOT need root privileges to use scsictl. +You do NOT need root privileges to use scsictl. scsictl also runs on non-Pi Linux platforms. Note: The command and type arguments are case insensitive. Only the first letter of the command/type is evaluated by the tool. @@ -60,7 +61,7 @@ Set the default image folder. Gets the list of reserved device IDs. .TP .BR \-L\fI " "\fILOG_LEVEL -Set the piscsi log level (trace, debug, info, warn, err, off). +Set the piscsi log level (trace, debug, info, warning, error, off). .TP .BR \-h\fI " " \fIHOST The piscsi host to connect to, default is 'localhost'. @@ -95,9 +96,12 @@ The piscsi port to connect to, default is 6868. .BR \-r\fI " " \fIRESERVED_IDS Comma-separated list of IDs to reserve. Pass an empty list in order to not reserve anything. .TP -.BR \-s\fI +.BR \-s\fI " " \fI[FOLDER_PATTERN:FILE_PATTERN:OPERATIONS] Display server-side settings like available images or supported device types. .TP +.BR \-S\fI +Display statistics. +.TP .BR \-T\fI Display all device types and their properties. .TP diff --git a/doc/scsictl_man_page.txt b/doc/scsictl_man_page.txt index 9281242c..f98b7208 100644 --- a/doc/scsictl_man_page.txt +++ b/doc/scsictl_man_page.txt @@ -1,31 +1,34 @@ !! ------ THIS FILE IS AUTO_GENERATED! DO NOT MANUALLY UPDATE!!! -!! ------ The native file is scsictl.1. Re-run 'make docs' after updating - - -piscsi(1) General Commands Manual piscsi(1) +!! ------ The native file is scsictl.1. Re-run 'make docs' after updating\n\n +piscsi(1) General Commands Manual piscsi(1) NAME scsictl - Sends management commands to the piscsi process SYNOPSIS - scsictl -e | -l | -m | -o | -s | -v | -D | -I | -L | -O | -P | -T | -V | -X | [-C FILENAME:FILESIZE] [-E FILENAME] [-F IM‐ - AGE_FOLDER] [-R CURRENT_NAME:NEW_NAME] [-c CMD] [-f FILE|PARAM] [-g LOG_LEVEL] [-h HOST] [-i ID[:LUN] [-n NAME] [-p PORT] - [-r RESERVED_IDS] [-t TYPE] [-x CURRENT_NAME:NEW_NAME] [-z LOCALE] + scsictl -e | -l | -m | -o | -v | -D | -I | -L | -O | -P | -S | -T | -V + | -X | [-C FILENAME:FILESIZE] | [-E FILENAME] | [-F IMAGE_FOLDER] | [-R + CURRENT_NAME:NEW_NAME] | [-c CMD] | [-f FILE|PARAM] | [-g LOG_LEVEL] | + [-h HOST] | [-i ID[:LUN]] | [-n NAME] | [-p PORT] | [-r RESERVED_IDS] | + [-s [FOLDER_PATTERN:FILE_PATTERN:OPERATIONS]] | [-t TYPE] | [-x CUR‐ + RENT_NAME:NEW_NAME] | [-z LOCALE] DESCRIPTION - scsictl sends commands to the piscsi process to make configuration adjustments at runtime or to check the status of the de‐ - vices. + scsictl sends commands to the piscsi process to make configuration ad‐ + justments at runtime or to check the status of the devices. Either the -i or -l option should be specified at one time. Not both. - You do NOT need root privileges to use scsictl. + You do NOT need root privileges to use scsictl. scsictl also runs on + non-Pi Linux platforms. - Note: The command and type arguments are case insensitive. Only the first letter of the command/type is evaluated by the - tool. + Note: The command and type arguments are case insensitive. Only the + first letter of the command/type is evaluated by the tool. OPTIONS -C FILENAME:FILESIZE - Create an image file in the default image folder with the specified name and size in bytes. + Create an image file in the default image folder with the speci‐ + fied name and size in bytes. -D Detach all devices. @@ -38,22 +41,28 @@ OPTIONS -I Gets the list of reserved device IDs. -L LOG_LEVEL - Set the piscsi log level (trace, debug, info, warn, err, off). + Set the piscsi log level (trace, debug, info, warning, error, + off). -h HOST The piscsi host to connect to, default is 'localhost'. -e List all images files in the default image folder. - -N Lists all available network interfaces provided that they are up. + -N Lists all available network interfaces provided that they are + up. - -O Display the available piscsi server log levels and the current log level. + -O Display the available piscsi server log levels and the current + log level. - -P Prompt for the access token in case piscsi requires authentication. + -P Prompt for the access token in case piscsi requires authentica‐ + tion. - -l List all of the devices that are currently being emulated by PiSCSI, as well as their current status. + -l List all of the devices that are currently being emulated by + PiSCSI, as well as their current status. - -m List all file extensions recognized by PiSCSI and the device types they map to. + -m List all file extensions recognized by PiSCSI and the device + types they map to. -o Display operation meta data information. @@ -64,9 +73,14 @@ OPTIONS The piscsi port to connect to, default is 6868. -r RESERVED_IDS - Comma-separated list of IDs to reserve. Pass an empty list in order to not reserve anything. + Comma-separated list of IDs to reserve. Pass an empty list in + order to not reserve anything. - -s Display server-side settings like available images or supported device types. + -s [FOLDER_PATTERN:FILE_PATTERN:OPERATIONS] + Display server-side settings like available images or supported + device types. + + -S Display statistics. -T Display all device types and their properties. @@ -86,30 +100,36 @@ OPTIONS Overrides the default locale for client-facing error messages. -i ID[:LUN] - The SCSI ID and optional LUN that you want to control. (0-7:0-31) + The SCSI ID and optional LUN that you want to control. + (0-7:0-31) -c CMD Command is the operation being requested. Options are: a(ttach): Attach disk d(etach): Detach disk i(nsert): Insert media (removable media devices only) e(ject): Eject media (removable media devices only) - p(rotect): Write protect the medium (not for CD-ROMs, which are always read-only) - u(nprotect): Remove write protection from the medium (not for CD-ROMs, which are always read-only) + p(rotect): Write protect the medium (not for CD-ROMs, which + are always read-only) + u(nprotect): Remove write protection from the medium (not for + CD-ROMs, which are always read-only) s(how): Display device information eject, protect and unprotect are idempotent. -b BLOCK_SIZE - The optional block size, either 512, 1024, 2048 or 4096 bytes. The default size is 512 bytes. + The optional block size, either 512, 1024, 2048 or 4096 bytes. + The default size is 512 bytes. -f FILE|PARAM - Device-specific: Either a path to a disk image file, or a parameter for a non-disk device. See the piscsi(1) man - page for permitted file types. + Device-specific: Either a path to a disk image file, or a param‐ + eter for a non-disk device. See the piscsi(1) man page for per‐ + mitted file types. -t TYPE - Specifies the device type. This type overrides the type derived from the file extension of the specified image. See - the piscsi(1) man page for the available device types. For some types there are shortcuts (only the first letter is - required): + Specifies the device type. This type overrides the type derived + from the file extension of the specified image. See the + piscsi(1) man page for the available device types. For some + types there are shortcuts (only the first letter is required): hd: SCSI hard disk drive rm: SCSI removable media drive cd: CD-ROM @@ -120,13 +140,17 @@ OPTIONS services: Host services device -n VENDOR:PRODUCT:REVISION - The vendor, product and revision for the device, to be returned with the INQUIRY data. A complete set of name compo‐ - nents must be provided. VENDOR may have up to 8, PRODUCT up to 16, REVISION up to 4 characters. Padding with blanks - to the maxium length is automatically applied. Once set the name of a device cannot be changed. + The vendor, product and revision for the device, to be returned + with the INQUIRY data. A complete set of name components must be + provided. VENDOR may have up to 8, PRODUCT up to 16, REVISION up + to 4 characters. Padding with blanks to the maxium length is au‐ + tomatically applied. Once set the name of a device cannot be + changed. -u UNIT - Unit number (0-31). This will default to 0. This option is only used when there are multiple SCSI devices on a - shared SCSI controller. (This is not common) + Unit number (0-31). This will default to 0. This option is only + used when there are multiple SCSI devices on a shared SCSI con‐ + troller. (This is not common) EXAMPLES Show a listing of all of the SCSI devices and their current status. @@ -139,8 +163,8 @@ EXAMPLES | 0 | 1 | SCHD | /home/pi/harddisk.hda +----+-----+------+------------------------------------- - Request the PiSCSI process to attach a disk (assumed) to SCSI ID 0 with the contents of the file system image "HDIIM‐ - AGE0.HDS". + Request the PiSCSI process to attach a disk (assumed) to SCSI ID 0 with + the contents of the file system image "HDIIMAGE0.HDS". scsictl -i 0 -f HDIIMAGE0.HDS SEE ALSO @@ -148,4 +172,4 @@ SEE ALSO Full documentation is available at: - piscsi(1) + piscsi(1) diff --git a/doc/scsidump.1 b/doc/scsidump.1 index 0fe2cb52..4ec16147 100644 --- a/doc/scsidump.1 +++ b/doc/scsidump.1 @@ -10,6 +10,8 @@ scsidump \- SCSI disk dumping tool for PiSCSI [\fB\-r\fR] [\fB\-v\fR] [\fB\-p\fR] +[\fB\-I\fR] ID[:LUN] +[\fB\-S\fR] .SH DESCRIPTION .B scsidump has two modes of operation: dump and restore. These can be used with physical storage media, including hard drives and magneto optical drives. Dump mode can be used with read-only media such as CD/DVD drives. @@ -47,6 +49,12 @@ Enable verbose logging. .TP .BR \-p\fI Generate a .properties file that is compatible with the PiSCSI web interface. The output filename will match the image filename with ".properties" appended. The generated file should be moved to ~/.config/piscsi. +.TP +.BR \-I\fI " "\fIID[:LUN] +Display INQUIRY data of ID[:LUN]. +.TP +.BR \-S\fI +Scan SCSI bus for devices. .SH EXAMPLES Dump Mode: [SCSI Drive] ---> [PiSCSI host] diff --git a/doc/scsidump_man_page.txt b/doc/scsidump_man_page.txt index a39e64bb..70d63389 100644 --- a/doc/scsidump_man_page.txt +++ b/doc/scsidump_man_page.txt @@ -1,51 +1,78 @@ !! ------ THIS FILE IS AUTO_GENERATED! DO NOT MANUALLY UPDATE!!! -!! ------ The native file is scsidump.1. Re-run 'make docs' after updating - - -scsidump(1) General Commands Manual scsidump(1) +!! ------ The native file is scsidump.1. Re-run 'make docs' after updating\n\n +scsidump(1) General Commands Manual scsidump(1) NAME scsidump - SCSI disk dumping tool for PiSCSI SYNOPSIS - scsidump -t ID[:LUN] [-i BID] -f FILE [-s BUFFER_SIZE] [-r] [-v] [-p] + scsidump -t ID[:LUN] [-i BID] -f FILE [-s BUFFER_SIZE] [-r] [-v] [-p] + [-I] ID[:LUN] [-S] DESCRIPTION - scsidump has two modes of operation: dump and restore. These can be used with physical storage media, including hard drives and magneto optical drives. Dump mode can be used with read-only media such as CD/DVD drives. + scsidump has two modes of operation: dump and restore. These can be + used with physical storage media, including hard drives and magneto op‐ + tical drives. Dump mode can be used with read-only media such as CD/DVD + drives. - When operating in dump mode, scsidump will copy all data from a remote SCSI drive to an image on the local filesystem. If enabled, it will also generate a .properties file that can be used to more closely emulate the source drive. + When operating in dump mode, scsidump will copy all data from a remote + SCSI drive to an image on the local filesystem. If enabled, it will + also generate a .properties file that can be used to more closely emu‐ + late the source drive. - If you are operating in restore mode, scsidump will copy the data from a local binary image to a remote physical SCSI drive. The remote SCSI drive MUST be writable. + If you are operating in restore mode, scsidump will copy the data from + a local binary image to a remote physical SCSI drive. The remote SCSI + drive MUST be writable. NOTES - scsidump requires either a direct connection (one without transceivers) or a FULLSPEC PiSCSI/RaSCSI board. + scsidump requires either a direct connection (one without transceivers) + or a FULLSPEC PiSCSI/RaSCSI board. - If the generated drive image is intended to be used with PiSCSI, the drive image should be moved to ~/images (or the location specified to the piscsi service). + If the generated drive image is intended to be used with PiSCSI, the + drive image should be moved by the user to ~/images (or the location + specified to the piscsi service). OPTIONS -t ID[:LUN] - SCSI ID and optional LUN of the remote SCSI device. The remote SCSI device will be functioning as the "Target" device. + SCSI ID and optional LUN of the remote SCSI device. The remote + SCSI device will be functioning as the "Target" device. - -i BID SCSI ID of the PiSCSI device. If not specified, the PiSCSI device will use ID 7. The PiSCSI host will be functioning as the "Initiator" device. + -i BID SCSI ID of the PiSCSI device. If not specified, the PiSCSI de‐ + vice will use ID 7. The PiSCSI host will be functioning as the + "Initiator" device. -f FILE Path to the dump file. -s BUFFER_SIZE - The transfer buffer size, specified in bytes. Default is 1 MiB. This is specified in bytes with a minimum value of 65536 (64 KiB). + The transfer buffer size, specified in bytes. Default is 1 MiB. + This is specified in bytes with a minimum value of 65536 (64 + KiB). -r Run in restore mode. Defaults to dump mode if not specified. -v Enable verbose logging. - -p Generate a .properties file that is compatible with the PiSCSI web interface. The output filename will match the image filename with ".properties" appended. The generated file should be moved to ~/.config/piscsi. + -p Generate a .properties file that is compatible with the PiSCSI + web interface. The output filename will match the image filename + with ".properties" appended. The generated file should be moved + to ~/.config/piscsi. + + -I ID[:LUN] + Display INQUIRY data of ID[:LUN]. + + -S Scan SCSI bus for devices. EXAMPLES - Dump Mode: [SCSI Drive] ---> [PiSCSI host] Launch scsidump to dump an all data from SCSI ID 3 with block size 64 KiB, store it to the local filesystem as a drive image named outimage.hda, and generate the outimage.hda.properties file with the drive's INQUIRY - information: + Dump Mode: [SCSI Drive] ---> [PiSCSI host] Launch scsidump to dump an + all data from SCSI ID 3 with block size 64 KiB, store it to the local + filesystem as a drive image named outimage.hda, and generate the outim‐ + age.hda.properties file with the drive's INQUIRY information: scsidump -t 3 -f ./outimage.hda -s 65536 -p - Restore Mode: [PiSCSI host] ---> [SCSI Drive] Launch scsidump to restore/upload a drive image from the local file system to SCSI ID 0 with block size 1MiB: + Restore Mode: [PiSCSI host] ---> [SCSI Drive] Launch scsidump to re‐ + store/upload a drive image from the local file system to SCSI ID 0 with + block size 1MiB: scsidump -r -t 0 -f ./outimage.hda -s 1048576 SEE ALSO @@ -53,4 +80,4 @@ SEE ALSO Full documentation is available at: - scsidump(1) + scsidump(1) diff --git a/doc/scsimon.1 b/doc/scsimon.1 index 48a76230..7b971803 100644 --- a/doc/scsimon.1 +++ b/doc/scsimon.1 @@ -18,9 +18,11 @@ To quit scsimon, press Control + C. None .SH EXAMPLES -Launch scsimon to capture all SCSI traffic available to the PiSCSI hardware: +Make sure you've stopped the piscsi service. Then launch scsimon to capture all SCSI traffic available to the PiSCSI hardware: scsimon +If you're trying to capture a specific scenario, you'll want to wait to start scsimon until immediately before the scenario. + .SH SEE ALSO scsictl(1), piscsi(1), scsidump(1) diff --git a/doc/scsimon_man_page.txt b/doc/scsimon_man_page.txt index 97b7e1bf..f708fbfa 100644 --- a/doc/scsimon_man_page.txt +++ b/doc/scsimon_man_page.txt @@ -2,20 +2,22 @@ !! ------ The native file is scsimon.1. Re-run 'make docs' after updating -scsimon(1) General Commands Manual scsimon(1) +scsimon(1) General Commands Manual scsimon(1) NAME - scsimon - Acts as a data capture tool for all traffic on the SCSI bus. Data is stored in a Value Change Dump (VCD) file. + scsimon - Acts as a data capture tool for all traffic on the SCSI bus. Data is stored in a Value Change Dump + (VCD) file. SYNOPSIS scsimon DESCRIPTION - scsimon monitors all of the traffic on the SCSI bus, using a PiSCSI device. The data is cached in memory while the tool is - running. A circular buffer is used so that only the most recent 1,000,000 transactions are stored. The tool will continue - to run until the user presses CTRL-C, or the process receives a SIGINT signal. + scsimon monitors all of the traffic on the SCSI bus, using a PiSCSI device. The data is cached in memory while + the tool is running. A circular buffer is used so that only the most recent 1,000,000 transactions are stored. + The tool will continue to run until the user presses CTRL-C, or the process receives a SIGINT signal. - The logged data is stored in a file called "log.vcd" in the current working directory from where scsimon was launched. + The logged data is stored in a file called "log.vcd" in the current working directory from where scsimon was + launched. Currently, scsimon doesn't accept any arguments. @@ -25,12 +27,16 @@ OPTIONS None EXAMPLES - Launch scsimon to capture all SCSI traffic available to the PiSCSI hardware: + Make sure you've stopped the piscsi service. Then launch scsimon to capture all SCSI traffic available to the + PiSCSI hardware: scsimon + If you're trying to capture a specific scenario, you'll want to wait to start scsimon until immediately before + the scenario. + SEE ALSO scsictl(1), piscsi(1), scsidump(1) Full documentation is available at: - scsimon(1) + scsimon(1) diff --git a/docker/README.md b/docker/README.md index afb007a3..736ac6ef 100644 --- a/docker/README.md +++ b/docker/README.md @@ -36,7 +36,7 @@ The following environment variables are available when using Docker Compose: | Environment Variable | Default | | -------------------- |----------| -| `OS_VERSION` | buster | +| `OS_VERSION` | bullseye | | `WEB_HTTP_PORT` | 8080 | | `WEB_HTTPS_PORT` | 8443 | | `WEB_LOG_LEVEL` | info | diff --git a/docker/docker-compose.ci.yml b/docker/docker-compose.ci.yml index 40bd95e6..2687eaa5 100644 --- a/docker/docker-compose.ci.yml +++ b/docker/docker-compose.ci.yml @@ -16,7 +16,7 @@ services: context: .. dockerfile: docker/web/Dockerfile args: - - OS_VERSION=buster + - OS_VERSION=bullseye volumes: - ./volumes/images:/home/pi/images:delegated init: true diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 4ae30d27..e3ea35a2 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -21,13 +21,13 @@ services: web: container_name: piscsi_web - image: piscsi-web:${OS_VERSION:-buster} + image: piscsi-web:${OS_VERSION:-bullseye} pull_policy: never build: context: .. dockerfile: docker/web/Dockerfile args: - - OS_VERSION=${OS_VERSION:-buster} + - OS_VERSION=${OS_VERSION:-bullseye} volumes: - ./volumes/images:/home/pi/images:delegated - ./volumes/config:/home/pi/.config/piscsi:delegated diff --git a/docker/web/Dockerfile b/docker/web/Dockerfile index 4d235eca..1d74a423 100644 --- a/docker/web/Dockerfile +++ b/docker/web/Dockerfile @@ -1,5 +1,5 @@ ARG DEBIAN_FRONTEND=noninteractive -ARG OS_VERSION=buster +ARG OS_VERSION=bullseye FROM "debian:${OS_VERSION}-slim" diff --git a/easyinstall.sh b/easyinstall.sh index 33c2fdb6..21b1d5a7 100755 --- a/easyinstall.sh +++ b/easyinstall.sh @@ -47,9 +47,14 @@ echo -e $logo } CONNECT_TYPE="FULLSPEC" -# clang v11 is the latest distributed by Buster -COMPILER="clang++-11" -CORES=1 +COMPILER="clang++" +MEM=$(grep MemTotal /proc/meminfo | awk '{print $2}') +CORES=`expr $MEM / 450000` +if [ $CORES -gt $(nproc) ]; then + CORES=$(nproc) +elif [ $CORES -lt 1 ]; then + CORES=1 +fi USER=$(whoami) BASE=$(dirname "$(readlink -f "${0}")") CPP_PATH="$BASE/cpp" @@ -71,8 +76,8 @@ SECRET_FILE="$HOME/.config/piscsi/secret" FILE_SHARE_PATH="$HOME/shared_files" FILE_SHARE_NAME="Pi File Server" -APT_PACKAGES_COMMON="build-essential git protobuf-compiler bridge-utils" -APT_PACKAGES_BACKEND="libspdlog-dev libpcap-dev libprotobuf-dev protobuf-compiler libgmock-dev clang-11" +APT_PACKAGES_COMMON="build-essential git protobuf-compiler bridge-utils ca-certificates" +APT_PACKAGES_BACKEND="libspdlog-dev libpcap-dev libprotobuf-dev protobuf-compiler libgmock-dev clang" APT_PACKAGES_PYTHON="python3 python3-dev python3-pip python3-venv python3-setuptools python3-wheel libev-dev libevdev2" APT_PACKAGES_WEB="nginx-light genisoimage man2html hfsutils dosfstools kpartx unzip unar disktype gettext" @@ -106,7 +111,15 @@ function sudoCheck() { function deleteFile() { if sudo test -f "$1/$2"; then sudo rm "$1/$2" || exit 1 - echo "Deleted $1/$2" + echo "Deleted file $1/$2" + fi +} + +# Delete dir if it exists +function deleteDir() { + if sudo test -d "$1"; then + sudo rm -rf "$1" || exit 1 + echo "Deleted directory $1" fi } @@ -189,8 +202,10 @@ function installPiscsi() { # install sudo make install CONNECT_TYPE="$CONNECT_TYPE" /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" requirements.txt +``` + +## Static analysis and formatting + +The CI workflow is set up to check code formatting with `black`, +and linting with `flake8`. If non-conformant code is found, the CI job +will fail. + +Before checking in new code, install the development packages and run +these two tools locally. + +``` +pip install -r web/requirements-dev.txt +``` + +Note that `black` only works correctly if you run it in the root of the +`python/` dir: + +``` +cd python +black . +``` + +Optionally: It is recommended to run pylint against new code to protect against bugs and keep the code readable and maintainable. The local pylint configuration lives in .pylintrc. In order for pylint to recognize venv libraries, the pylint-venv package is required. diff --git a/python/common/requirements.txt b/python/common/requirements.txt index 3a69e2ed..658ef8fe 100644 --- a/python/common/requirements.txt +++ b/python/common/requirements.txt @@ -1,2 +1,7 @@ +certifi==2023.7.22 +charset-normalizer==3.3.2 +idna==3.4 protobuf==3.19.5 -requests==2.26.0 +requests==2.31.0 +urllib3==2.0.7 +vcgencmd==0.1.1 diff --git a/python/common/src/piscsi/file_cmds.py b/python/common/src/piscsi/file_cmds.py index a64298ae..08666f75 100644 --- a/python/common/src/piscsi/file_cmds.py +++ b/python/common/src/piscsi/file_cmds.py @@ -5,7 +5,6 @@ Module for methods reading from and writing to the file system import logging import asyncio from os import walk, path -from functools import lru_cache from pathlib import PurePath, Path from zipfile import ZipFile, is_zipfile from subprocess import run, Popen, PIPE, CalledProcessError, TimeoutExpired @@ -17,23 +16,22 @@ from re import search import requests -import piscsi_interface_pb2 as proto from piscsi.common_settings import ( CFG_DIR, CONFIG_FILE_SUFFIX, PROPERTIES_SUFFIX, - ARCHIVE_FILE_SUFFIXES, RESERVATIONS, SHELL_ERROR, ) from piscsi.piscsi_cmds import PiscsiCmds from piscsi.return_codes import ReturnCodes -from piscsi.socket_cmds import SocketCmds from util import unarchiver FILE_READ_ERROR = "Unhandled exception when reading file: %s" FILE_WRITE_ERROR = "Unhandled exception when writing to file: %s" URL_SAFE = "/:?&" +# Common file sharing protocol meta data dirs to filter out from target upload dirs +EXCLUDED_DIRS = ["Network Trash Folder", "Temporary Items", "TheVolumeSettingsFolder"] class FileCmds: @@ -41,18 +39,8 @@ class FileCmds: class for methods reading from and writing to the file system """ - def __init__(self, sock_cmd: SocketCmds, piscsi: PiscsiCmds, token=None, locale=None): - self.sock_cmd = sock_cmd + def __init__(self, piscsi: PiscsiCmds): self.piscsi = piscsi - self.token = token - self.locale = locale - - def send_pb_command(self, command): - if logging.getLogger().isEnabledFor(logging.DEBUG): - # TODO: Uncouple/move to common dependency - logging.debug(self.piscsi.format_pb_command(command)) - - return self.sock_cmd.send_pb_command(command.SerializeToString()) # noinspection PyMethodMayBeStatic def list_config_files(self): @@ -74,89 +62,18 @@ class FileCmds: Returns a (list) of (str) subdir_list. """ subdir_list = [] - # Filter out file sharing meta data dirs - excluded_dirs = ("Network Trash Folder", "Temporary Items", "TheVolumeSettingsFolder") for root, dirs, _files in walk(directory, topdown=True): # Strip out dirs that begin with . - dirs[:] = [d for d in dirs if not d[0] == "."] + dirs[:] = [d for d in dirs if d[0] != "."] for dir in dirs: - if dir not in excluded_dirs: + if dir not in EXCLUDED_DIRS: dirpath = path.join(root, dir) - subdir_list.append(dirpath.replace(directory, "", 1)) + # Remove the section of the path up until the first subdir + subdir_list.append(dirpath.replace(directory + "/", "", 1)) subdir_list.sort() return subdir_list - def list_images(self): - """ - Sends a IMAGE_FILES_INFO command to the server - Returns a (dict) with (bool) status, (str) msg, and (list) of (dict)s files - - """ - command = proto.PbCommand() - command.operation = proto.PbOperation.DEFAULT_IMAGE_FILES_INFO - command.params["token"] = self.token - command.params["locale"] = self.locale - - data = self.send_pb_command(command) - result = proto.PbResult() - result.ParseFromString(data) - - server_info = self.piscsi.get_server_info() - files = [] - for file in result.image_files_info.image_files: - prop_file_path = Path(CFG_DIR) / f"{file.name}.{PROPERTIES_SUFFIX}" - # Add properties meta data for the image, if matching prop file is found - if prop_file_path.exists(): - process = self.read_drive_properties(prop_file_path) - prop = process["conf"] - else: - prop = False - - archive_contents = [] - if PurePath(file.name).suffix.lower()[1:] in ARCHIVE_FILE_SUFFIXES: - try: - archive_info = self._get_archive_info( - f"{server_info['image_dir']}/{file.name}", - _cache_extra_key=file.size, - ) - - properties_files = [ - x["path"] - for x in archive_info["members"] - if x["path"].endswith(PROPERTIES_SUFFIX) - ] - - for member in archive_info["members"]: - if member["is_dir"] or member["is_resource_fork"]: - continue - - if PurePath(member["path"]).suffix.lower()[1:] == PROPERTIES_SUFFIX: - member["is_properties_file"] = True - elif f"{member['path']}.{PROPERTIES_SUFFIX}" in properties_files: - member[ - "related_properties_file" - ] = f"{member['path']}.{PROPERTIES_SUFFIX}" - - archive_contents.append(member) - except (unarchiver.LsarCommandError, unarchiver.LsarOutputError): - pass - - size_mb = "{:,.1f}".format(file.size / 1024 / 1024) - dtype = proto.PbDeviceType.Name(file.type) - files.append( - { - "name": file.name, - "size": file.size, - "size_mb": size_mb, - "detected_type": dtype, - "prop": prop, - "archive_contents": archive_contents, - } - ) - - return {"status": result.status, "msg": result.msg, "files": files} - # noinspection PyMethodMayBeStatic def delete_file(self, file_path): """ @@ -572,12 +489,12 @@ class FileCmds: iso_filename = Path(server_info["image_dir"]) / f"{file_name}.iso" with TemporaryDirectory() as tmp_dir: - req_proc = self.download_to_dir(quote(url, safe=URL_SAFE), tmp_dir, file_name) + tmp_full_path = Path(tmp_dir) / file_name + req_proc = self.download_to_dir(quote(url, safe=URL_SAFE), tmp_full_path) logging.info("Downloaded %s to %s", file_name, tmp_dir) if not req_proc["status"]: return {"status": False, "msg": req_proc["msg"]} - tmp_full_path = Path(tmp_dir) / file_name if is_zipfile(tmp_full_path): if "XtraStuf.mac" in str(ZipFile(str(tmp_full_path)).namelist()): logging.info( @@ -649,9 +566,9 @@ class FileCmds: } # noinspection PyMethodMayBeStatic - def download_to_dir(self, url, save_dir, file_name): + def download_to_dir(self, url, target_path): """ - Takes (str) url, (str) save_dir, (str) file_name + Takes (str) url, (Path) target_path Returns (dict) with (bool) status and (str) msg """ logging.info("Making a request to download %s", url) @@ -664,7 +581,7 @@ class FileCmds: ) as req: req.raise_for_status() try: - with open(f"{save_dir}/{file_name}", "wb") as download: + with open(str(target_path), "wb") as download: for chunk in req.iter_content(chunk_size=8192): download.write(chunk) except FileNotFoundError as error: @@ -677,7 +594,7 @@ class FileCmds: logging.info("Response content-type: %s", req.headers["content-type"]) logging.info("Response status code: %s", req.status_code) - parameters = {"file_name": file_name, "save_dir": save_dir} + parameters = {"target_path": str(target_path)} return { "status": True, "return_code": ReturnCodes.DOWNLOADTODIR_SUCCESS, @@ -892,15 +809,3 @@ class FileCmds: logging.info("stderr: %s", stderr) return {"returncode": proc.returncode, "stdout": stdout, "stderr": stderr} - - # noinspection PyMethodMayBeStatic - @lru_cache(maxsize=32) - def _get_archive_info(self, file_path, **kwargs): - """ - Cached wrapper method to improve performance, e.g. on index screen - """ - try: - return unarchiver.inspect_archive(file_path) - except (unarchiver.LsarCommandError, unarchiver.LsarOutputError) as error: - logging.error(str(error)) - raise diff --git a/python/common/src/piscsi/piscsi_cmds.py b/python/common/src/piscsi/piscsi_cmds.py index 2f7b16fc..408f8b76 100644 --- a/python/common/src/piscsi/piscsi_cmds.py +++ b/python/common/src/piscsi/piscsi_cmds.py @@ -2,10 +2,21 @@ Module for commands sent to the PiSCSI backend service. """ +import logging +from pathlib import PurePath, Path +from functools import lru_cache + import piscsi_interface_pb2 as proto from piscsi.return_codes import ReturnCodes from piscsi.socket_cmds import SocketCmds -import logging + +from piscsi.common_settings import ( + CFG_DIR, + PROPERTIES_SUFFIX, + ARCHIVE_FILE_SUFFIXES, +) + +from util import unarchiver class PiscsiCmds: @@ -24,6 +35,79 @@ class PiscsiCmds: return self.sock_cmd.send_pb_command(command.SerializeToString()) + def list_images(self): + """ + Sends a IMAGE_FILES_INFO command to the server + Returns a (dict) with (bool) status, (str) msg, and (list) of (dict)s files + """ + from piscsi.file_cmds import FileCmds + + self.file_cmd = FileCmds(piscsi=self) + + command = proto.PbCommand() + command.operation = proto.PbOperation.DEFAULT_IMAGE_FILES_INFO + command.params["token"] = self.token + command.params["locale"] = self.locale + + data = self.send_pb_command(command) + result = proto.PbResult() + result.ParseFromString(data) + + server_info = self.get_server_info() + files = [] + for file in result.image_files_info.image_files: + prop_file_path = Path(CFG_DIR) / f"{file.name}.{PROPERTIES_SUFFIX}" + # Add properties meta data for the image, if matching prop file is found + if prop_file_path.exists(): + process = self.file_cmd.read_drive_properties(prop_file_path) + prop = process["conf"] + else: + prop = False + + archive_contents = [] + if PurePath(file.name).suffix.lower()[1:] in ARCHIVE_FILE_SUFFIXES: + try: + archive_info = self._get_archive_info( + f"{server_info['image_dir']}/{file.name}", + _cache_extra_key=file.size, + ) + + properties_files = [ + x["path"] + for x in archive_info["members"] + if x["path"].endswith(PROPERTIES_SUFFIX) + ] + + for member in archive_info["members"]: + if member["is_dir"] or member["is_resource_fork"]: + continue + + if PurePath(member["path"]).suffix.lower()[1:] == PROPERTIES_SUFFIX: + member["is_properties_file"] = True + elif f"{member['path']}.{PROPERTIES_SUFFIX}" in properties_files: + member[ + "related_properties_file" + ] = f"{member['path']}.{PROPERTIES_SUFFIX}" + + archive_contents.append(member) + except (unarchiver.LsarCommandError, unarchiver.LsarOutputError): + pass + + size_mb = "{:,.1f}".format(file.size / 1024 / 1024) + dtype = proto.PbDeviceType.Name(file.type) + files.append( + { + "name": file.name, + "size": file.size, + "size_mb": size_mb, + "detected_type": dtype, + "prop": prop, + "archive_contents": archive_contents, + } + ) + + return {"status": result.status, "msg": result.msg, "files": files} + def get_server_info(self): """ Sends a SERVER_INFO command to the server. @@ -521,3 +605,15 @@ class PiscsiCmds: message += f", device: {formatted_device}" return message + + # noinspection PyMethodMayBeStatic + @lru_cache(maxsize=32) + def _get_archive_info(self, file_path, **kwargs): + """ + Cached wrapper method to improve performance, e.g. on index screen + """ + try: + return unarchiver.inspect_archive(file_path) + except (unarchiver.LsarCommandError, unarchiver.LsarOutputError) as error: + logging.error(str(error)) + raise diff --git a/python/common/src/piscsi/return_codes.py b/python/common/src/piscsi/return_codes.py index cfd8265c..25cd2392 100644 --- a/python/common/src/piscsi/return_codes.py +++ b/python/common/src/piscsi/return_codes.py @@ -27,3 +27,11 @@ class ReturnCodes: EXTRACTIMAGE_NO_FILES_SPECIFIED = 91 EXTRACTIMAGE_NO_FILES_EXTRACTED = 92 EXTRACTIMAGE_COMMAND_ERROR = 93 + UNDER_VOLTAGE_DETECTED = 100 + ARM_FREQUENCY_CAPPED = 101 + CURRENTLY_THROTTLED = 102 + SOFT_TEMPERATURE_LIMIT_ACTIVE = 103 + UNDER_VOLTAGE_HAS_OCCURRED = 116 + ARM_FREQUENCY_CAPPING_HAS_OCCURRED = 117 + THROTTLING_HAS_OCCURRED = 118 + SOFT_TEMPERATURE_LIMIT_HAS_OCCURRED = 119 diff --git a/python/common/src/piscsi/sys_cmds.py b/python/common/src/piscsi/sys_cmds.py index 0da185d8..37c85f4a 100644 --- a/python/common/src/piscsi/sys_cmds.py +++ b/python/common/src/piscsi/sys_cmds.py @@ -3,6 +3,7 @@ Module with methods that interact with the Pi system """ import subprocess import logging +import sys from subprocess import run, CalledProcessError from shutil import disk_usage from re import findall, match @@ -10,6 +11,12 @@ from socket import socket, gethostname, AF_INET, SOCK_DGRAM from pathlib import Path from platform import uname +try: + from vcgencmd import Vcgencmd +except ImportError: + pass + +from piscsi.return_codes import ReturnCodes from piscsi.common_settings import SHELL_ERROR @@ -102,12 +109,12 @@ class SysCmds: return False @staticmethod - def disk_space(): + def disk_space(path): """ - Returns a (dict) with (int) total (int) used (int) free - This is the disk space information of the volume where this app is running + Takes (str) path with the path to the dir / mount point to inspect + Returns a (dict) with (int) total (int) used (int) free disk space """ - total, used, free = disk_usage(__file__) + total, used, free = disk_usage(path) return {"total": total, "used": used, "free": free} @staticmethod @@ -263,3 +270,40 @@ class SysCmds: return process.returncode, process.stdout.decode("utf-8") return process.returncode, process.stderr.decode("utf-8") + + @staticmethod + def get_throttled(enabled_modes, test_modes): + """ + Takes (list) enabled_modes & (list) test_modes parameters & returns a + tuple of (str) category & (str) message. + + enabled_modes is a list of modes that will be enabled for display if + they're triggered. test_modes works similarly to enabled_mode but will + ALWAYS display the modes listed for troubleshooting styling. + """ + if "vcgencmd" in sys.modules: + vcgcmd = Vcgencmd() + t_states = vcgcmd.get_throttled()["breakdown"] + matched_states = [] + + state_msgs = { + "0": ("error", ReturnCodes.UNDER_VOLTAGE_DETECTED), + "1": ("warning", ReturnCodes.ARM_FREQUENCY_CAPPED), + "2": ("error", ReturnCodes.CURRENTLY_THROTTLED), + "3": ("warning", ReturnCodes.SOFT_TEMPERATURE_LIMIT_ACTIVE), + "16": ("warning", ReturnCodes.UNDER_VOLTAGE_HAS_OCCURRED), + "17": ("warning", ReturnCodes.ARM_FREQUENCY_CAPPING_HAS_OCCURRED), + "18": ("warning", ReturnCodes.THROTTLING_HAS_OCCURRED), + "19": ("warning", ReturnCodes.SOFT_TEMPERATURE_LIMIT_HAS_OCCURRED), + } + + for k in t_states: + if t_states[k] and k in enabled_modes: + matched_states.append(state_msgs[k]) + + for t in test_modes: + matched_states.append(state_msgs[t]) + + return matched_states + else: + return [] diff --git a/python/ctrlboard/requirements.txt b/python/ctrlboard/requirements.txt index 4eb9c68e..be85c311 100644 --- a/python/ctrlboard/requirements.txt +++ b/python/ctrlboard/requirements.txt @@ -2,7 +2,7 @@ #adafruit-circuitpython-framebuf==1.4.8 #adafruit-circuitpython-ssd1306==2.12.3 luma-oled==3.8.1 -Pillow==9.3.0 +Pillow==10.0.1 RPi.GPIO==0.7.0 protobuf==3.19.5 unidecode==1.3.2 diff --git a/python/ctrlboard/service-infra/piscsi-ctrlboard.service b/python/ctrlboard/service-infra/piscsi-ctrlboard.service index 3eb9de72..0c336d40 100644 --- a/python/ctrlboard/service-infra/piscsi-ctrlboard.service +++ b/python/ctrlboard/service-infra/piscsi-ctrlboard.service @@ -1,6 +1,6 @@ [Unit] Description=PiSCSI Control Board service -After=network.target piscsi.service +After=network-online.target piscsi.service [Service] Type=simple diff --git a/python/ctrlboard/src/ctrlboard_event_handler/ctrlboard_menu_update_event_handler.py b/python/ctrlboard/src/ctrlboard_event_handler/ctrlboard_menu_update_event_handler.py index 6b48d9f7..768e996f 100644 --- a/python/ctrlboard/src/ctrlboard_event_handler/ctrlboard_menu_update_event_handler.py +++ b/python/ctrlboard/src/ctrlboard_event_handler/ctrlboard_menu_update_event_handler.py @@ -107,8 +107,7 @@ class CtrlBoardMenuUpdateEventHandler(Observer): except AttributeError: log = logging.getLogger(__name__) log.error( - "Handler function [%s] not found or returned an error. Skipping.", - str(handler_function_name), + "Handler function not found or returned an error. Skipping.", ) # noinspection PyUnusedLocal diff --git a/python/ctrlboard/src/ctrlboard_menu_builder.py b/python/ctrlboard/src/ctrlboard_menu_builder.py index 25075f1d..3f322131 100644 --- a/python/ctrlboard/src/ctrlboard_menu_builder.py +++ b/python/ctrlboard/src/ctrlboard_menu_builder.py @@ -8,7 +8,7 @@ from piscsi.piscsi_cmds import PiscsiCmds class CtrlBoardMenuBuilder(MenuBuilder): - """Class fgor building the control board UI specific menus""" + """Class for building the control board UI specific menus""" SCSI_ID_MENU = "scsi_id_menu" ACTION_MENU = "action_menu" @@ -48,7 +48,7 @@ class CtrlBoardMenuBuilder(MenuBuilder): return self.create_device_info_menu(context_object) log = logging.getLogger(__name__) - log.warning("Provided menu name [%s] cannot be built!", name) + log.error("Provided menu name cannot be built!") return self.create_scsi_id_list_menu(context_object) @@ -142,7 +142,7 @@ class CtrlBoardMenuBuilder(MenuBuilder): def create_images_menu(self, context_object=None): """Creates a sub menu showing all the available images""" menu = Menu(CtrlBoardMenuBuilder.IMAGES_MENU) - images_info = self.file_cmd.list_images() + images_info = self.piscsi_cmd.list_images() menu.add_entry("Return", {"context": self.IMAGES_MENU, "action": self.ACTION_RETURN}) images = images_info["files"] sorted_images = sorted(images, key=lambda d: d["name"]) diff --git a/python/oled/requirements.txt b/python/oled/requirements.txt index 139b09f5..7d240e48 100644 --- a/python/oled/requirements.txt +++ b/python/oled/requirements.txt @@ -1,5 +1,15 @@ -pkg-resources==0.0.0 +Adafruit-Blinka==8.24.0 +adafruit-circuitpython-busdevice==5.2.6 +adafruit-circuitpython-framebuf==1.6.4 +adafruit-circuitpython-requests==2.0.2 adafruit-circuitpython-ssd1306==2.12.11 -Pillow==9.3.0 +adafruit-circuitpython-typing==1.9.5 +Adafruit-PlatformDetect==3.53.0 +Adafruit-PureIO==1.1.11 +Pillow==10.0.1 protobuf==3.20.2 -unidecode==1.3.6 +pyftdi==0.55.0 +pyserial==3.5 +pyusb==1.2.1 +typing_extensions==4.8.0 +Unidecode==1.3.6 diff --git a/python/oled/resources/splash_start_32.bmp b/python/oled/resources/splash_start_32.bmp index 5c7549ed..e3493eca 100644 Binary files a/python/oled/resources/splash_start_32.bmp and b/python/oled/resources/splash_start_32.bmp differ diff --git a/python/oled/resources/splash_start_64.bmp b/python/oled/resources/splash_start_64.bmp index 4448f28d..3316e0d7 100644 Binary files a/python/oled/resources/splash_start_64.bmp and b/python/oled/resources/splash_start_64.bmp differ diff --git a/python/oled/service-infra/piscsi-oled.service b/python/oled/service-infra/piscsi-oled.service index a339e6a2..9be68433 100644 --- a/python/oled/service-infra/piscsi-oled.service +++ b/python/oled/service-infra/piscsi-oled.service @@ -1,6 +1,6 @@ [Unit] Description=PiSCSI-OLED Monitor service -After=network.target piscsi.service +After=network-online.target piscsi.service [Service] Type=simple diff --git a/python/pyproject.toml b/python/pyproject.toml index aea91ba3..0ede3f46 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -1,4 +1,4 @@ [tool.black] line-length = 100 -target-version = ['py37', 'py38', 'py39'] +target-version = ['py39', 'py310', 'py311'] extend-exclude = ".*_pb2.py" diff --git a/python/web/.stylelintrc.json b/python/web/.stylelintrc.json index d7b63129..8bd7861c 100644 --- a/python/web/.stylelintrc.json +++ b/python/web/.stylelintrc.json @@ -1,6 +1,7 @@ { - "extends": ["stylelint-config-standard", "stylelint-config-prettier"], + "extends": ["stylelint-config-standard"], "rules": { - "no-descending-specificity": null + "no-descending-specificity": null, + "media-feature-range-notation": "prefix" } -} \ No newline at end of file +} diff --git a/python/web/mock/bin/vcgencmd b/python/web/mock/bin/vcgencmd new file mode 100644 index 00000000..ce21e8e2 --- /dev/null +++ b/python/web/mock/bin/vcgencmd @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +# Info: https://www.raspberrypi.com/documentation/computers/os.html#vcgencmd +# +# Bit Hex value Meaning +# ----- ----------- ------------------------ +# 0 0x1 Under-voltage detected +# 1 0x2 Arm frequency capped +# 2 0x4 Currently throttled +# 3 0x8 Soft temperature limit active +# 16 0x10000 Under-voltage has occurred +# 17 0x20000 Arm frequency capping has occurred +# 18 0x40000 Throttling has occurred +# 19 0x80000 Soft temperature limit has occurred + +if [[ "$1" == "get_throttled" ]] +then + # Return 'Under-voltage detected' & 'Under-voltage has occurred' + echo "throttled=0x10001" +fi + +echo "Mock does not recognize: $0 $@" +exit 1 \ No newline at end of file diff --git a/python/web/package-lock.json b/python/web/package-lock.json index d2bae6b7..b2460de6 100644 --- a/python/web/package-lock.json +++ b/python/web/package-lock.json @@ -1,44 +1,43 @@ { "name": "web", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { "devDependencies": { - "prettier": "2.7.1", - "stylelint": "^14.14.1", - "stylelint-config-prettier": "^9.0.3", - "stylelint-config-standard": "^29.0.0" + "prettier": "3.0.0", + "stylelint": "^15.10.2", + "stylelint-config-standard": "^34.0.0" } }, "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-validator-identifier": "^7.22.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, @@ -46,21 +45,84 @@ "node": ">=6.9.0" } }, - "node_modules/@csstools/selector-specificity": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz", - "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==", + "node_modules/@csstools/css-parser-algorithms": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.3.0.tgz", + "integrity": "sha512-dTKSIHHWc0zPvcS5cqGP+/TPFUJB0ekJ9dGKvMAFoNuBFhDPBt9OMGNZiIA5vTiNdGHHBeScYPXIGBMnVOahsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^2.1.1" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.1.1.tgz", + "integrity": "sha512-GbrTj2Z8MCTUv+52GE0RbFGM527xuXZ0Xa5g0Z+YN573uveS4G0qi6WNOMyz3yrFM/jaILTTwJ0+umx81EzqfA==", "dev": true, "engines": { - "node": "^12 || ^14 || >=16" + "node": "^14 || ^16 || >=18" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/csstools" + } + }, + "node_modules/@csstools/media-query-list-parser": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.2.tgz", + "integrity": "sha512-M8cFGGwl866o6++vIY7j1AKuq9v57cf+dGepScwCcbut9ypJNr4Cj+LLTWligYUZ0uyhEoJDKt5lvyBfh2L3ZQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "postcss": "^8.2", - "postcss-selector-parser": "^6.0.10" + "@csstools/css-parser-algorithms": "^2.3.0", + "@csstools/css-tokenizer": "^2.1.1" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.0.tgz", + "integrity": "sha512-hBI9tfBtuPIi885ZsZ32IMEU/5nlZH/KOVYJCOh7gyMxaVLGmLedYqFN6Ui1LXkI8JlC8IsuC0rF0btcRZKd5g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^6.0.13" } }, "node_modules/@nodelib/fs.scandir": { @@ -110,12 +172,6 @@ "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "dev": true }, - "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true - }, "node_modules/ajv": { "version": "8.11.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", @@ -153,6 +209,12 @@ "node": ">=4" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -224,26 +286,30 @@ } }, "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-7.0.2.tgz", + "integrity": "sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==", "dev": true, "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" + "camelcase": "^6.3.0", + "map-obj": "^4.1.0", + "quick-lru": "^5.1.1", + "type-fest": "^1.2.1" }, "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -291,30 +357,45 @@ "dev": true }, "node_modules/cosmiconfig": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", - "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", + "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==", "dev": true, "dependencies": { - "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" + "path-type": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" } }, "node_modules/css-functions-list": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.1.0.tgz", - "integrity": "sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.0.tgz", + "integrity": "sha512-d/jBMPyYybkkLVypgtGv12R+pIFw4/f/IHtCTxWpZc8ofTYOPigIgmA6vu5rMHartZC+WuXhBUHfnyNUIQSYrg==", "dev": true, "engines": { "node": ">=12.22" } }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -345,12 +426,15 @@ } }, "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-5.0.1.tgz", + "integrity": "sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/decamelize-keys": { @@ -369,6 +453,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/decamelize-keys/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/decamelize-keys/node_modules/map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", @@ -421,9 +514,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz", + "integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -479,16 +572,19 @@ } }, "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "dependencies": { - "locate-path": "^5.0.0", + "locate-path": "^6.0.0", "path-exists": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/flat-cache": { @@ -649,9 +745,9 @@ } }, "node_modules/html-tags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz", - "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", "dev": true, "engines": { "node": ">=8" @@ -661,9 +757,9 @@ } }, "node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true, "engines": { "node": ">= 4" @@ -713,12 +809,15 @@ } }, "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", "dev": true, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/inflight": { @@ -750,9 +849,9 @@ "dev": true }, "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", "dev": true, "dependencies": { "has": "^1.0.3" @@ -830,6 +929,18 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -852,9 +963,9 @@ } }, "node_modules/known-css-properties": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.26.0.tgz", - "integrity": "sha512-5FZRzrZzNTBruuurWpvZnvP9pum+fe0HcK8z/ooo+U+Hmp4vtbyp1/QDsqmufirXy4egGzbaH/y2uCZf+6W5Kg==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.27.0.tgz", + "integrity": "sha512-uMCj6+hZYDoffuvAJjFAPz56E9uoowFHmTkqRtRq5WyC5Q6Cu/fTZKNQpX/RbzChBYLLl3lo8CjFZBAZXq9qFg==", "dev": true }, "node_modules/lines-and-columns": { @@ -864,15 +975,18 @@ "dev": true }, "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "dependencies": { - "p-locate": "^4.1.0" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lodash.truncate": { @@ -915,27 +1029,33 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true + }, "node_modules/meow": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", - "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", + "version": "10.1.5", + "resolved": "https://registry.npmjs.org/meow/-/meow-10.1.5.tgz", + "integrity": "sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==", "dev": true, "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize": "^1.2.0", + "@types/minimist": "^1.2.2", + "camelcase-keys": "^7.0.0", + "decamelize": "^5.0.0", "decamelize-keys": "^1.1.0", "hard-rejection": "^2.1.0", "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" + "normalize-package-data": "^3.0.2", + "read-pkg-up": "^8.0.0", + "redent": "^4.0.0", + "trim-newlines": "^4.0.2", + "type-fest": "^1.2.2", + "yargs-parser": "^20.2.9" }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -1005,10 +1125,16 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -1050,39 +1176,33 @@ } }, "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "dependencies": { - "p-try": "^2.0.0" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "dependencies": { - "p-limit": "^2.2.0" + "p-limit": "^3.0.2" }, "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/parent-module": { @@ -1133,12 +1253,6 @@ "node": ">=0.10.0" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -1167,9 +1281,9 @@ } }, "node_modules/postcss": { - "version": "8.4.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", - "integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "dev": true, "funding": [ { @@ -1179,10 +1293,14 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "nanoid": "^3.3.4", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -1190,12 +1308,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss-media-query-parser": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", - "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", - "dev": true - }, "node_modules/postcss-resolve-nested-selector": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", @@ -1219,9 +1331,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", - "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -1238,15 +1350,15 @@ "dev": true }, "node_modules/prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz", + "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==", "dev": true, "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" @@ -1282,102 +1394,66 @@ ] }, "node_modules/quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", "dev": true, "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg/node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "node_modules/read-pkg": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-6.0.0.tgz", + "integrity": "sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==", "dev": true, "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/read-pkg/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true, + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^3.0.2", + "parse-json": "^5.2.0", + "type-fest": "^1.0.1" + }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-8.0.0.tgz", + "integrity": "sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==", + "dev": true, + "dependencies": { + "find-up": "^5.0.0", + "read-pkg": "^6.0.0", + "type-fest": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-4.0.0.tgz", + "integrity": "sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==", "dev": true, "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" + "indent-string": "^5.0.0", + "strip-indent": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/require-from-string": { @@ -1389,23 +1465,6 @@ "node": ">=0.10.0" } }, - "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -1464,9 +1523,9 @@ } }, "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -1479,10 +1538,16 @@ } }, "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz", + "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/slash": { "version": "3.0.0", @@ -1553,9 +1618,9 @@ } }, "node_modules/spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "dev": true, "dependencies": { "spdx-expression-parse": "^3.0.0", @@ -1579,9 +1644,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", - "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", + "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", "dev": true }, "node_modules/string-width": { @@ -1611,15 +1676,18 @@ } }, "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.0.0.tgz", + "integrity": "sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==", "dev": true, "dependencies": { - "min-indent": "^1.0.0" + "min-indent": "^1.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/style-search": { @@ -1629,96 +1697,88 @@ "dev": true }, "node_modules/stylelint": { - "version": "14.14.1", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.14.1.tgz", - "integrity": "sha512-Jnftu+lSD8cSpcV/+Z2nfgfgFpTIS1FcujezXPngtoIQ6wtwutL22MsNE0dJuMiM1h1790g2qIjAyUZCMrX4sw==", + "version": "15.10.2", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.10.2.tgz", + "integrity": "sha512-UxqSb3hB74g4DTO45QhUHkJMjKKU//lNUAOWyvPBVPZbCknJ5HjOWWZo+UDuhHa9FLeVdHBZXxu43eXkjyIPWg==", "dev": true, "dependencies": { - "@csstools/selector-specificity": "^2.0.2", + "@csstools/css-parser-algorithms": "^2.3.0", + "@csstools/css-tokenizer": "^2.1.1", + "@csstools/media-query-list-parser": "^2.1.2", + "@csstools/selector-specificity": "^3.0.0", "balanced-match": "^2.0.0", "colord": "^2.9.3", - "cosmiconfig": "^7.0.1", - "css-functions-list": "^3.1.0", + "cosmiconfig": "^8.2.0", + "css-functions-list": "^3.2.0", + "css-tree": "^2.3.1", "debug": "^4.3.4", - "fast-glob": "^3.2.12", + "fast-glob": "^3.3.0", "fastest-levenshtein": "^1.0.16", "file-entry-cache": "^6.0.1", "global-modules": "^2.0.0", "globby": "^11.1.0", "globjoin": "^0.1.4", - "html-tags": "^3.2.0", - "ignore": "^5.2.0", + "html-tags": "^3.3.1", + "ignore": "^5.2.4", "import-lazy": "^4.0.0", "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", - "known-css-properties": "^0.26.0", + "known-css-properties": "^0.27.0", "mathml-tag-names": "^2.1.3", - "meow": "^9.0.0", + "meow": "^10.1.5", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "picocolors": "^1.0.0", - "postcss": "^8.4.18", - "postcss-media-query-parser": "^0.2.3", + "postcss": "^8.4.25", "postcss-resolve-nested-selector": "^0.1.1", "postcss-safe-parser": "^6.0.0", - "postcss-selector-parser": "^6.0.10", + "postcss-selector-parser": "^6.0.13", "postcss-value-parser": "^4.2.0", "resolve-from": "^5.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "style-search": "^0.1.0", - "supports-hyperlinks": "^2.3.0", + "supports-hyperlinks": "^3.0.0", "svg-tags": "^1.0.0", "table": "^6.8.1", - "v8-compile-cache": "^2.3.0", - "write-file-atomic": "^4.0.2" + "write-file-atomic": "^5.0.1" }, "bin": { - "stylelint": "bin/stylelint.js" + "stylelint": "bin/stylelint.mjs" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": "^14.13.1 || >=16.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/stylelint" } }, - "node_modules/stylelint-config-prettier": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/stylelint-config-prettier/-/stylelint-config-prettier-9.0.3.tgz", - "integrity": "sha512-5n9gUDp/n5tTMCq1GLqSpA30w2sqWITSSEiAWQlpxkKGAUbjcemQ0nbkRvRUa0B1LgD3+hCvdL7B1eTxy1QHJg==", - "dev": true, - "bin": { - "stylelint-config-prettier": "bin/check.js", - "stylelint-config-prettier-check": "bin/check.js" - }, - "engines": { - "node": ">= 12" - }, - "peerDependencies": { - "stylelint": ">=11.0.0" - } - }, "node_modules/stylelint-config-recommended": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-9.0.0.tgz", - "integrity": "sha512-9YQSrJq4NvvRuTbzDsWX3rrFOzOlYBmZP+o513BJN/yfEmGSr0AxdvrWs0P/ilSpVV/wisamAHu5XSk8Rcf4CQ==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-13.0.0.tgz", + "integrity": "sha512-EH+yRj6h3GAe/fRiyaoO2F9l9Tgg50AOFhaszyfov9v6ayXJ1IkSHwTxd7lB48FmOeSGDPLjatjO11fJpmarkQ==", "dev": true, + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, "peerDependencies": { - "stylelint": "^14.10.0" + "stylelint": "^15.10.0" } }, "node_modules/stylelint-config-standard": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-29.0.0.tgz", - "integrity": "sha512-uy8tZLbfq6ZrXy4JKu3W+7lYLgRQBxYTUUB88vPgQ+ZzAxdrvcaSUW9hOMNLYBnwH+9Kkj19M2DHdZ4gKwI7tg==", + "version": "34.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-34.0.0.tgz", + "integrity": "sha512-u0VSZnVyW9VSryBG2LSO+OQTjN7zF9XJaAJRX/4EwkmU0R2jYwmBSN10acqZisDitS0CLiEiGjX7+Hrq8TAhfQ==", "dev": true, "dependencies": { - "stylelint-config-recommended": "^9.0.0" + "stylelint-config-recommended": "^13.0.0" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" }, "peerDependencies": { - "stylelint": "^14.14.0" + "stylelint": "^15.10.0" } }, "node_modules/supports-color": { @@ -1734,16 +1794,16 @@ } }, "node_modules/supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz", + "integrity": "sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==", "dev": true, "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" }, "engines": { - "node": ">=8" + "node": ">=14.18" } }, "node_modules/supports-hyperlinks/node_modules/has-flag": { @@ -1767,18 +1827,6 @@ "node": ">=8" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/svg-tags": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", @@ -1814,18 +1862,21 @@ } }, "node_modules/trim-newlines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-4.1.1.tgz", + "integrity": "sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==", "dev": true, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", "dev": true, "engines": { "node": ">=10" @@ -1849,12 +1900,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -1884,16 +1929,16 @@ "dev": true }, "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", "dev": true, "dependencies": { "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" + "signal-exit": "^4.0.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/yallist": { @@ -1902,15 +1947,6 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/yargs-parser": { "version": "20.2.9", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", @@ -1919,1425 +1955,18 @@ "engines": { "node": ">=10" } - } - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "requires": { - "@babel/highlight": "^7.18.6" - } }, - "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", - "dev": true - }, - "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@csstools/selector-specificity": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz", - "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==", - "dev": true, - "requires": {} - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@types/minimist": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", - "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", - "dev": true - }, - "@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", - "dev": true - }, - "@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true - }, - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "dev": true - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, - "balanced-match": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", - "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - }, - "dependencies": { - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - } - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "cosmiconfig": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", - "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", - "dev": true, - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - } - }, - "css-functions-list": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.1.0.tgz", - "integrity": "sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w==", - "dev": true - }, - "cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true - }, - "decamelize-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", - "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", - "dev": true, - "requires": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "dependencies": { - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", - "dev": true - } - } - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - } - }, - "fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true - }, - "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "dev": true, - "requires": { - "global-prefix": "^3.0.0" - } - }, - "global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "dev": true, - "requires": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - } - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, - "globjoin": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", - "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", - "dev": true - }, - "hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "html-tags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz", - "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==", - "dev": true - }, - "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - } - } - }, - "import-lazy": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", - "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", - "dev": true - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", - "dev": true - }, - "is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "known-css-properties": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.26.0.tgz", - "integrity": "sha512-5FZRzrZzNTBruuurWpvZnvP9pum+fe0HcK8z/ooo+U+Hmp4vtbyp1/QDsqmufirXy4egGzbaH/y2uCZf+6W5Kg==", - "dev": true - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", - "dev": true - }, - "mathml-tag-names": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", - "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", - "dev": true - }, - "meow": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", - "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", - "dev": true, - "requires": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize": "^1.2.0", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - } - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "dev": true - }, - "normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dev": true, - "requires": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "postcss": { - "version": "8.4.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", - "integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==", - "dev": true, - "requires": { - "nanoid": "^3.3.4", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - } - }, - "postcss-media-query-parser": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", - "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", - "dev": true - }, - "postcss-resolve-nested-selector": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", - "integrity": "sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==", - "dev": true - }, - "postcss-safe-parser": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", - "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", - "dev": true, - "requires": {} - }, - "postcss-selector-parser": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", - "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", - "dev": true, - "requires": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - } - }, - "postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true - }, - "prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", - "dev": true - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "dev": true - }, - "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true - } - } - }, - "read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "dependencies": { - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - } - } - }, - "redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "requires": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - } - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true - }, - "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dev": true, - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } - } - }, - "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", - "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "requires": { - "min-indent": "^1.0.0" - } - }, - "style-search": { + "node_modules/yocto-queue": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", - "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==", - "dev": true - }, - "stylelint": { - "version": "14.14.1", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.14.1.tgz", - "integrity": "sha512-Jnftu+lSD8cSpcV/+Z2nfgfgFpTIS1FcujezXPngtoIQ6wtwutL22MsNE0dJuMiM1h1790g2qIjAyUZCMrX4sw==", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, - "requires": { - "@csstools/selector-specificity": "^2.0.2", - "balanced-match": "^2.0.0", - "colord": "^2.9.3", - "cosmiconfig": "^7.0.1", - "css-functions-list": "^3.1.0", - "debug": "^4.3.4", - "fast-glob": "^3.2.12", - "fastest-levenshtein": "^1.0.16", - "file-entry-cache": "^6.0.1", - "global-modules": "^2.0.0", - "globby": "^11.1.0", - "globjoin": "^0.1.4", - "html-tags": "^3.2.0", - "ignore": "^5.2.0", - "import-lazy": "^4.0.0", - "imurmurhash": "^0.1.4", - "is-plain-object": "^5.0.0", - "known-css-properties": "^0.26.0", - "mathml-tag-names": "^2.1.3", - "meow": "^9.0.0", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.18", - "postcss-media-query-parser": "^0.2.3", - "postcss-resolve-nested-selector": "^0.1.1", - "postcss-safe-parser": "^6.0.0", - "postcss-selector-parser": "^6.0.10", - "postcss-value-parser": "^4.2.0", - "resolve-from": "^5.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "style-search": "^0.1.0", - "supports-hyperlinks": "^2.3.0", - "svg-tags": "^1.0.0", - "table": "^6.8.1", - "v8-compile-cache": "^2.3.0", - "write-file-atomic": "^4.0.2" - } - }, - "stylelint-config-prettier": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/stylelint-config-prettier/-/stylelint-config-prettier-9.0.3.tgz", - "integrity": "sha512-5n9gUDp/n5tTMCq1GLqSpA30w2sqWITSSEiAWQlpxkKGAUbjcemQ0nbkRvRUa0B1LgD3+hCvdL7B1eTxy1QHJg==", - "dev": true, - "requires": {} - }, - "stylelint-config-recommended": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-9.0.0.tgz", - "integrity": "sha512-9YQSrJq4NvvRuTbzDsWX3rrFOzOlYBmZP+o513BJN/yfEmGSr0AxdvrWs0P/ilSpVV/wisamAHu5XSk8Rcf4CQ==", - "dev": true, - "requires": {} - }, - "stylelint-config-standard": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-29.0.0.tgz", - "integrity": "sha512-uy8tZLbfq6ZrXy4JKu3W+7lYLgRQBxYTUUB88vPgQ+ZzAxdrvcaSUW9hOMNLYBnwH+9Kkj19M2DHdZ4gKwI7tg==", - "dev": true, - "requires": { - "stylelint-config-recommended": "^9.0.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "dev": true, - "requires": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" + "engines": { + "node": ">=10" }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "svg-tags": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", - "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", - "dev": true - }, - "table": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", - "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", - "dev": true, - "requires": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "trim-newlines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", - "dev": true - }, - "type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", - "dev": true - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true - }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true } } } diff --git a/python/web/package.json b/python/web/package.json index 56ef0a57..5ff05b21 100644 --- a/python/web/package.json +++ b/python/web/package.json @@ -1,8 +1,7 @@ { "devDependencies": { - "prettier": "2.7.1", - "stylelint": "^14.14.1", - "stylelint-config-prettier": "^9.0.3", - "stylelint-config-standard": "^29.0.0" + "prettier": "3.0.0", + "stylelint": "^15.10.2", + "stylelint-config-standard": "^34.0.0" } } diff --git a/python/web/requirements-dev.txt b/python/web/requirements-dev.txt index 06ff7a82..f65d5f8c 100644 --- a/python/web/requirements-dev.txt +++ b/python/web/requirements-dev.txt @@ -3,4 +3,5 @@ pytest-httpserver==1.0.6 black==22.8.0 flake8==5.0.4 watchdog==2.1.9 -requests==2.28.1 +requests==2.31.0 +vcgencmd==0.1.1 diff --git a/python/web/requirements.txt b/python/web/requirements.txt index 83045903..88e3f57d 100644 --- a/python/web/requirements.txt +++ b/python/web/requirements.txt @@ -1,8 +1,17 @@ +Babel==2.13.1 bjoern==3.2.2 -Flask==2.2.2 +blinker==1.6.3 +charset-normalizer==2.1.1 +click==8.1.7 +Flask==3.0.0 +flask-babel==4.0.0 +itsdangerous==2.1.2 Jinja2==3.1.2 +MarkupSafe==2.1.3 protobuf==3.20.2 -requests==2.28.1 +pytz==2023.3.post1 +requests==2.31.0 simplepam==0.1.5 -flask_babel==2.0.0 -ua-parser==0.16.1 \ No newline at end of file +ua-parser==0.16.1 +vcgencmd==0.1.1 +Werkzeug==3.0.1 diff --git a/python/web/service-infra/piscsi-web.service b/python/web/service-infra/piscsi-web.service index cf7c8b85..3f4d7ed4 100644 --- a/python/web/service-infra/piscsi-web.service +++ b/python/web/service-infra/piscsi-web.service @@ -1,6 +1,6 @@ [Unit] Description=PiSCSI-Web service -After=network.target +After=network-online.target piscsi.service [Service] Type=simple diff --git a/python/web/src/return_code_mapper.py b/python/web/src/return_code_mapper.py index f6c6e273..dd5e43b9 100644 --- a/python/web/src/return_code_mapper.py +++ b/python/web/src/return_code_mapper.py @@ -23,7 +23,7 @@ class ReturnCodeMapper: ReturnCodes.DOWNLOADFILETOISO_SUCCESS: _("Created CD-ROM ISO image with arguments \"%(value)s\""), ReturnCodes.DOWNLOADTODIR_SUCCESS: - _("%(file_name)s downloaded to %(save_dir)s"), + _("Downloaded file to %(target_path)s"), ReturnCodes.WRITEFILE_SUCCESS: _("File created: %(target_path)s"), ReturnCodes.WRITEFILE_COULD_NOT_WRITE: @@ -50,6 +50,29 @@ class ReturnCodeMapper: _("No files were extracted (existing files are skipped)"), ReturnCodes.EXTRACTIMAGE_COMMAND_ERROR: _("Unable to extract archive: %(error)s"), + ReturnCodes.UNDER_VOLTAGE_DETECTED: + _("Potential instability - Under voltage detected - Make sure to use a sufficient " + "power source (2.5+ amps)."), + ReturnCodes.ARM_FREQUENCY_CAPPED: + _("Potential instability - ARM frequency capped - Ensure sufficient airflow/cooling."), + ReturnCodes.CURRENTLY_THROTTLED: + _("Potential instability - Currently throttled - Make sure to use a sufficient power " + "source (2.5+ amps)."), + ReturnCodes.SOFT_TEMPERATURE_LIMIT_ACTIVE: + _("Potential instability - Soft-temperature limit active - Ensure sufficient " + "airflow/cooling."), + ReturnCodes.UNDER_VOLTAGE_HAS_OCCURRED: + _("Potential instability - Under voltage has occurred since last reboot. Make sure " + "to use a sufficient power source (2.5+ amps)."), + ReturnCodes.ARM_FREQUENCY_CAPPING_HAS_OCCURRED: + _("Potential instability - ARM frequency capping has occurred since last reboot. " + "Ensure sufficient airflow/cooling."), + ReturnCodes.THROTTLING_HAS_OCCURRED: + _("Potential instability - Throttling has occurred since the last reboot. Make sure " + "to use a sufficient power source (2.5+ amps)."), + ReturnCodes.SOFT_TEMPERATURE_LIMIT_HAS_OCCURRED: + _("Potential instability - Soft temperature limit has occurred since last reboot. " + "Ensure sufficient airflow/cooling."), } # fmt: on diff --git a/python/web/src/settings.py b/python/web/src/settings.py index 3a2c4d73..5994c719 100644 --- a/python/web/src/settings.py +++ b/python/web/src/settings.py @@ -31,3 +31,19 @@ TEMPLATE_THEME_DEFAULT = "modern" # Fallback theme for older browsers TEMPLATE_THEME_LEGACY = "classic" + +# Enable throttle notifications +# +# Available modes: +# "0": "Under-voltage detected" +# "1": "Arm frequency capped" +# "2": "Currently throttled" +# "3": "Soft temperature limit active" +# "16": "Under-voltage has occurred" +# "17": "Arm frequency capping has occurred" +# "18": "Throttling has occurred" +# "19": "Soft temperature limit has occurred" +THROTTLE_NOTIFY_MODES = ["0", "16"] +# Include a list of modes to be shown ALL THE TIME to be used for styling +# and formatting. +THROTTLE_TEST_MODES = [] diff --git a/python/web/src/static/themes/classic/style.css b/python/web/src/static/themes/classic/style.css index 23b4ab88..d77de45a 100644 --- a/python/web/src/static/themes/classic/style.css +++ b/python/web/src/static/themes/classic/style.css @@ -13,12 +13,19 @@ form { display: inline; } -table, tr, td { +table, +tr, +td { border: 1px solid black; border-collapse: collapse; margin: none; } +th { + color: white; + background-color: black; +} + h1 { color: white; font-size: 20px; @@ -48,7 +55,7 @@ div.footer { } div.footer div.theme-change-hint { - margin-bottom: 15px; + margin-bottom: 15px; } div.logged-in { @@ -95,12 +102,16 @@ div.flash div.info { background-color: #0d6efd; } +div.flash > div a { + display: none; +} + td.inactive { text-align: center; background-color: tan; } -ul.inline_list { +ul.inline-list { list-style: none; } @@ -113,7 +124,8 @@ summary.filename { text-decoration: underline; } -.dropzone, .dropzone * { +.dropzone, +.dropzone * { box-sizing: border-box; } @@ -134,7 +146,7 @@ summary.filename { position: relative; display: inline-block; width: 180px; - margin: .5em; + margin: 0.5em; } .dropzone .dz-preview .dz-progress { @@ -159,7 +171,8 @@ summary.filename { display: block; } -.dropzone .dz-preview .dz-error-mark, .dropzone .dz-preview .dz-success-mark { +.dropzone .dz-preview .dz-error-mark, +.dropzone .dz-preview .dz-success-mark { position: absolute; display: none; top: 30px; @@ -168,3 +181,38 @@ summary.filename { left: 50%; margin-left: -27px; } + +div.throttle-notice > div { + display: grid; + align-items: center; + background-color: #efefef; + background-repeat: no-repeat; + background-position: 1rem center; + background-size: 1rem; + font-size: x-small; + font-weight: bold; + justify-content: center; +} + +div.throttle-notice > div.error { + background-color: #dc3545; + align-items: center; +} + +div.throttle-notice > div.warning { + background-color: #ffc107; + align-items: center; +} + +div.throttle-notice > div a { + color: black; + text-decoration: none; +} + +div.throttle-notice > div a:hover { + text-decoration: underline; +} + +label.hidden { + display: none; +} diff --git a/python/web/src/static/themes/modern/icons/cloud-off.svg b/python/web/src/static/themes/modern/icons/cloud-off.svg new file mode 100644 index 00000000..b53410ad --- /dev/null +++ b/python/web/src/static/themes/modern/icons/cloud-off.svg @@ -0,0 +1 @@ + diff --git a/python/web/src/static/themes/modern/icons/cloud.svg b/python/web/src/static/themes/modern/icons/cloud.svg new file mode 100644 index 00000000..448e1485 --- /dev/null +++ b/python/web/src/static/themes/modern/icons/cloud.svg @@ -0,0 +1 @@ + diff --git a/python/web/src/static/themes/modern/icons/command.svg b/python/web/src/static/themes/modern/icons/command.svg new file mode 100644 index 00000000..e4d75595 --- /dev/null +++ b/python/web/src/static/themes/modern/icons/command.svg @@ -0,0 +1 @@ + diff --git a/python/web/src/static/themes/modern/icons/manual copy.svg b/python/web/src/static/themes/modern/icons/home.svg similarity index 55% rename from python/web/src/static/themes/modern/icons/manual copy.svg rename to python/web/src/static/themes/modern/icons/home.svg index 12ffcbc4..7bb31b23 100644 --- a/python/web/src/static/themes/modern/icons/manual copy.svg +++ b/python/web/src/static/themes/modern/icons/home.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/python/web/src/static/themes/modern/icons/manual.svg b/python/web/src/static/themes/modern/icons/manual.svg deleted file mode 100644 index 12ffcbc4..00000000 --- a/python/web/src/static/themes/modern/icons/manual.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/python/web/src/static/themes/modern/style.css b/python/web/src/static/themes/modern/style.css index 361f5bed..e057da98 100644 --- a/python/web/src/static/themes/modern/style.css +++ b/python/web/src/static/themes/modern/style.css @@ -62,6 +62,10 @@ div.notice { color: #fff; } +label.hidden { + display: none; +} + /* ------------------------------------------------------------------------------ Tables @@ -419,8 +423,8 @@ div.header div.authentication-disabled a { color: #fff; } - div.header div.login-status.logged-in a { - background: var(--danger) no-repeat right 0.5rem center; + div.header div.login-status.logged-in span.log-out-button a { + background: var(--primary) no-repeat right 0.5rem center; background-image: url("icons/log-out.svg"); background-size: var(--icon-size); border-radius: var(--border-radius); @@ -430,6 +434,17 @@ div.header div.authentication-disabled a { color: #fff; } + div.header div.login-status.logged-in span.admin-button a { + background: var(--secondary) no-repeat right 0.5rem center; + background-image: url("icons/command.svg"); + background-size: var(--icon-size); + border-radius: var(--border-radius); + padding: 0.25rem 2.25rem 0.25rem 0.75rem; + display: inline-block; + text-decoration: none; + color: #fff; + } + div.header div.login-status.logged-in span.logged-in-as-text { margin-right: 1rem; } @@ -470,6 +485,49 @@ div.footer div.theme-change-hint a { color: yellow; } +/* + ------------------------------------------------------------------------------ + Throttle messages + ------------------------------------------------------------------------------ + */ + +div.throttle-notice > div { + display: grid; + align-items: center; + background-color: #efefef; + background-repeat: no-repeat; + background-position: 1rem center; + background-size: 1rem; + font-size: x-small; + font-weight: bold; +} + +div.throttle-notice > div.error { + background-color: var(--danger); + background-image: url("icons/error.svg"); + color: #fff; + align-items: center; +} + +div.throttle-notice > div.warning { + background-color: var(--warning); + background-image: url("icons/warning.svg"); + align-items: center; +} + +div.throttle-notice > div > span.message { + padding-left: 3rem; +} + +div.throttle-notice > div a { + color: black; + text-decoration: none; +} + +div.throttle-notice > div a:hover { + text-decoration: underline; +} + /* ------------------------------------------------------------------------------ Flash messages @@ -487,7 +545,7 @@ div.flash > div { } div.flash > div a { - display: inline-block !important; + display: inline-block; padding: 0.25rem 0.75rem; margin-left: auto; color: #fff; @@ -892,23 +950,25 @@ section#system div.power-control { /* ------------------------------------------------------------------------------ - Index > Section: Manual + Admin > Section: Services ------------------------------------------------------------------------------ */ -section#manual { - margin: 2rem 0 1rem; +section#services ul.service-list { + list-style: none; + padding-left: 0; } -section#manual a { - margin: auto; - display: block; +section#services li.service-item { + margin-bottom: 0.5em; padding: 0.25rem 0 0.25rem 2rem; - background: url("icons/manual.svg") no-repeat left center; - font-weight: bold; } -section#manual a p { - margin: 0; +section#services li.enabled { + background: url("icons/cloud.svg") no-repeat left center; +} + +section#services li.disabled { + background: url("icons/cloud-off.svg") no-repeat left center; } /* @@ -991,3 +1051,18 @@ body.page-manpage div.content p.home { margin-top: 2rem; font-weight: bold; } + +/* + ------------------------------------------------------------------------------ + Base > Back + ------------------------------------------------------------------------------ + */ +a.back { + padding: 0.25rem 0 0.25rem 2rem; + font-weight: bold; + background: url("icons/home.svg") no-repeat left center; +} + +a.back span.separator { + display: none; +} diff --git a/python/web/src/templates/admin.html b/python/web/src/templates/admin.html new file mode 100644 index 00000000..c28d41a4 --- /dev/null +++ b/python/web/src/templates/admin.html @@ -0,0 +1,193 @@ +{% extends "base.html" %} +{% block content %} + +
+
+ + {{ _("Logging") }} + +
    +
  • {{ _("The current dropdown selection indicates the active log level.") }}
  • +
+
+ +
+
+ + + + + +
+
+ +
+
+ + + +
+
+
+ +
+ +
+
+ + {{ _("Appearance") }} + +
    +
  • {{ _("Theme and language are auto-detected for your user agent. Here you can change the default.") }}
  • +
  • {{ _("The System Name is the \"pretty\" hostname if set, with a fallback to the regular hostname.") }}
  • +
+
+ +
+
+ {{ _("The current theme is \"%(theme)s\".", theme=current_theme) }} + {% if current_theme == "classic" %} + {{ _('Switch to the %(theme)s theme', theme="modern") }} + {% else %} + {{ _('Switch to the %(theme)s theme', theme="classic") }} + {% endif %} +
+ +
+ + + +
+
+ +
+
+ + + +
+
+ + +
+
+
+ +
+ +
+
+ + {{ _("Companion Services") }} + +
    +
  • {{ _("If you want to add a service, run the easyinstall.sh script and choose the one to install.") }}
  • +
  • {{ _("In order to manage the services in the Web UI, you may install Webmin as well.") }}
  • +
+
+
    + {% if netatalk_configured %} +
  • + {{ _("Mac AFP file sharing is enabled.") }} + {% else %} +
  • + {{ _("Mac AFP file sharing is disabled.") }} + {% endif %} +
  • + {% if webmin_configured %} +
  • + + {{ _("Manage the AFP server") }} + +
  • + {% endif %} + {% if samba_configured %} +
  • + {{ _("Windows SMB file sharing is enabled.") }} + {% else %} +
  • + {{ _("Windows SMB file sharing is disabled.") }} + {% endif %} +
  • + {% if webmin_configured %} +
  • + + {{ _("Manage the SMB server") }} + +
  • + {% endif %} + {% if ftp_configured %} +
  • + {{ _("FTP file sharing is enabled.") }} + {% else %} +
  • + {{ _("FTP file sharing is disabled.") }} + {% endif %} +
  • + {% if macproxy_configured %} +
  • + {{ _("Vintage web proxy is running at %(ip_addr)s (default port 5000)", ip_addr=env['ip_addr']) }} + {% else %} +
  • + {{ _("Vintage web proxy is disabled.") }} + {% endif %} +
  • +
+
+ +
+ +
+
+ + {{ _("System Operations") }} + +
    +
  • {{ _("IMPORTANT: Always shut down the system before turning off the power. Failing to do so may lead to data loss.") }}
  • +
+
+ +
+
+ +
+
+ +
+
+
+ +
+{% endblock content %} diff --git a/python/web/src/templates/base.html b/python/web/src/templates/base.html index 25331690..0c555f72 100644 --- a/python/web/src/templates/base.html +++ b/python/web/src/templates/base.html @@ -32,7 +32,9 @@ {% else %} +
+ {% if (env["throttle_status"]|length > 0) %} + {% for category, response in env["throttle_status"] %} + + {% endfor %} + {% endif %} +
+
{% if get_flashed_messages(): %} {% for category, message in get_flashed_messages(with_categories=true) %} @@ -82,7 +95,7 @@ {% else %}
{{ message }}
{% endif %} - +
{% endfor %} {% endif %} @@ -92,32 +105,17 @@ {{ content_class }} {% block content %}{% endblock content %} + {% if not is_root_page %} + + {% endif %}