diff --git a/.github/workflows/aminet-release.yml b/.github/workflows/aminet-release.yml new file mode 100644 index 0000000..8a1bf9a --- /dev/null +++ b/.github/workflows/aminet-release.yml @@ -0,0 +1,133 @@ +name: Publish Aminet releases + +on: + release: + types: [published] + +permissions: + contents: read + +jobs: + build-amigaos3: + name: Build AmigaOS 3 Aminet upload + if: ${{ !github.event.release.prerelease && startsWith(github.event.release.tag_name, 'v') }} + runs-on: ubuntu-latest + container: stefanreinauer/amiga-gcc:gcc-v13.3 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ github.event.release.tag_name }} + fetch-depth: 0 + + - name: Mark git directory as safe + run: git config --global --add safe.directory '*' + + - name: Build AmigaOS 3 archive + env: + ODFS_GIT_VERSION: ${{ github.event.release.tag_name }} + run: make amigaos3-lha + + - name: Prepare AmigaOS 3 Aminet upload files + run: | + set -eu + mkdir -p dist + cp build/amiga/ODFileSystem.lha dist/ODFileSystem.lha + cp docs/ODFileSystem.readme dist/ODFileSystem.readme + + - name: Upload AmigaOS 3 Aminet artifact + uses: actions/upload-artifact@v4 + with: + name: aminet-os3-upload + path: | + dist/ODFileSystem.lha + dist/ODFileSystem.readme + if-no-files-found: error + + build-amigaos4: + name: Build AmigaOS 4 Aminet upload + if: ${{ !github.event.release.prerelease && startsWith(github.event.release.tag_name, 'v') }} + runs-on: ubuntu-latest + container: stefanreinauer/amigappc-gcc + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ github.event.release.tag_name }} + fetch-depth: 0 + + - name: Mark git directory as safe + run: git config --global --add safe.directory '*' + + - name: Check AmigaOS 4 compiler + run: | + command -v ppc-amigaos-gcc + ppc-amigaos-gcc -dumpmachine + + - name: Build AmigaOS 4 archive + env: + ODFS_GIT_VERSION: ${{ github.event.release.tag_name }} + run: | + make amigaos4-lha \ + CC=ppc-amigaos-gcc \ + AMIGA_BUILD=build/amigaos4 \ + AMIGA_TEST_BUILD=build/amigaos4-test + + - name: Prepare AmigaOS 4 Aminet upload files + run: | + set -eu + mkdir -p dist + cp build/amigaos4/ODFileSystem-amigaos4.lha \ + dist/ODFileSystem-amigaos4.lha + cp docs/ODFileSystem_OS4.readme dist/ODFileSystem_OS4.readme + + - name: Upload AmigaOS 4 Aminet artifact + uses: actions/upload-artifact@v4 + with: + name: aminet-os4-upload + path: | + dist/ODFileSystem-amigaos4.lha + dist/ODFileSystem_OS4.readme + if-no-files-found: error + + publish: + name: Upload to Aminet + needs: + - build-amigaos3 + - build-amigaos4 + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Download AmigaOS 3 Aminet artifact + uses: actions/download-artifact@v4 + with: + name: aminet-os3-upload + path: dist/os3 + + - name: Download AmigaOS 4 Aminet artifact + uses: actions/download-artifact@v4 + with: + name: aminet-os4-upload + path: dist/os4 + + - name: Upload AmigaOS 3 package to Aminet + id: aminet-os3 + uses: sidick/aminet-release-action@v1 + with: + filename: dist/os3/ODFileSystem.lha + readme: dist/os3/ODFileSystem.readme + category: disk/cdrom + inject-version: true + + - name: Upload AmigaOS 4 package to Aminet + id: aminet-os4 + uses: sidick/aminet-release-action@v1 + with: + filename: dist/os4/ODFileSystem-amigaos4.lha + readme: dist/os4/ODFileSystem_OS4.readme + category: disk/cdrom + inject-version: true diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 4e4d696..8961f75 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -43,6 +43,9 @@ jobs: - name: Build ROM test profile run: make rom-test + - name: Build AmigaOS 3 archive + run: make amigaos3-lha + - name: Upload CI artifacts uses: actions/upload-artifact@v4 with: @@ -52,5 +55,42 @@ jobs: build/amiga-test/ODFileSystem build/amiga-rom/ODFileSystem build/amiga-rom-test/ODFileSystem + build/amiga/ODFileSystem.lha build/host/tools/ if-no-files-found: error + + build-amigaos4: + runs-on: ubuntu-latest + container: stefanreinauer/amigappc-gcc + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Mark git directory as safe + run: git config --global --add safe.directory '*' + + - name: Check AmigaOS 4 compiler + run: | + command -v ppc-amigaos-gcc + ppc-amigaos-gcc -dumpmachine + + - name: Build AmigaOS 4 handlers and archive + run: | + make amigaos4-lha \ + CC=ppc-amigaos-gcc \ + AMIGA_BUILD=build/amigaos4 \ + AMIGA_TEST_BUILD=build/amigaos4-test + + - name: Upload AmigaOS 4 CI artifacts + uses: actions/upload-artifact@v4 + with: + name: odfilesystem-amigaos4-build + path: | + build/amigaos4/ODFileSystem + build/amigaos4/CDFileSystem + build/amigaos4-test/ODFileSystem + build/amigaos4-test/CDFileSystem + build/amigaos4/ODFileSystem-amigaos4.lha + if-no-files-found: error diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8c14384..fc614a0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,8 +19,8 @@ env: RELEASE_REF: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', github.event.inputs.tag) || github.ref }} jobs: - build: - name: Build release artifacts + build-amigaos3: + name: Build AmigaOS 3 release artifacts runs-on: ubuntu-latest container: stefanreinauer/amiga-gcc:gcc-v13.3 @@ -44,15 +44,12 @@ jobs: - name: Mark git directory as safe run: git config --global --add safe.directory '*' - - name: Build release artifacts + - name: Build AmigaOS 3 release artifacts env: ODFS_GIT_VERSION: ${{ env.RELEASE_TAG }} run: | - make amiga - make amiga-test + make amigaos3-lha make adf - make rom - make rom-test - name: Collect release files run: | @@ -63,30 +60,103 @@ jobs: cp build/amiga-rom/ODFileSystem dist/ODFileSystem-rom cp build/amiga-rom-test/ODFileSystem dist/ODFileSystem-rom-test cp build/amiga-test/ODFileSystem.adf dist/ODFileSystem.adf + cp build/amiga/ODFileSystem.lha dist/ODFileSystem.lha - - name: Upload release artifacts + - name: Upload AmigaOS 3 release artifacts uses: actions/upload-artifact@v4 with: - name: release-artifacts + name: amigaos3-release-artifacts path: | dist/ODFileSystem dist/ODFileSystem-test dist/ODFileSystem-rom dist/ODFileSystem-rom-test dist/ODFileSystem.adf + dist/ODFileSystem.lha + if-no-files-found: error + + build-amigaos4: + name: Build AmigaOS 4 release artifacts + runs-on: ubuntu-latest + container: stefanreinauer/amigappc-gcc + + steps: + - name: Validate release tag + run: | + case "${RELEASE_TAG}" in + v*) ;; + *) + echo "Release tag must start with v: ${RELEASE_TAG}" >&2 + exit 1 + ;; + esac + + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ env.RELEASE_REF }} + + - name: Mark git directory as safe + run: git config --global --add safe.directory '*' + + - name: Check AmigaOS 4 compiler + run: | + command -v ppc-amigaos-gcc + ppc-amigaos-gcc -dumpmachine + + - name: Build AmigaOS 4 release artifacts + env: + ODFS_GIT_VERSION: ${{ env.RELEASE_TAG }} + run: | + make amigaos4-lha \ + CC=ppc-amigaos-gcc \ + AMIGA_BUILD=build/amigaos4 \ + AMIGA_TEST_BUILD=build/amigaos4-test + + - name: Collect AmigaOS 4 release files + run: | + set -eu + mkdir -p dist + cp build/amigaos4/ODFileSystem dist/ODFileSystem-amigaos4 + cp build/amigaos4/CDFileSystem dist/CDFileSystem + cp build/amigaos4-test/ODFileSystem \ + dist/ODFileSystem-amigaos4-test + cp build/amigaos4-test/CDFileSystem dist/CDFileSystem-test + cp build/amigaos4/ODFileSystem-amigaos4.lha \ + dist/ODFileSystem-amigaos4.lha + + - name: Upload AmigaOS 4 release artifacts + uses: actions/upload-artifact@v4 + with: + name: amigaos4-release-artifacts + path: | + dist/ODFileSystem-amigaos4 + dist/CDFileSystem + dist/ODFileSystem-amigaos4-test + dist/CDFileSystem-test + dist/ODFileSystem-amigaos4.lha if-no-files-found: error release: name: Create GitHub draft release runs-on: ubuntu-latest - needs: build + needs: + - build-amigaos3 + - build-amigaos4 steps: - - name: Download build artifacts + - name: Download AmigaOS 3 build artifacts + uses: actions/download-artifact@v4 + with: + name: amigaos3-release-artifacts + path: artifacts/amigaos3 + + - name: Download AmigaOS 4 build artifacts uses: actions/download-artifact@v4 with: - name: release-artifacts - path: artifacts + name: amigaos4-release-artifacts + path: artifacts/amigaos4 - name: Create draft release uses: softprops/action-gh-release@v2 @@ -96,8 +166,14 @@ jobs: draft: true generate_release_notes: true files: | - artifacts/ODFileSystem - artifacts/ODFileSystem-test - artifacts/ODFileSystem-rom - artifacts/ODFileSystem-rom-test - artifacts/ODFileSystem.adf + artifacts/amigaos3/ODFileSystem + artifacts/amigaos3/ODFileSystem-test + artifacts/amigaos3/ODFileSystem-rom + artifacts/amigaos3/ODFileSystem-rom-test + artifacts/amigaos3/ODFileSystem.adf + artifacts/amigaos3/ODFileSystem.lha + artifacts/amigaos4/ODFileSystem-amigaos4 + artifacts/amigaos4/CDFileSystem + artifacts/amigaos4/ODFileSystem-amigaos4-test + artifacts/amigaos4/CDFileSystem-test + artifacts/amigaos4/ODFileSystem-amigaos4.lha diff --git a/Makefile b/Makefile index d5bde13..9cd9c83 100644 --- a/Makefile +++ b/Makefile @@ -5,17 +5,46 @@ # ---- toolchain selection ---- -# Amiga cross-compiler (m68k-amigaos or m68k-aros-gcc) +# Amiga cross-compiler. +# Override with CC=ppc-amigaos-gcc for an AmigaOS 4 PPC build. +ifeq ($(origin CC),default) CC = m68k-amigaos-gcc -STRIP = m68k-amigaos-strip - -# NDK include path (override with: make NDK_PATH=/your/path) -NDK_PATH ?= $(shell realpath $$(dirname $$(which $(CC)))/../m68k-amigaos/ndk-include 2>/dev/null) +endif # AROS cross-compiler (override: make CC=m68k-aros-gcc AROS=1) # When AROS=1, uses -static instead of -noixemul and defines __AROS__ AROS ?= 0 +# Derive target tools from CC so CC=ppc-amigaos-gcc also selects the +# matching ppc-amigaos-ar/strip/size tools. +AMIGA_CC_TARGET := $(shell $(CC) -dumpmachine 2>/dev/null) +AMIGA_TOOL_PREFIX ?= $(patsubst %-gcc,%,$(notdir $(CC))) +AMIGA_AR ?= $(AMIGA_TOOL_PREFIX)-ar +AMIGA_SIZE ?= $(AMIGA_TOOL_PREFIX)-size +AMIGA_OBJCOPY ?= $(AMIGA_TOOL_PREFIX)-objcopy +STRIP ?= $(AMIGA_TOOL_PREFIX)-strip + +ifneq ($(filter ppc-amigaos,$(AMIGA_CC_TARGET)),) +AMIGA_TARGET ?= os4 +else ifeq ($(AROS),1) +AMIGA_TARGET ?= aros +else +AMIGA_TARGET ?= os3 +endif + +ifeq ($(AMIGA_TARGET),os4) +AMIGA_OSDIR := os4 +else +AMIGA_OSDIR := os3 +endif + +# NDK include path (override with: make NDK_PATH=/your/path) +ifeq ($(AMIGA_TARGET),os4) +NDK_PATH ?= $(shell realpath $$(dirname $$(which $(CC)))/../ppc-amigaos/SDK/include/include_h 2>/dev/null) +else +NDK_PATH ?= $(shell realpath $$(dirname $$(which $(CC)))/../m68k-amigaos/ndk-include 2>/dev/null) +endif + # Host compiler HOSTCC ?= cc @@ -25,6 +54,11 @@ AMIGA_DATE ?= $(shell date '+%-d.%-m.%Y') ODFS_GIT_VERSION ?= $(shell desc=$$(git describe --tags --match "v*" --dirty --always 2>/dev/null || echo unknown); printf '%s\n' "$$desc" | grep -q '^v' && printf '%s' "$$desc" || printf 'early-0-g%s' "$$desc") INCLUDES = -I include -I backends +AMIGA_PLATFORM_INCLUDES = -I platform/amiga \ + -I platform/amiga/common \ + -I platform/amiga/$(AMIGA_OSDIR) +AMIGA_INCLUDES = $(INCLUDES) $(AMIGA_PLATFORM_INCLUDES) \ + $(if $(NDK_PATH),-I$(NDK_PATH)) # ---- optional 3rdparty submodules ---- @@ -53,8 +87,12 @@ SERIAL_DEBUG ?= 0 PACKET_TRACE ?= 0 # Release size limits (override when intentional growth is approved) +ifeq ($(AMIGA_TARGET),os4) +AMIGA_SIZE_LIMIT ?= 131072 +else AMIGA_SIZE_LIMIT ?= 60000 -ROM_SIZE_LIMIT ?= 32768 +endif +ROM_SIZE_LIMIT ?= 40960 SIZE_LIMIT_NAME ?= AMIGA_SIZE_LIMIT SIZE_LIMIT_DESC ?= release Amiga handler @@ -85,26 +123,53 @@ FEATURE_DEFS = \ -DODFS_FEATURE_HFSPLUS=$(FEATURE_HFSPLUS) \ -DODFS_FEATURE_CDDA=$(FEATURE_CDDA) -ifeq ($(AROS),1) -CFLAGS = -Os -m68000 -mtune=68020-60 -msoft-float -static -nostartfiles \ - -Wall -Wextra -Werror \ - -Wstrict-prototypes -Wmissing-prototypes \ - -Wno-array-bounds \ - -MMD -MP \ - -DAMIGA -D__AROS__ $(FEATURE_DEFS) -LDFLAGS = -static -LIBS = -lamiga -lgcc +ifeq ($(AMIGA_TARGET),aros) +AMIGA_CPUFLAGS ?= -m68000 -mtune=68020-60 -msoft-float +AMIGA_SYSFLAGS ?= -static +AMIGA_WARNFLAGS = +AMIGA_DEFS = -DAMIGA -D__AROS__ +LDFLAGS = $(AMIGA_SYSFLAGS) +LIBS = -lamiga -lgcc +HANDLER_LDFLAGS = -nostartfiles +HANDLER_LIBS = -nostdlib -Wl,-u,_exit -lgcc -lc -lgcc -lamiga -ramiga-dev +else ifeq ($(AMIGA_TARGET),os4) +AMIGA_CRT ?= newlib +AMIGA_CPUFLAGS ?= -mcpu=powerpc +# No unwind tables: the freestanding handler has no exception support, +# and the kickstart loader expects plain PT_LOAD program headers only. +AMIGA_SYSFLAGS ?= -mcrt=$(AMIGA_CRT) -fno-asynchronous-unwind-tables +AMIGA_WARNFLAGS = +AMIGA_DEFS = -DAMIGA -D__USE_INLINE__ -D__USE_BASETYPE__ +LDFLAGS = $(AMIGA_SYSFLAGS) +# Keep OS4 library/interface ownership explicit in os4/sys_compat.c. +# Do not add -lauto to the handler link. +LIBS = -lc -lgcc +# The handler must not run the newlib C runtime startup: it consumes +# the first process message, which is the handler's ACTION_STARTUP +# packet. os4/start.c provides the freestanding entry instead. +# -static keeps gcc from passing --eh-frame-hdr, so the binary carries +# only the plain PT_LOAD program headers the kickstart loader expects. +HANDLER_LDFLAGS = -nostartfiles -static -Wl,-u,_start +HANDLER_LIBS = -nostdlib -lgcc else -CFLAGS = -Os -m68000 -mtune=68020-60 -msoft-float -noixemul -nostartfiles \ - -Wall -Wextra -Werror \ - -Wstrict-prototypes -Wmissing-prototypes \ - -Wno-array-bounds \ - -MMD -MP \ - -DAMIGA $(FEATURE_DEFS) -LDFLAGS = -noixemul -LIBS = -lamiga -lgcc +AMIGA_CPUFLAGS ?= -m68000 -mtune=68020-60 -msoft-float +AMIGA_SYSFLAGS ?= -noixemul +AMIGA_WARNFLAGS = +AMIGA_DEFS = -DAMIGA +LDFLAGS = $(AMIGA_SYSFLAGS) +LIBS = -lamiga -lgcc +HANDLER_LDFLAGS = -nostartfiles +HANDLER_LIBS = -nostdlib -Wl,-u,_exit -lgcc -lc -lgcc -lamiga -ramiga-dev endif +CFLAGS = -Os $(AMIGA_CPUFLAGS) $(AMIGA_SYSFLAGS) -nostartfiles \ + -Wall -Wextra -Werror \ + $(AMIGA_WARNFLAGS) \ + -Wstrict-prototypes -Wmissing-prototypes \ + -Wno-array-bounds \ + -MMD -MP \ + $(AMIGA_DEFS) $(FEATURE_DEFS) + # ---- build directories ---- HOST_BUILD = build/host @@ -139,11 +204,27 @@ HOST_SRCS = platform/host/file_media.c # Amiga handler sources AMIGA_SRCS = platform/amiga/handler_main.c \ - platform/amiga/libc_stubs.c \ - platform/amiga/printf_local.c + platform/amiga/printf_local.c \ + platform/amiga/$(AMIGA_OSDIR)/sys_compat.c +ifeq ($(AMIGA_TARGET),os4) +AMIGA_SRCS += platform/amiga/os4/main.c \ + platform/amiga/os4/start.c \ + platform/amiga/os4/freestanding.c \ + platform/amiga/os4/vector_port.c +else +AMIGA_SRCS += platform/amiga/libc_stubs.c +endif + +# Freestanding libc replacements: stop the compiler from recognizing +# the copy loops and emitting calls to the functions being defined. +$(AMIGA_BUILD)/platform/amiga/os4/freestanding.o: CFLAGS += -fno-builtin # Amiga assembly +ifeq ($(AMIGA_TARGET),os4) +AMIGA_ASM_SRCS = +else AMIGA_ASM_SRCS = platform/amiga/startup.S +endif AMIGA_ASM_OBJS = $(patsubst %.S,$(AMIGA_BUILD)/%.o,$(AMIGA_ASM_SRCS)) HOST_LIB_SRCS = $(CORE_SRCS) $(HOST_SRCS) @@ -176,6 +257,12 @@ TOOL_DEPS = $(patsubst %,$(HOST_BUILD)/tools/%.d,$(TOOL_NAMES)) # ---- handler target (Amiga) ---- HANDLER = $(AMIGA_BUILD)/ODFileSystem +KICKSTART_MODULE = +AMIGA_ARTIFACTS = $(HANDLER) +ifeq ($(AMIGA_TARGET),os4) +KICKSTART_MODULE = $(AMIGA_BUILD)/CDFileSystem +AMIGA_ARTIFACTS += $(KICKSTART_MODULE) +endif TEST_HANDLER = $(AMIGA_TEST_BUILD)/ODFileSystem AMIGA_TEST_TOOL = $(AMIGA_TEST_BUILD)/test_handler ADF = $(AMIGA_TEST_BUILD)/ODFileSystem.adf @@ -183,18 +270,29 @@ ADF_VOLUME = ODFileSystem ADF_DOSDRIVER = platform/amiga/dosdrivers/CD0 ADF_DOSDRIVER_ICON = platform/amiga/dosdrivers/CD0.info XDFTOOL ?= xdftool +LHA ?= lha +AMIGAOS3_PACKAGE_NAME ?= ODFileSystem +AMIGAOS3_PACKAGE = $(AMIGA_BUILD)/$(AMIGAOS3_PACKAGE_NAME).lha +AMIGAOS3_PACKAGE_DIR = $(AMIGA_BUILD)/$(AMIGAOS3_PACKAGE_NAME)-lha +AMIGAOS3_README = docs/ODFileSystem.readme +AMIGAOS4_PACKAGE_NAME ?= ODFileSystem-amigaos4 +AMIGAOS4_PACKAGE = $(AMIGA_BUILD)/$(AMIGAOS4_PACKAGE_NAME).lha +AMIGAOS4_PACKAGE_DIR = $(AMIGA_BUILD)/$(AMIGAOS4_PACKAGE_NAME)-lha +AMIGAOS4_README = docs/ODFileSystem_OS4.readme # ================================================================== # targets # ================================================================== -.PHONY: all host amiga amiga-test adf rom rom-test lib tests tools fuzz check golden-check malformed-check fuzz-check integration-check clean size +.PHONY: all host amiga amiga-test adf rom rom-test lib tests tools fuzz +.PHONY: check golden-check malformed-check fuzz-check integration-check +.PHONY: clean size amigaos3-lha amigaos4-lha all: host host: lib tests tools -amiga: $(HANDLER) +amiga: $(AMIGA_ARTIFACTS) @echo " $(HANDLER) built successfully" @size=$$(wc -c < "$(HANDLER)"); \ echo " Handler size: $$size bytes"; \ @@ -202,6 +300,11 @@ amiga: $(HANDLER) echo " ERROR: $(SIZE_LIMIT_DESC) exceeds $(AMIGA_SIZE_LIMIT) bytes"; \ echo " If this growth is intentional, rerun with $(SIZE_LIMIT_NAME)="; \ exit 1; \ + fi; \ + if [ -n "$(KICKSTART_MODULE)" ]; then \ + ksize=$$(wc -c < "$(KICKSTART_MODULE)"); \ + echo " Kickstart module: $(KICKSTART_MODULE)"; \ + echo " CDFileSystem size: $$ksize bytes"; \ fi amiga-test: @@ -225,6 +328,57 @@ adf: amiga-test $(AMIGA_TEST_TOOL) $(ADF_DOSDRIVER) $(ADF_DOSDRIVER_ICON) Makefi write $(ADF_DOSDRIVER_ICON) @echo " ADF image ready: $(ADF)" +amigaos3-lha: + @if [ "$(AMIGA_TARGET)" != "os3" ]; then \ + echo " ERROR: amigaos3-lha requires CC=m68k-amigaos-gcc"; \ + exit 1; \ + fi + @$(MAKE) --no-print-directory AMIGA_BUILD=$(AMIGA_BUILD) amiga + @$(MAKE) --no-print-directory AMIGA_TEST_BUILD=$(AMIGA_TEST_BUILD) \ + amiga-test + @$(MAKE) --no-print-directory ROM_BUILD=$(ROM_BUILD) rom + @$(MAKE) --no-print-directory ROM_TEST_BUILD=$(ROM_TEST_BUILD) \ + rom-test + @rm -rf "$(AMIGAOS3_PACKAGE_DIR)" "$(AMIGAOS3_PACKAGE)" + @mkdir -p "$(AMIGAOS3_PACKAGE_DIR)" + @cp "$(HANDLER)" "$(AMIGAOS3_PACKAGE_DIR)/ODFileSystem" + @cp "$(TEST_HANDLER)" "$(AMIGAOS3_PACKAGE_DIR)/ODFileSystem-test" + @cp "$(ROM_BUILD)/ODFileSystem" \ + "$(AMIGAOS3_PACKAGE_DIR)/ODFileSystem-rom" + @cp "$(ROM_TEST_BUILD)/ODFileSystem" \ + "$(AMIGAOS3_PACKAGE_DIR)/ODFileSystem-rom-test" + @cp "$(AMIGAOS3_README)" "$(AMIGAOS3_PACKAGE_DIR)/README.md" + @echo " LHA $(AMIGAOS3_PACKAGE)" + @(cd "$(AMIGAOS3_PACKAGE_DIR)" && \ + $(LHA) -aq "$(CURDIR)/$(AMIGAOS3_PACKAGE)" \ + ODFileSystem ODFileSystem-test \ + ODFileSystem-rom ODFileSystem-rom-test README.md) + @echo " LHA archive ready: $(AMIGAOS3_PACKAGE)" + +amigaos4-lha: + @if [ "$(AMIGA_TARGET)" != "os4" ]; then \ + echo " ERROR: amigaos4-lha requires CC=ppc-amigaos-gcc"; \ + exit 1; \ + fi + @$(MAKE) --no-print-directory AMIGA_BUILD=$(AMIGA_BUILD) amiga + @$(MAKE) --no-print-directory AMIGA_TEST_BUILD=$(AMIGA_TEST_BUILD) \ + amiga-test + @rm -rf "$(AMIGAOS4_PACKAGE_DIR)" "$(AMIGAOS4_PACKAGE)" + @mkdir -p "$(AMIGAOS4_PACKAGE_DIR)" + @cp "$(HANDLER)" "$(AMIGAOS4_PACKAGE_DIR)/ODFileSystem-amigaos4" + @cp "$(KICKSTART_MODULE)" "$(AMIGAOS4_PACKAGE_DIR)/CDFileSystem" + @cp "$(TEST_HANDLER)" \ + "$(AMIGAOS4_PACKAGE_DIR)/ODFileSystem-amigaos4-test" + @cp "$(AMIGA_TEST_BUILD)/CDFileSystem" \ + "$(AMIGAOS4_PACKAGE_DIR)/CDFileSystem-test" + @cp "$(AMIGAOS4_README)" "$(AMIGAOS4_PACKAGE_DIR)/README.md" + @echo " LHA $(AMIGAOS4_PACKAGE)" + @(cd "$(AMIGAOS4_PACKAGE_DIR)" && \ + $(LHA) -aq "$(CURDIR)/$(AMIGAOS4_PACKAGE)" \ + ODFileSystem-amigaos4 CDFileSystem \ + ODFileSystem-amigaos4-test CDFileSystem-test README.md) + @echo " LHA archive ready: $(AMIGAOS4_PACKAGE)" + # ROM profile: minimal build for burning into ROM # ISO9660 + Rock Ridge + Joliet + Multisession, no debug, no UDF/HFS/CDDA rom: @@ -258,7 +412,7 @@ rom-test: # Print size breakdown of Amiga library objects size: $(AMIGA_BUILD)/libodfs.a @echo "=== Amiga object sizes ===" - @m68k-amigaos-size $(AMIGA_BUILD)/libodfs.a + @$(AMIGA_SIZE) $(AMIGA_BUILD)/libodfs.a # ---- host library ---- @@ -362,14 +516,14 @@ $(HOST_BUILD)/tools/imgdump: tools/imgdump/imgdump.c $(HOST_BUILD)/libodfs.a $(AMIGA_BUILD)/libodfs.a: $(AMIGA_LIB_OBJS) @mkdir -p $(@D) @echo " AR $@ (amiga)" - @m68k-amigaos-ar rcs $@ $^ + @$(AMIGA_AR) rcs $@ $^ # ---- Amiga object files ---- $(AMIGA_BUILD)/%.o: %.c @mkdir -p $(@D) @echo " CC $<" - @$(CC) $(CPPFLAGS) $(INCLUDES) $(CFLAGS) -c -o $@ $< + @$(CC) $(CPPFLAGS) $(AMIGA_INCLUDES) $(CFLAGS) -c -o $@ $< # ---- Amiga assembly ---- @@ -383,12 +537,12 @@ $(AMIGA_BUILD)/%.o: %.S $(AMIGA_TEST_BUILD)/tests/amiga/%.o: tests/amiga/%.c @mkdir -p $(@D) @echo " CC $<" - @$(CC) $(CPPFLAGS) $(INCLUDES) $(CFLAGS) -c -o $@ $< + @$(CC) $(CPPFLAGS) $(AMIGA_INCLUDES) $(CFLAGS) -c -o $@ $< $(AMIGA_TEST_TOOL): $(AMIGA_TEST_BUILD)/tests/amiga/test_handler.o @mkdir -p $(@D) @echo " LINK $@" - @$(CC) $(LDFLAGS) -o $@ $< -lc -lamiga -lgcc + @$(CC) $(LDFLAGS) -o $@ $< $(LIBS) @echo " STRIP $@" @$(STRIP) $@ @@ -397,10 +551,20 @@ $(AMIGA_TEST_TOOL): $(AMIGA_TEST_BUILD)/tests/amiga/test_handler.o $(HANDLER): $(AMIGA_ASM_OBJS) $(AMIGA_BUILD)/libodfs.a @mkdir -p $(@D) @echo " LINK $@" - @$(CC) $(LDFLAGS) -nostartfiles -o $@ $(AMIGA_ASM_OBJS) -L$(AMIGA_BUILD) -lodfs -nostdlib -Wl,-u,_exit -lgcc -lc -lgcc -lamiga -ramiga-dev + @$(CC) $(LDFLAGS) $(HANDLER_LDFLAGS) -o $@ $(AMIGA_ASM_OBJS) -L$(AMIGA_BUILD) -lodfs $(HANDLER_LIBS) @echo " STRIP $@" @$(STRIP) $@ +ifeq ($(AMIGA_TARGET),os4) +$(KICKSTART_MODULE): $(AMIGA_BUILD)/platform/amiga/os4/start.o $(AMIGA_BUILD)/libodfs.a + @mkdir -p $(@D) + @echo " LINK $@ (kickstart)" + @$(CC) $(LDFLAGS) -nostartfiles -nostdlib -Wl,-r -o $@.unstripped $< -L$(AMIGA_BUILD) -lodfs -lgcc + @echo " STRIP $@" + @$(AMIGA_OBJCOPY) --strip-unneeded $@.unstripped $@ + @rm -f $@.unstripped +endif + # ---- clean ---- diff --git a/README.md b/README.md index eb0033e..386b0ed 100644 --- a/README.md +++ b/README.md @@ -135,21 +135,145 @@ Real-world `AS` source images used during development: The automated real-image golden test downloads only the smaller `Arabian Nights` archive on demand, verifies the Archive.org MD5 before reuse, extracts track 1 to a plain 2048-byte data image, and skips cleanly if download or extraction tooling is unavailable. If a prepared data-track image already exists locally, set `ODFS_REAL_AS_IMAGE=/path/to/arabian_nights.iso` to reuse it without redownloading. The larger `Benefactor` image is kept as a manual reference input. -## Sample Mountlist +## Building for AmigaOS + +The Amiga handler is built with the `amiga` make target. The Makefile selects +the Amiga frontend from the compiler target: + +- `m68k-amigaos-gcc` builds the AmigaOS 3/classic handler +- `ppc-amigaos-gcc` builds the native AmigaOS 4 handler + +### AmigaOS 3 -Build the handler with: +With an m68k AmigaOS cross-compiler in `PATH`, run: ```sh make amiga ``` -This builds the release handler with serial logging disabled. For a test build -with serial output enabled, use `make amiga-test`. +This uses `m68k-amigaos-gcc` by default and writes: + +```text +build/amiga/ODFileSystem +``` + +If the compiler is not in `PATH`, pass it explicitly: + +```sh +make amiga CC=/path/to/m68k-amigaos-gcc +``` + +If the matching `ar`, `size`, and `strip` tools are also outside `PATH`, pass +those too: + +```sh +make amiga \ + CC=/path/to/m68k-amigaos-gcc \ + AMIGA_AR=/path/to/m68k-amigaos-ar \ + AMIGA_SIZE=/path/to/m68k-amigaos-size \ + STRIP=/path/to/m68k-amigaos-strip +``` + +To build the OS3 release archive, use: + +```sh +make amigaos3-lha +``` + +This creates `build/amiga/ODFileSystem.lha` containing `ODFileSystem`, +`ODFileSystem-test`, `ODFileSystem-rom`, `ODFileSystem-rom-test`, and +`README.md`. The test ADF is built and released separately. + +### AmigaOS 4 + +With the AmigaOS 4 PPC toolchain in `PATH`, run: + +```sh +make amiga CC=ppc-amigaos-gcc +``` + +This selects `AMIGA_TARGET=os4` automatically and builds the native OS4 +filesystem handler. To keep OS3 and OS4 outputs side by side, use a separate +build directory: + +```sh +make amiga \ + CC=ppc-amigaos-gcc \ + AMIGA_BUILD=build/amiga-os4 +``` -Release builds enforce a default size limit of `60000` bytes. If intentional -growth needs a higher ceiling, override it with `AMIGA_SIZE_LIMIT=`. +The handler is then written to: + +```text +build/amiga-os4/ODFileSystem +``` + +For OS4 builds the Makefile also writes a Kickstart-module form: + +```text +build/amiga-os4/CDFileSystem +``` + +These two files are intentionally different: + +- `ODFileSystem` is the normal disk-loadable filesystem handler. Copy it to + `L:ODFileSystem` and use it with a DOSDriver or mountlist. +- `CDFileSystem` is a relocatable Kickstart resident module. Use it when + replacing `Kickstart/CDFileSystem` in an OS4 Kickstart directory or + `Kickstart.zip`; the Kicklayout entry remains `MODULE Kickstart/CDFileSystem`. + +If the OS4 toolchain is installed outside `PATH`, pass the full tool paths: + +```sh +make amiga \ + CC=/opt/amiga-ppc/bin/ppc-amigaos-gcc \ + AMIGA_AR=/opt/amiga-ppc/bin/ppc-amigaos-ar \ + AMIGA_OBJCOPY=/opt/amiga-ppc/bin/ppc-amigaos-objcopy \ + AMIGA_SIZE=/opt/amiga-ppc/bin/ppc-amigaos-size \ + STRIP=/opt/amiga-ppc/bin/ppc-amigaos-strip \ + AMIGA_BUILD=build/amiga-os4 +``` + +The Makefile normally derives the NDK include path from the selected compiler. +If your SDK is elsewhere, add `NDK_PATH=/path/to/include_h`. + +### Debug and Size Limits + +Release builds have serial logging disabled. For a test build with serial +output enabled, use `make amiga-test` with the same toolchain selection: + +```sh +make amiga-test +make amiga-test CC=ppc-amigaos-gcc AMIGA_TEST_BUILD=build/amiga-os4-test +``` + +The OS4 test build likewise produces both `ODFileSystem` and `CDFileSystem` +under the selected test build directory. + +To build the OS4 release archive, use: + +```sh +make amigaos4-lha \ + CC=ppc-amigaos-gcc \ + AMIGA_BUILD=build/amiga-os4 \ + AMIGA_TEST_BUILD=build/amiga-os4-test +``` + +This creates `build/amiga-os4/ODFileSystem-amigaos4.lha` containing +`ODFileSystem-amigaos4`, `CDFileSystem`, `ODFileSystem-amigaos4-test`, +`CDFileSystem-test`, and `README.md`. + +Release builds enforce a default size limit of `60000` bytes for OS3 and +`131072` bytes for OS4. If intentional growth needs a higher ceiling, override +it with `AMIGA_SIZE_LIMIT=`. During local bring-up, the limit can be +disabled with `ENFORCE_SIZE_LIMITS=0`. + +## Sample Mountlist -Then copy `build/amiga/ODFileSystem` to `L:ODFileSystem`. +For the mountlist examples below, copy the built handler to +`L:ODFileSystem`. With the default build directory that is +`build/amiga/ODFileSystem`; if you set `AMIGA_BUILD`, use the corresponding +`ODFileSystem` output from that directory. For Workbench-style installation, copy: diff --git a/backends/hfs/hfs.c b/backends/hfs/hfs.c index 49d1dd0..38a20d0 100644 --- a/backends/hfs/hfs.c +++ b/backends/hfs/hfs.c @@ -623,33 +623,13 @@ static odfs_err_t hfs_read(void *backend_ctx, if (offset >= file->size) { *len = 0; return ODFS_OK; } size_t want = *len; - if (offset + want > file->size) + if (want > file->size - offset) want = (size_t)(file->size - offset); /* file->extent.lba = first extent start allocation block */ uint64_t data_start = hfs_ab_to_byte(ctx, (uint16_t)file->extent.lba); - - size_t done = 0; - uint8_t *out = buf; - - while (done < want) { - uint64_t pos = data_start + offset + done; - uint32_t lba = (uint32_t)(pos / 2048); - uint32_t lba_off = (uint32_t)(pos % 2048); - const uint8_t *sector; - - odfs_err_t err = odfs_cache_read(cache, lba, §or); - if (err != ODFS_OK) { *len = done; return err; } - - size_t chunk = 2048 - lba_off; - if (chunk > want - done) chunk = want - done; - - memcpy(out + done, sector + lba_off, chunk); - done += chunk; - } - - *len = done; - return ODFS_OK; + *len = want; + return odfs_cache_read_bytes(cache, 0, data_start + offset, buf, len); } /* ------------------------------------------------------------------ */ diff --git a/backends/hfsplus/hfsplus.c b/backends/hfsplus/hfsplus.c index 5016bfa..68a6d3e 100644 --- a/backends/hfsplus/hfsplus.c +++ b/backends/hfsplus/hfsplus.c @@ -604,32 +604,13 @@ static odfs_err_t hfsp_read(void *backend_ctx, if (offset >= file->size) { *len = 0; return ODFS_OK; } size_t want = *len; - if (offset + want > file->size) + if (want > file->size - offset) want = (size_t)(file->size - offset); /* simple case: first extent only (covers most small files) */ uint64_t data_start = hfsp_block_to_byte(ctx, file->extent.lba); - size_t done = 0; - uint8_t *out = buf; - - while (done < want) { - uint64_t pos = data_start + offset + done; - uint32_t lba = (uint32_t)(pos / 2048); - uint32_t lba_off = (uint32_t)(pos % 2048); - const uint8_t *sector; - - odfs_err_t err = odfs_cache_read(cache, lba, §or); - if (err != ODFS_OK) { *len = done; return err; } - - size_t chunk = 2048 - lba_off; - if (chunk > want - done) chunk = want - done; - - memcpy(out + done, sector + lba_off, chunk); - done += chunk; - } - - *len = done; - return ODFS_OK; + *len = want; + return odfs_cache_read_bytes(cache, 0, data_start + offset, buf, len); } /* ------------------------------------------------------------------ */ diff --git a/backends/iso9660/iso9660.c b/backends/iso9660/iso9660.c index b443386..cd77818 100644 --- a/backends/iso9660/iso9660.c +++ b/backends/iso9660/iso9660.c @@ -464,39 +464,16 @@ static odfs_err_t iso_read(void *backend_ctx, (void)backend_ctx; (void)log; size_t want = *len; - size_t done = 0; - uint8_t *out = buf; if (offset >= file->size) { *len = 0; return ODFS_OK; } - if (offset + want > file->size) + if (want > file->size - offset) want = (size_t)(file->size - offset); - while (done < want) { - uint64_t file_pos = offset + done; - uint32_t sector_lba = file->extent.lba + (uint32_t)(file_pos / ISO_SECTOR_SIZE); - uint32_t sector_off = (uint32_t)(file_pos % ISO_SECTOR_SIZE); - const uint8_t *sector; - odfs_err_t err; - - err = odfs_cache_read(cache, sector_lba, §or); - if (err != ODFS_OK) { - *len = done; - return err; - } - - size_t chunk = ISO_SECTOR_SIZE - sector_off; - if (chunk > want - done) - chunk = want - done; - - memcpy(out + done, sector + sector_off, chunk); - done += chunk; - } - - *len = done; - return ODFS_OK; + *len = want; + return odfs_cache_read_bytes(cache, file->extent.lba, offset, buf, len); } /* ------------------------------------------------------------------ */ diff --git a/backends/joliet/joliet.c b/backends/joliet/joliet.c index 1a80c76..73f934e 100644 --- a/backends/joliet/joliet.c +++ b/backends/joliet/joliet.c @@ -358,33 +358,13 @@ static odfs_err_t joliet_read(void *backend_ctx, (void)backend_ctx; (void)log; size_t want = *len; - size_t done = 0; - uint8_t *out = buf; if (offset >= file->size) { *len = 0; return ODFS_OK; } - if (offset + want > file->size) + if (want > file->size - offset) want = (size_t)(file->size - offset); - while (done < want) { - uint64_t file_pos = offset + done; - uint32_t sector_lba = file->extent.lba + (uint32_t)(file_pos / ISO_SECTOR_SIZE); - uint32_t sector_off = (uint32_t)(file_pos % ISO_SECTOR_SIZE); - const uint8_t *sector; - odfs_err_t err; - - err = odfs_cache_read(cache, sector_lba, §or); - if (err != ODFS_OK) { *len = done; return err; } - - size_t chunk = ISO_SECTOR_SIZE - sector_off; - if (chunk > want - done) - chunk = want - done; - - memcpy(out + done, sector + sector_off, chunk); - done += chunk; - } - - *len = done; - return ODFS_OK; + *len = want; + return odfs_cache_read_bytes(cache, file->extent.lba, offset, buf, len); } /* ------------------------------------------------------------------ */ diff --git a/backends/udf/udf.c b/backends/udf/udf.c index 8736619..6b72399 100644 --- a/backends/udf/udf.c +++ b/backends/udf/udf.c @@ -690,31 +690,11 @@ static odfs_err_t udf_read(void *backend_ctx, size_t want = *len; if (offset >= fsize) { *len = 0; return ODFS_OK; } - if (offset + want > fsize) + if (want > fsize - offset) want = (size_t)(fsize - offset); - size_t done = 0; - uint8_t *out = buf; - - while (done < want) { - uint64_t pos = offset + done; - uint32_t slba = data_lba + (uint32_t)(pos / 2048); - uint32_t soff = (uint32_t)(pos % 2048); - const uint8_t *sector; - - err = odfs_cache_read(cache, slba, §or); - if (err != ODFS_OK) { *len = done; return err; } - - size_t chunk = 2048 - soff; - if (chunk > want - done) - chunk = want - done; - - memcpy(out + done, sector + soff, chunk); - done += chunk; - } - - *len = done; - return ODFS_OK; + *len = want; + return odfs_cache_read_bytes(cache, data_lba, offset, buf, len); } /* ------------------------------------------------------------------ */ diff --git a/core/cache_block.c b/core/cache_block.c index 8053d48..d7d549b 100644 --- a/core/cache_block.c +++ b/core/cache_block.c @@ -8,14 +8,105 @@ #include "odfs/alloc.h" #include +#define ODFS_CACHE_STREAM_MIN_SECTORS 2u + +static void cache_reset_hash(odfs_cache_t *cache) +{ + uint32_t i; + + if (!cache) + return; + + if (cache->buckets) { + for (i = 0; i < cache->hash_size; i++) + cache->buckets[i] = -1; + } + if (cache->next) { + for (i = 0; i < cache->capacity; i++) + cache->next[i] = -1; + } +} + +static uint32_t cache_hash_lba(const odfs_cache_t *cache, uint32_t lba) +{ + return cache->hash_size ? (lba % cache->hash_size) : 0; +} + +static int32_t cache_find_index(const odfs_cache_t *cache, uint32_t lba) +{ + int32_t idx; + + if (!cache || !cache->buckets || !cache->next || cache->hash_size == 0) + return -1; + + idx = cache->buckets[cache_hash_lba(cache, lba)]; + while (idx >= 0) { + const odfs_cache_entry_t *entry = &cache->entries[idx]; + + if (entry->valid && entry->lba == lba) + return idx; + idx = cache->next[idx]; + } + return -1; +} + +static void cache_insert_index(odfs_cache_t *cache, uint32_t idx) +{ + uint32_t bucket; + + if (!cache || !cache->buckets || !cache->next || idx >= cache->capacity) + return; + + bucket = cache_hash_lba(cache, cache->entries[idx].lba); + cache->next[idx] = cache->buckets[bucket]; + cache->buckets[bucket] = (int32_t)idx; +} + +static void cache_remove_index(odfs_cache_t *cache, uint32_t idx) +{ + uint32_t bucket; + int32_t cur; + int32_t prev = -1; + + if (!cache || !cache->buckets || !cache->next || idx >= cache->capacity) + return; + + bucket = cache_hash_lba(cache, cache->entries[idx].lba); + cur = cache->buckets[bucket]; + while (cur >= 0) { + if ((uint32_t)cur == idx) { + if (prev >= 0) + cache->next[prev] = cache->next[cur]; + else + cache->buckets[bucket] = cache->next[cur]; + cache->next[cur] = -1; + return; + } + prev = cur; + cur = cache->next[cur]; + } +} + odfs_err_t odfs_cache_init(odfs_cache_t *cache, odfs_media_t *media, uint32_t capacity) { uint32_t sector_size; + uint32_t i; + size_t entries_bytes; + size_t next_bytes; + size_t bucket_bytes; if (!cache || !media || capacity == 0) return ODFS_ERR_INVAL; + if (capacity > (UINT32_MAX - 1u) / 2u) + return ODFS_ERR_RANGE; + + entries_bytes = (size_t)capacity * sizeof(odfs_cache_entry_t); + next_bytes = (size_t)capacity * sizeof(cache->next[0]); + if (entries_bytes / sizeof(odfs_cache_entry_t) != capacity || + next_bytes / sizeof(cache->next[0]) != capacity) + return ODFS_ERR_OVERFLOW; memset(cache, 0, sizeof(*cache)); sector_size = odfs_media_sector_size(media); @@ -26,15 +117,34 @@ odfs_err_t odfs_cache_init(odfs_cache_t *cache, if (!cache->entries) return ODFS_ERR_NOMEM; + cache->hash_size = capacity * 2u + 1u; + bucket_bytes = (size_t)cache->hash_size * sizeof(cache->buckets[0]); + if (bucket_bytes / sizeof(cache->buckets[0]) != cache->hash_size) { + odfs_free(cache->entries); + memset(cache, 0, sizeof(*cache)); + return ODFS_ERR_OVERFLOW; + } + cache->buckets = odfs_malloc(bucket_bytes); + cache->next = odfs_malloc(next_bytes); + if (!cache->buckets || !cache->next) { + odfs_free(cache->buckets); + odfs_free(cache->next); + odfs_free(cache->entries); + memset(cache, 0, sizeof(*cache)); + return ODFS_ERR_NOMEM; + } + /* allocate data buffers for each entry */ - for (uint32_t i = 0; i < capacity; i++) { + for (i = 0; i < capacity; i++) { cache->entries[i].data = odfs_malloc(sector_size); if (!cache->entries[i].data) { /* roll back */ for (uint32_t j = 0; j < i; j++) odfs_free(cache->entries[j].data); + odfs_free(cache->buckets); + odfs_free(cache->next); odfs_free(cache->entries); - cache->entries = NULL; + memset(cache, 0, sizeof(*cache)); return ODFS_ERR_NOMEM; } cache->entries[i].valid = 0; @@ -44,6 +154,7 @@ odfs_err_t odfs_cache_init(odfs_cache_t *cache, cache->sector_size = sector_size; cache->clock = 0; cache->media = media; + cache_reset_hash(cache); return ODFS_OK; } @@ -55,6 +166,8 @@ void odfs_cache_destroy(odfs_cache_t *cache) for (uint32_t i = 0; i < cache->capacity; i++) odfs_free(cache->entries[i].data); + odfs_free(cache->buckets); + odfs_free(cache->next); odfs_free(cache->entries); memset(cache, 0, sizeof(*cache)); @@ -67,6 +180,8 @@ void odfs_cache_flush(odfs_cache_t *cache) for (uint32_t i = 0; i < cache->capacity; i++) cache->entries[i].valid = 0; + cache->valid_count = 0; + cache_reset_hash(cache); } odfs_err_t odfs_cache_read(odfs_cache_t *cache, @@ -76,7 +191,8 @@ odfs_err_t odfs_cache_read(odfs_cache_t *cache, uint32_t i; uint32_t victim = 0; uint32_t oldest_age = UINT32_MAX; - uint32_t used = 0; + int victim_valid; + int32_t hit; odfs_err_t err; if (!cache || !cache->entries || !out) @@ -85,18 +201,12 @@ odfs_err_t odfs_cache_read(odfs_cache_t *cache, cache->stats.reads++; cache->clock++; - /* search for hit */ - for (i = 0; i < cache->capacity; i++) { - if (cache->entries[i].valid) { - used++; - if (cache->entries[i].lba == lba) { - /* hit */ - cache->entries[i].age = cache->clock; - cache->stats.hits++; - *out = cache->entries[i].data; - return ODFS_OK; - } - } + hit = cache_find_index(cache, lba); + if (hit >= 0) { + cache->entries[hit].age = cache->clock; + cache->stats.hits++; + *out = cache->entries[hit].data; + return ODFS_OK; } /* miss — find victim (LRU or first invalid) */ @@ -113,31 +223,169 @@ odfs_err_t odfs_cache_read(odfs_cache_t *cache, } } - /* evicting a valid entry */ - cache->stats.evictions++; - fill: + victim_valid = cache->entries[victim].valid; err = odfs_media_read(cache->media, lba, 1, cache->entries[victim].data); if (err != ODFS_OK) return err; + if (victim_valid) { + cache_remove_index(cache, victim); + cache->stats.evictions++; + } else { + cache->valid_count++; + } + cache->entries[victim].lba = lba; cache->entries[victim].age = cache->clock; cache->entries[victim].valid = 1; + cache_insert_index(cache, victim); - /* track high-water mark */ - used = 0; - for (i = 0; i < cache->capacity; i++) { - if (cache->entries[i].valid) - used++; - } - if (used > cache->stats.max_used) - cache->stats.max_used = used; + if (cache->valid_count > cache->stats.max_used) + cache->stats.max_used = cache->valid_count; *out = cache->entries[victim].data; return ODFS_OK; } +static odfs_err_t cache_copy_sector(odfs_cache_t *cache, + uint32_t lba, + uint32_t offset, + uint8_t *out, + size_t len) +{ + const uint8_t *sector; + odfs_err_t err; + + err = odfs_cache_read(cache, lba, §or); + if (err != ODFS_OK) + return err; + + memcpy(out, sector + offset, len); + return ODFS_OK; +} + +odfs_err_t odfs_cache_read_bytes(odfs_cache_t *cache, + uint32_t start_lba, + uint64_t offset, + void *buf, + size_t *len) +{ + uint8_t *out = buf; + uint32_t sector_size; + uint64_t lba64; + uint32_t lba; + uint32_t sector_off; + size_t want; + size_t done = 0; + odfs_err_t err; + + if (!cache || !cache->entries || !cache->media || !buf || !len) + return ODFS_ERR_INVAL; + + sector_size = cache->sector_size; + if (sector_size == 0) + return ODFS_ERR_INVAL; + + want = *len; + if (want == 0) + return ODFS_OK; + + lba64 = (uint64_t)start_lba + offset / sector_size; + if (lba64 > UINT32_MAX) + return ODFS_ERR_RANGE; + lba = (uint32_t)lba64; + sector_off = (uint32_t)(offset % sector_size); + + if (sector_off != 0) { + size_t chunk = sector_size - sector_off; + + if (chunk > want) + chunk = want; + + err = cache_copy_sector(cache, lba, sector_off, out, chunk); + if (err != ODFS_OK) { + *len = done; + return err; + } + + done += chunk; + out += chunk; + if (done < want && lba == UINT32_MAX) { + *len = done; + return ODFS_ERR_RANGE; + } + lba++; + } + + while (want - done >= sector_size) { + size_t full_sectors = (want - done) / sector_size; + uint64_t next_lba; + uint32_t count; + size_t bytes; + + if (full_sectors < ODFS_CACHE_STREAM_MIN_SECTORS) + break; + + if (full_sectors > UINT32_MAX) + count = UINT32_MAX; + else + count = (uint32_t)full_sectors; + + if ((size_t)count > ((size_t)-1) / sector_size) + count = (uint32_t)(((size_t)-1) / sector_size); + if (count == 0) + return ODFS_ERR_OVERFLOW; + if ((uint64_t)count > (uint64_t)UINT32_MAX - lba + 1u) + count = (uint32_t)((uint64_t)UINT32_MAX - lba + 1u); + + err = odfs_media_read(cache->media, lba, count, out); + if (err != ODFS_OK) { + *len = done; + return err; + } + + bytes = (size_t)count * sector_size; + done += bytes; + out += bytes; + next_lba = (uint64_t)lba + count; + if (done < want && next_lba > UINT32_MAX) { + *len = done; + return ODFS_ERR_RANGE; + } + lba = (uint32_t)next_lba; + } + + while (want - done >= sector_size) { + err = cache_copy_sector(cache, lba, 0, out, sector_size); + if (err != ODFS_OK) { + *len = done; + return err; + } + done += sector_size; + out += sector_size; + if (done < want && lba == UINT32_MAX) { + *len = done; + return ODFS_ERR_RANGE; + } + lba++; + } + + if (done < want) { + size_t tail = want - done; + + err = cache_copy_sector(cache, lba, 0, out, tail); + if (err != ODFS_OK) { + *len = done; + return err; + } + done += tail; + } + + *len = done; + return ODFS_OK; +} + const odfs_cache_stats_t *odfs_cache_get_stats(const odfs_cache_t *cache) { return &cache->stats; diff --git a/core/namefix.c b/core/namefix.c index 7f6041f..00672dd 100644 --- a/core/namefix.c +++ b/core/namefix.c @@ -13,21 +13,67 @@ #include +#define ODFS_NAMEFIX_CHUNK_SIZE 4096u + void odfs_namefix_init(odfs_namefix_state_t *state) { state->head = NULL; + state->chunks = NULL; } void odfs_namefix_destroy(odfs_namefix_state_t *state) { - odfs_namefix_entry_t *entry = state->head; - while (entry) { - odfs_namefix_entry_t *next = entry->next; - odfs_free(entry->name); - odfs_free(entry); - entry = next; + odfs_namefix_chunk_t *chunk = state->chunks; + + while (chunk) { + odfs_namefix_chunk_t *next = chunk->next; + odfs_free(chunk); + chunk = next; } + state->head = NULL; + state->chunks = NULL; +} + +static size_t odfs_namefix_align(size_t size) +{ + size_t align = sizeof(void *); + + return (size + align - 1u) & ~(align - 1u); +} + +static void *odfs_namefix_alloc(odfs_namefix_state_t *state, size_t size) +{ + odfs_namefix_chunk_t *chunk; + size_t chunk_size = ODFS_NAMEFIX_CHUNK_SIZE; + size_t total; + + if (!state || size == 0) + return NULL; + + size = odfs_namefix_align(size); + chunk = state->chunks; + if (chunk && size <= chunk->size - chunk->used) { + void *ptr = (unsigned char *)(chunk + 1) + chunk->used; + chunk->used += size; + return ptr; + } + + if (size > chunk_size) + chunk_size = size; + if (chunk_size > (size_t)-1 - sizeof(*chunk)) + return NULL; + + total = sizeof(*chunk) + chunk_size; + chunk = odfs_malloc(total); + if (!chunk) + return NULL; + + chunk->next = state->chunks; + chunk->used = size; + chunk->size = chunk_size; + state->chunks = chunk; + return (void *)(chunk + 1); } static int odfs_namefix_contains(const odfs_namefix_state_t *state, @@ -45,21 +91,14 @@ static int odfs_namefix_contains(const odfs_namefix_state_t *state, static odfs_err_t odfs_namefix_remember(odfs_namefix_state_t *state, const char *name) { - size_t len = strlen(name); - odfs_namefix_entry_t *entry = odfs_malloc(sizeof(*entry)); - char *copy; + size_t len = strlen(name) + 1u; + odfs_namefix_entry_t *entry; + entry = odfs_namefix_alloc(state, sizeof(*entry) + len); if (!entry) return ODFS_ERR_NOMEM; - copy = odfs_malloc(len + 1); - if (!copy) { - odfs_free(entry); - return ODFS_ERR_NOMEM; - } - - memcpy(copy, name, len + 1); - entry->name = copy; + memcpy(entry->name, name, len); entry->next = state->head; state->head = entry; return ODFS_OK; diff --git a/docs/ODFileSystem.readme b/docs/ODFileSystem.readme new file mode 100644 index 0000000..b530621 --- /dev/null +++ b/docs/ODFileSystem.readme @@ -0,0 +1,131 @@ +Short: Modern optical-disc filesystem +Author: stefan.reinauer@coreboot.org (Stefan Reinauer) +Uploader: stefan reinauer coreboot org (Stefan Reinauer) +Type: disk/cdrom +Version: [VERSION] +Requires: AmigaOS 3.x, CD/DVD drive or emulator CD device +Architecture: m68k-amigaos + +ODFileSystem +============ + +ODFileSystem is a read-only optical-disc filesystem handler for +AmigaOS. It mounts CD-ROM, DVD, Blu-ray, and image-backed optical +media through the normal AmigaDOS handler interface. + +The AmigaOS 3 release archive contains the m68k handlers intended for +classic Amiga systems and emulators. Copy the normal handler to L: and +use it from a DOSDriver or Mountlist entry. + + +Archive Contents +---------------- + +- ODFileSystem + Normal disk-loadable filesystem handler. Copy this to + L:ODFileSystem for regular use. + +- ODFileSystem-test + Test build with serial debug output enabled. + +- ODFileSystem-rom + Size-reduced ROM-profile build. + +- ODFileSystem-rom-test + Test build of the ROM profile with serial debug output enabled. + +- README.md + This file. + + +Features +-------- + +- ISO 9660 directory and file access +- Rock Ridge long names and metadata +- Joliet Unicode names +- UDF bridge-disc support +- HFS and HFS+ data-fork access +- Multisession media support +- CDDA tracks exposed as virtual audio files +- Read-only operation for safe use with optical media + +For ISO-family hybrid discs, ODFileSystem prefers Rock Ridge, then +Joliet, then plain ISO 9660. For bridge discs it prefers the ISO view +by default; UDF and HFS can be selected explicitly through mount +options. + + +Installation +------------ + +1. Copy the handler to: + + L:ODFileSystem + +2. Install or edit a DOSDriver entry, for example: + + DEVS:DOSDrivers/CD0 + +3. Set the filesystem in the DOSDriver or Mountlist to: + + FileSystem = L:ODFileSystem + +4. Set Device and Unit for your CD/DVD device. + +Typical examples: + +- SCSI CD-ROM: Device = scsi.device, Unit = SCSI ID +- A1200 IDE slave: Device = scsi.device, Unit = 1 +- WinUAE/FS-UAE: use the emulator's configured CD device/unit + +Mount the device from Workbench by double-clicking the DOSDriver icon, +or from Shell with: + + Mount CD0: + + +Mount Options +------------- + +Options can be supplied with the DOSDriver Control string: + + Control = "LOWERCASE UDF FILEBUFFERS=128" + +Supported options include: + +- LOWERCASE + Lowercase plain ISO 9660 names. + +- NOROCKRIDGE or NORR + Disable Rock Ridge. + +- NOJOLIET or NOJ + Disable Joliet. + +- UDF + Prefer UDF on bridge discs. + +- HFSFIRST or HF + Prefer HFS on hybrid HFS/ISO discs. + +- FILEBUFFERS=n or FB=n + Set the filesystem block-cache size. + +- AIFF + Expose CDDA audio tracks as AIFF files instead of WAV files. + + +Limitations +----------- + +ODFileSystem is read-only. + +HFS and HFS+ support exposes data forks only. Resource forks and +Finder metadata are not presented through the AmigaDOS view, so some +classic Mac media may not appear exactly as they would on Mac OS. + +Project source and current documentation are available from the +ODFileSystem repository: + + https://github.com/reinauer/ODFileSystem diff --git a/docs/ODFileSystem_OS4.readme b/docs/ODFileSystem_OS4.readme new file mode 100644 index 0000000..2ab5131 --- /dev/null +++ b/docs/ODFileSystem_OS4.readme @@ -0,0 +1,162 @@ +Short: Modern optical-disc filesystem for OS4 +Author: stefan.reinauer@coreboot.org (Stefan Reinauer) +Uploader: stefan reinauer coreboot org (Stefan Reinauer) +Type: disk/cdrom +Version: [VERSION] +Requires: AmigaOS 4.1 Final Edition +Architecture: ppc-amigaos + +ODFileSystem for AmigaOS 4 +========================== + +ODFileSystem is a read-only optical-disc filesystem handler for +AmigaOS. It mounts CD-ROM, DVD, Blu-ray, and image-backed optical +media through the normal AmigaDOS filesystem interface. + +This archive contains the PowerPC AmigaOS 4 build. It is separate +from the m68k AmigaOS 3 archive. + + +Archive Contents +---------------- + +- ODFileSystem-amigaos4 + Disk-loadable filesystem handler. Copy or rename this to + L:ODFileSystem when installing it for use with a DOSDriver or + mountlist entry. + +- CDFileSystem + Relocatable Kickstart resident module. Use this to replace the + AmigaOS 4 Kickstart CDFileSystem module. + +- ODFileSystem-amigaos4-test + Test build of the disk-loadable handler with serial debug output + enabled. + +- CDFileSystem-test + Test build of the Kickstart resident module with serial debug output + enabled. + +- README.md + This file. + + +Features +-------- + +- ISO 9660 directory and file access +- Rock Ridge long names and metadata +- Joliet Unicode names +- UDF bridge-disc support +- HFS and HFS+ data-fork access +- Multisession media support +- CDDA tracks exposed as virtual audio files +- Read-only operation for safe use with optical media + +For ISO-family hybrid discs, ODFileSystem prefers Rock Ridge, then +Joliet, then plain ISO 9660. For bridge discs it prefers the ISO view +by default; UDF and HFS can be selected explicitly through mount +options. + + +Disk-Loaded Installation +------------------------ + +Use this method if you want ODFileSystem to behave like a normal +filesystem handler loaded from disk. + +1. Copy the handler to: + + L:ODFileSystem + +2. Install or edit a DOSDriver entry, for example: + + DEVS:DOSDrivers/CD0 + +3. Set the filesystem in the DOSDriver or Mountlist to: + + FileSystem = L:ODFileSystem + +4. Set Device and Unit for your CD/DVD device. + +Mount the device from Workbench by double-clicking the DOSDriver icon, +or from Shell with: + + Mount CD0: + + +Kickstart Installation +---------------------- + +Use this method if you want ODFileSystem to replace the Kickstart +CDFileSystem module used during early boot. + +1. Back up your existing Kickstart CD filesystem module: + + Kickstart/CDFileSystem + +2. Copy the supplied module to: + + Kickstart/CDFileSystem + + The file name should remain CDFileSystem. Do not rename it to + ODFileSystem.kmod unless you also edit Kicklayout. + +3. Check that Kicklayout contains this module entry: + + MODULE Kickstart/CDFileSystem + +4. Rebuild or update your Kickstart.zip if your machine or emulator + boots from a zipped Kickstart directory. + +5. Reboot. + +The supplied CDFileSystem file is an AmigaOS 4 Kickstart resident +module. It is intentionally different from the disk-loadable +ODFileSystem handler. + + +Mount Options +------------- + +Options can be supplied with the DOSDriver Control string: + + Control = "LOWERCASE UDF FILEBUFFERS=128" + +Supported options include: + +- LOWERCASE + Lowercase plain ISO 9660 names. + +- NOROCKRIDGE or NORR + Disable Rock Ridge. + +- NOJOLIET or NOJ + Disable Joliet. + +- UDF + Prefer UDF on bridge discs. + +- HFSFIRST or HF + Prefer HFS on hybrid HFS/ISO discs. + +- FILEBUFFERS=n or FB=n + Set the filesystem block-cache size. + +- AIFF + Expose CDDA audio tracks as AIFF files instead of WAV files. + + +Limitations +----------- + +ODFileSystem is read-only. + +HFS and HFS+ support exposes data forks only. Resource forks and +Finder metadata are not presented through the AmigaDOS view, so some +classic Mac media may not appear exactly as they would on Mac OS. + +Project source and current documentation are available from the +ODFileSystem repository: + + https://github.com/reinauer/ODFileSystem diff --git a/docs/rom-profile.md b/docs/rom-profile.md index f61f1b1..6ada4d5 100644 --- a/docs/rom-profile.md +++ b/docs/rom-profile.md @@ -38,7 +38,7 @@ serial output disabled. These targets do not reuse or clobber the normal Amiga build in `build/amiga/`. ## Size Budget -`make rom` enforces a default release size limit of `30000` bytes. +`make rom` enforces a default release size limit of `40960` bytes. If intentional growth needs a higher ceiling, override it with: diff --git a/include/odfs/alloc.h b/include/odfs/alloc.h index e2ee590..cdf372c 100644 --- a/include/odfs/alloc.h +++ b/include/odfs/alloc.h @@ -13,14 +13,25 @@ #if ODFS_PLATFORM_AMIGA +#include "amiga_target_compat.h" + #include +#include +#if ODFS_AMIGA_OS4 +#include +#include +#endif #include static inline void *odfs_malloc(size_t size) { if (size == 0) size = 1; +#if ODFS_AMIGA_OS4 + return AllocVecTags((ULONG)size, AVT_Type, MEMF_SHARED, TAG_END); +#else return AllocVec((ULONG)size, MEMF_PUBLIC); +#endif } static inline void *odfs_calloc(size_t count, size_t size) @@ -34,7 +45,14 @@ static inline void *odfs_calloc(size_t count, size_t size) if (total == 0) total = 1; +#if ODFS_AMIGA_OS4 + return AllocVecTags((ULONG)total, + AVT_Type, MEMF_SHARED, + AVT_ClearWithValue, 0, + TAG_END); +#else return AllocVec((ULONG)total, MEMF_PUBLIC | MEMF_CLEAR); +#endif } static inline void odfs_free(void *ptr) diff --git a/include/odfs/cache.h b/include/odfs/cache.h index 5b25f81..a1ce45c 100644 --- a/include/odfs/cache.h +++ b/include/odfs/cache.h @@ -33,7 +33,11 @@ typedef struct odfs_cache_entry { /* block cache */ typedef struct odfs_cache { odfs_cache_entry_t *entries; + int32_t *buckets; /* hash bucket -> entry index */ + int32_t *next; /* hash collision chain per entry */ uint32_t capacity; /* number of entries */ + uint32_t hash_size; + uint32_t valid_count; uint32_t sector_size; uint32_t clock; /* LRU clock */ odfs_cache_stats_t stats; @@ -51,6 +55,17 @@ odfs_err_t odfs_cache_read(odfs_cache_t *cache, uint32_t lba, const uint8_t **out); +/* + * Read bytes from a contiguous extent. Partial edge sectors and small reads + * use the block cache; aligned runs of multiple full sectors bypass it so + * streaming file reads can be coalesced by the media layer. + */ +odfs_err_t odfs_cache_read_bytes(odfs_cache_t *cache, + uint32_t start_lba, + uint64_t offset, + void *buf, + size_t *len); + /* invalidate all entries */ void odfs_cache_flush(odfs_cache_t *cache); diff --git a/include/odfs/namefix.h b/include/odfs/namefix.h index 1e44ea2..8c57ac4 100644 --- a/include/odfs/namefix.h +++ b/include/odfs/namefix.h @@ -12,11 +12,18 @@ typedef struct odfs_namefix_entry { struct odfs_namefix_entry *next; - char *name; + char name[1]; } odfs_namefix_entry_t; +typedef struct odfs_namefix_chunk { + struct odfs_namefix_chunk *next; + size_t used; + size_t size; +} odfs_namefix_chunk_t; + typedef struct odfs_namefix_state { odfs_namefix_entry_t *head; + odfs_namefix_chunk_t *chunks; } odfs_namefix_state_t; void odfs_namefix_init(odfs_namefix_state_t *state); diff --git a/platform/amiga/common/sys_compat.h b/platform/amiga/common/sys_compat.h new file mode 100644 index 0000000..3c485b5 --- /dev/null +++ b/platform/amiga/common/sys_compat.h @@ -0,0 +1,65 @@ +/* + * sys_compat.h - Amiga-family OS integration boundary + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef ODFS_AMIGA_SYS_COMPAT_H +#define ODFS_AMIGA_SYS_COMPAT_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct Hook; + +typedef LONG (*odfs_amiga_interrupt_fn)(APTR data); + +extern struct ExecBase *SysBase; +extern struct DosLibrary *DOSBase; + +void odfs_amiga_init_sysbase(void); +struct ExecBase *odfs_amiga_sysbase(void); +struct DosLibrary *odfs_amiga_dosbase(void); + +int odfs_amiga_open_libraries(void); +void odfs_amiga_close_libraries(void); + +void *odfs_amiga_alloc_mem(ULONG size, ULONG flags); +void odfs_amiga_free_mem(void *ptr, ULONG size); + +struct MsgPort *odfs_amiga_create_msg_port(void); +void odfs_amiga_delete_msg_port(struct MsgPort *port); +struct IORequest *odfs_amiga_create_io_request(struct MsgPort *port, + ULONG size); +void odfs_amiga_delete_io_request(struct IORequest *req); + +LONG odfs_amiga_alloc_signal(LONG num); +void odfs_amiga_free_signal(LONG num); + +/* + * Allocate a DosList entry (device or volume node) with the given + * dol_Type and a BCPL copy of name, all other fields zeroed. The + * returned node must be released with odfs_amiga_delete_dos_entry() + * after it has been removed from the DOS list. + */ +void *odfs_amiga_create_dos_entry(const char *name, LONG type); +void odfs_amiga_delete_dos_entry(void *node); + +void odfs_amiga_init_interrupt(struct Interrupt *intr, + const char *name, + APTR data, + odfs_amiga_interrupt_fn code); + +ULONG odfs_amiga_call_hook_pkt(struct Hook *hook, APTR object, APTR message); + +#endif /* ODFS_AMIGA_SYS_COMPAT_H */ diff --git a/platform/amiga/handler.h b/platform/amiga/handler.h index fd8aa64..eed2ff5 100644 --- a/platform/amiga/handler.h +++ b/platform/amiga/handler.h @@ -11,10 +11,12 @@ #include #include #include +#include #include #include #include +#include "amiga_target_compat.h" #include "aros_compat.h" #include "odfs/api.h" @@ -23,16 +25,60 @@ typedef struct odfs_entry odfs_entry_t; typedef struct odfs_lock odfs_lock_t; typedef struct odfs_fh odfs_fh_t; typedef struct odfs_changeint_data odfs_changeint_data_t; +#if ODFS_AMIGA_OS4 +struct FileSystemVectorPort; +#endif struct odfs_changeint_data { struct Task *task; ULONG sigmask; }; +/* Filesystem DOSType (matches the OS4 FileSysEntry). */ +#define ODFS_OS4_CD_DOSTYPE 0x43443031UL /* 'CD01' */ + +/* + * Media-adapter context passed to the odfs_media_ops callbacks. It must + * be per-handler-process: diskboot starts one handler process per CD + * unit, so a shared instance would let a later process clobber the + * device binding of an earlier one. It lives inside handler_global so + * each process owns its own copy. + */ +struct handler_global; +typedef struct amiga_media_ctx { + struct handler_global *g; +} amiga_media_ctx_t; + +#if !ODFS_AMIGA_OS4 +typedef struct odfs_exnext_cursor { + ULONG dir_key; + ULONG previous_key; + uint32_t resume; + int valid; + int cdda_emitted; +} odfs_exnext_cursor_t; +#endif + /* ---- handler globals ---- */ typedef struct handler_global { + struct MsgPort *process_port; /* owning process message port */ struct MsgPort *dosport; /* DOS message port */ +#if ODFS_AMIGA_OS4 + struct Task *handler_task; /* task that owns handler ports */ + struct Task *vector_io_task; /* task owning cached vector I/O */ + struct MsgPort *vector_io_port; /* cached caller-task reply port */ + struct IOStdReq *vector_io_req; /* cached caller-task I/O request */ + struct FileSystemVectorPort *vector_port; /* native OS4 vector port */ + LONG vector_sigbit; /* signal bit used by vector port */ + /* + * OS4 vector callbacks run in the calling process context, so all + * access to handler state from the vectors and from the handler + * process must hold this semaphore. + */ + struct SignalSemaphore fs_sem; +#endif + amiga_media_ctx_t media_ctx; /* per-process media-adapter ctx */ struct DeviceNode *devnode; /* startup packet device node */ struct FileSysStartupMsg *fssm; /* startup packet FSSM */ struct DeviceNode *published_devnode; /* DOS device-list entry */ @@ -87,6 +133,10 @@ typedef struct handler_global { odfs_node_t cdda_root; /* CDDA virtual dir node */ int has_cdda; /* audio tracks detected */ +#if !ODFS_AMIGA_OS4 + odfs_exnext_cursor_t root_exnext; /* null-lock root ExNext cursor */ +#endif + /* lock list */ struct MinList locklist; /* active locks */ struct MinList fhlist; /* active file handles */ @@ -98,6 +148,7 @@ typedef struct handler_global { struct odfs_volume { struct MinNode node; struct DeviceList *volnode; + odfs_lock_t *lock_head; ULONG id; ULONG object_count; }; @@ -108,6 +159,8 @@ struct odfs_entry { odfs_volume_t *volume; odfs_node_t fnode; odfs_node_t parent_node; + odfs_node_t grandparent_node; + int has_grandparent; ULONG refcount; }; @@ -115,12 +168,20 @@ struct odfs_entry { struct odfs_lock { struct MinNode node; /* for locklist */ +#if ODFS_AMIGA_OS4 + struct Lock *lock; /* DOS-allocated OS4 lock */ +#else struct FileLock lock; /* DOS lock (MUST be at known offset) */ ULONG dos_private[2]; /* reserve fl_SIZEOF..fl_SIZEOF+7 for DOS */ + odfs_exnext_cursor_t exnext; /* handler-owned ExNext resume cursor */ +#endif odfs_entry_t *entry; /* shared object metadata */ ULONG key; /* unique key */ + odfs_lock_t *volume_prev; /* per-volume DOS lock chain */ + odfs_lock_t *volume_next; /* per-volume DOS lock chain */ }; +#if !ODFS_AMIGA_OS4 typedef char odfs_lock_private_offset_must_match[ (offsetof(odfs_lock_t, dos_private) == offsetof(odfs_lock_t, lock) + sizeof(struct FileLock)) ? 1 : -1 @@ -128,6 +189,7 @@ typedef char odfs_lock_private_offset_must_match[ typedef char odfs_lock_private_size_must_match[ (sizeof(((odfs_lock_t *)0)->dos_private) == 8) ? 1 : -1 ]; +#endif /* ---- file handle wrapper ---- */ @@ -135,11 +197,34 @@ struct odfs_fh { struct MinNode node; /* for tracking */ odfs_entry_t *entry; /* shared object metadata */ LONG access; /* originating DOS access mode */ - ULONG pos; /* current read position */ + uint64_t pos; /* current read position */ }; /* ---- helper macros ---- */ +#if ODFS_AMIGA_OS4 +#define ODFS_LOCK_DOS(ol) \ + (((ol) != NULL) ? (ol)->lock : NULL) + +/* Convert a direct DOS lock pointer to our odfs_lock_t */ +#define LOCK_FROM_PTR(ptr) \ + ((ptr) ? (odfs_lock_t *)((struct Lock *)(ptr))->fl_FSPrivate1 : NULL) + +/* Convert BPTR lock to our odfs_lock_t */ +#define LOCK_FROM_BPTR(bptr) \ + ((bptr) ? LOCK_FROM_PTR((struct Lock *)BADDR(bptr)) : NULL) + +/* Convert odfs_lock_t to BPTR for DOS */ +#define LOCK_TO_BPTR(ol) \ + (((ol) && (ol)->lock) ? MKBADDR((ol)->lock) : 0) + +/* Convert odfs_lock_t to a direct DOS lock pointer */ +#define LOCK_TO_PTR(ol) \ + (((ol) && (ol)->lock) ? (ol)->lock : NULL) +#else +#define ODFS_LOCK_DOS(ol) \ + (((ol) != NULL) ? &(ol)->lock : NULL) + /* Convert BPTR lock to our odfs_lock_t */ #define LOCK_FROM_BPTR(bptr) \ ((bptr) ? (odfs_lock_t *)((UBYTE *)BADDR(bptr) - \ @@ -149,6 +234,16 @@ struct odfs_fh { #define LOCK_TO_BPTR(ol) \ ((ol) ? MKBADDR(&(ol)->lock) : 0) +/* Convert a direct DOS lock pointer to our odfs_lock_t */ +#define LOCK_FROM_PTR(ptr) \ + ((ptr) ? (odfs_lock_t *)((UBYTE *)(ptr) - \ + offsetof(odfs_lock_t, lock)) : NULL) + +/* Convert odfs_lock_t to a direct DOS lock pointer */ +#define LOCK_TO_PTR(ol) \ + ((ol) ? &(ol)->lock : NULL) +#endif + /* BCPL string to C string (AROS-compatible) */ static inline void bstr_to_cstr(BSTR bstr, char *buf, int bufsize) { @@ -196,7 +291,103 @@ static inline LONG odfs_err_to_dos(odfs_err_t err) } } +typedef struct odfs_handler_node_info { + const char *name; + const char *comment; + ULONG key; + ULONG protection; + uint64_t size; + struct DateStamp date; + LONG fib_type; + int is_dir; +} odfs_handler_node_info_t; + +/* shared operations used by packet and OS4 vector frontends */ +void odfs_handler_fill_node_info(handler_global_t *g, + const odfs_node_t *node, + odfs_handler_node_info_t *info); +#if ODFS_AMIGA_OS4 +LONG odfs_handler_resolve_object_node(handler_global_t *g, + odfs_lock_t *parent_lock, + const char *path, + odfs_node_t *node_out, + odfs_node_t *parent_out); +#endif +LONG odfs_handler_lock_object(handler_global_t *g, + odfs_lock_t *parent_lock, + const char *path, + LONG access, + odfs_lock_t **out); +LONG odfs_handler_free_lock_object(handler_global_t *g, odfs_lock_t *ol); +LONG odfs_handler_dup_lock_object(handler_global_t *g, + odfs_lock_t *src, + odfs_lock_t **out); +LONG odfs_handler_dup_lock_from_fh(handler_global_t *g, + odfs_fh_t *fh, + odfs_lock_t **out); +LONG odfs_handler_parent_lock_object(handler_global_t *g, + odfs_lock_t *ol, + odfs_lock_t **out); +LONG odfs_handler_parent_fh_object(handler_global_t *g, + odfs_fh_t *fh, + odfs_lock_t **out); +LONG odfs_handler_same_lock_object(handler_global_t *g, + odfs_lock_t *l1, + odfs_lock_t *l2, + LONG *same_result); +LONG odfs_handler_same_file_object(handler_global_t *g, + odfs_fh_t *fh1, + odfs_fh_t *fh2, + LONG *same_result); +LONG odfs_handler_open_object(handler_global_t *g, + odfs_lock_t *dirlock, + const char *path, + LONG mode, + odfs_fh_t **out); +LONG odfs_handler_open_from_lock_object(handler_global_t *g, + odfs_lock_t *ol, + odfs_fh_t **out); +LONG odfs_handler_close_object(handler_global_t *g, odfs_fh_t *fh); +LONG odfs_handler_read_object(handler_global_t *g, + odfs_fh_t *fh, + void *buf, + LONG len, + LONG *actual_out); +LONG odfs_handler_seek_object(handler_global_t *g, + odfs_fh_t *fh, + int64_t offset, + LONG mode, + int64_t *oldpos_out); +LONG odfs_handler_change_lock_mode(handler_global_t *g, + odfs_lock_t *ol, + LONG mode); +LONG odfs_handler_change_file_mode(handler_global_t *g, + odfs_fh_t *fh, + LONG mode); +LONG odfs_handler_get_file_position(handler_global_t *g, + odfs_fh_t *fh, + int64_t *pos_out); +LONG odfs_handler_get_file_size(handler_global_t *g, + odfs_fh_t *fh, + int64_t *size_out); +LONG odfs_handler_fill_info(handler_global_t *g, + odfs_lock_t *ol, + struct InfoData *info); +LONG odfs_handler_get_lock_node(handler_global_t *g, + odfs_lock_t *ol, + const odfs_node_t **node_out); +LONG odfs_handler_get_fh_node(handler_global_t *g, + odfs_fh_t *fh, + const odfs_node_t **node_out); +LONG odfs_handler_next_dir_entry(handler_global_t *g, + odfs_lock_t *ol, + ULONG previous_key, + odfs_node_t *entry_out, + ULONG *key_out); +LONG odfs_handler_inhibit(handler_global_t *g, LONG state); + /* handler entry point (called from startup.S) */ void handler_main(void); +void handler_main_startup(struct Message *startup_msg); #endif /* ODFS_HANDLER_H */ diff --git a/platform/amiga/handler_main.c b/platform/amiga/handler_main.c index 703ada0..777e39b 100644 --- a/platform/amiga/handler_main.c +++ b/platform/amiga/handler_main.c @@ -9,6 +9,20 @@ */ #include "handler.h" +#include "sys_compat.h" + +#if ODFS_AMIGA_OS4 +#include "vector_port.h" +/* + * OS4 vector callbacks run in the calling process context; the handler + * process must hold the same semaphore while it touches handler state. + */ +#define ODFS_FS_LOCK(g) ObtainSemaphore(&(g)->fs_sem) +#define ODFS_FS_UNLOCK(g) ReleaseSemaphore(&(g)->fs_sem) +#else +#define ODFS_FS_LOCK(g) ((void)0) +#define ODFS_FS_UNLOCK(g) ((void)0) +#endif #if ODFS_FEATURE_CDDA #include "cdda/cdda.h" @@ -25,7 +39,6 @@ #include #include -#include #include @@ -42,16 +55,15 @@ static const char version_string[] __attribute__((used)) = "$VER: ODFileSystem " ODFS_GIT_VERSION " (" ODFS_AMIGA_DATE ")"; -/* library bases — set by handler_main() */ -struct ExecBase *SysBase; -struct DosLibrary *DOSBase; -struct Library *UtilityBase; - /* forward declarations */ static void handle_packet(handler_global_t *g, struct DosPacket *pkt); static void return_packet(handler_global_t *g, struct DosPacket *pkt); static void publish_device_node(handler_global_t *g); static void unpublish_device_node(handler_global_t *g); +#if ODFS_AMIGA_OS4 +static LONG activate_vector_port(handler_global_t *g); +static void deactivate_vector_port(handler_global_t *g); +#endif static void mount_volume(handler_global_t *g); static void unmount_volume(handler_global_t *g); static void free_volume(odfs_volume_t *volume); @@ -95,9 +107,8 @@ static int scsi_is_unsupported_command(const uint8_t *sense); * (x86 AROS) targets. */ -typedef struct amiga_media_ctx { - handler_global_t *g; -} amiga_media_ctx_t; +/* amiga_media_ctx_t is defined in handler.h and embedded per-process + * in handler_global so concurrent handler processes never share it. */ static int scsi_is_unsupported_command(const uint8_t *sense) { @@ -107,8 +118,10 @@ static int scsi_is_unsupported_command(const uint8_t *sense) return ((sense[2] & 0x0f) == 0x05 && sense[12] == 0x20); } -static LONG changeint_handler(odfs_changeint_data_t *ci asm("a1")) +static LONG changeint_signal(APTR data) { + odfs_changeint_data_t *ci = data; + if (ci && ci->task && ci->sigmask) Signal(ci->task, ci->sigmask); return 0; @@ -173,13 +186,13 @@ static void notify_workbench_disk_change(BOOL inserted) struct IOStdReq *req; struct InputEvent event; - port = CreateMsgPort(); + port = odfs_amiga_create_msg_port(); if (!port) return; - req = (struct IOStdReq *)CreateIORequest(port, sizeof(*req)); + req = (struct IOStdReq *)odfs_amiga_create_io_request(port, sizeof(*req)); if (!req) { - DeleteMsgPort(port); + odfs_amiga_delete_msg_port(port); return; } @@ -195,18 +208,93 @@ static void notify_workbench_disk_change(BOOL inserted) CloseDevice((struct IORequest *)req); } - DeleteIORequest((struct IORequest *)req); - DeleteMsgPort(port); + odfs_amiga_delete_io_request((struct IORequest *)req); + odfs_amiga_delete_msg_port(port); +} + +#if ODFS_AMIGA_OS4 +static void release_vector_io_request(handler_global_t *g) +{ + if (!g) + return; + + if (g->vector_io_req) { + odfs_amiga_delete_io_request((struct IORequest *)g->vector_io_req); + g->vector_io_req = NULL; + } + if (g->vector_io_port) { + odfs_amiga_delete_msg_port(g->vector_io_port); + g->vector_io_port = NULL; + } + g->vector_io_task = NULL; +} + +static struct IOStdReq *vector_io_request_for_current_task(handler_global_t *g) +{ + struct Task *task; + struct MsgPort *port; + struct IOStdReq *req; + + if (!g || !g->devreq) + return NULL; + + task = FindTask(NULL); + if (task == g->handler_task) + return g->devreq; + + if (g->vector_io_req && g->vector_io_task == task) { + g->vector_io_req->io_Device = g->devreq->io_Device; + g->vector_io_req->io_Unit = g->devreq->io_Unit; + return g->vector_io_req; + } + + release_vector_io_request(g); + + port = odfs_amiga_create_msg_port(); + if (!port) + return NULL; + + req = (struct IOStdReq *)odfs_amiga_create_io_request(port, sizeof(*req)); + if (!req) { + odfs_amiga_delete_msg_port(port); + return NULL; + } + + req->io_Device = g->devreq->io_Device; + req->io_Unit = g->devreq->io_Unit; + g->vector_io_task = task; + g->vector_io_port = port; + g->vector_io_req = req; + return req; } +#endif static odfs_err_t amiga_read_sectors(void *ctx, uint32_t lba, uint32_t count, void *buf) { amiga_media_ctx_t *am = ctx; handler_global_t *g = am->g; + struct IOStdReq *req = g->devreq; uint32_t total_bytes = count * g->sector_size; uint8_t *out = buf; uint32_t done = 0; + odfs_err_t ret = ODFS_OK; + +#if ODFS_AMIGA_OS4 + /* + * Native vector callbacks run in the caller's task, but g->devreq + * replies to the handler task's port. If a caller-task DoIO() has to + * wait for completion, the device signals the wrong task and the + * caller blocks forever. Use a request with a reply port owned by the + * current task for vector-context media I/O. Vector callbacks are + * serialized by fs_sem, so one cached caller-task request is enough. + */ + if (FindTask(NULL) != g->handler_task) { + req = vector_io_request_for_current_task(g); + if (!req) + return ODFS_ERR_NOMEM; + } +#endif /* * Read through the DMA-safe bounce buffer, one chunk at a time. @@ -232,36 +320,40 @@ static odfs_err_t amiga_read_sectors(void *ctx, uint32_t lba, byte_offset_lo = cur_lba * g->sector_size; } - g->devreq->io_Offset = byte_offset_lo; - g->devreq->io_Actual = byte_offset_hi; - g->devreq->io_Length = chunk; - g->devreq->io_Data = g->dma_buf; + req->io_Offset = byte_offset_lo; + req->io_Actual = byte_offset_hi; + req->io_Length = chunk; + req->io_Data = g->dma_buf; if (byte_offset_hi != 0) - g->devreq->io_Command = TD_READ64; + req->io_Command = TD_READ64; else - g->devreq->io_Command = CMD_READ; + req->io_Command = CMD_READ; - if (DoIO((struct IORequest *)g->devreq) != 0 || - g->devreq->io_Error != 0 || - g->devreq->io_Actual != chunk) { + if (DoIO((struct IORequest *)req) != 0 || + req->io_Error != 0 || + req->io_Actual != chunk) { ODFS_ERROR(&g->log, ODFS_SUB_IO, - "sector read failed lba=%lu count=%lu " - "chunk=%lu io_Error=%ld actual=%lu cmd=%lu", + "sector read failed unit=%lu lba=%lu count=%lu " + "chunk=%lu off=%lu io_Error=%ld actual=%lu cmd=%lu", + (unsigned long)g->devunit, (unsigned long)cur_lba, (unsigned long)count, (unsigned long)chunk, - (long)g->devreq->io_Error, - (unsigned long)g->devreq->io_Actual, - (unsigned long)g->devreq->io_Command); - return ODFS_ERR_IO; + (unsigned long)req->io_Offset, + (long)req->io_Error, + (unsigned long)req->io_Actual, + (unsigned long)req->io_Command); + ret = ODFS_ERR_IO; + goto out; } memcpy(out + done, g->dma_buf, chunk); done += chunk; } - return ODFS_OK; +out: + return ret; } static uint32_t amiga_sector_size(void *ctx) @@ -739,44 +831,6 @@ static odfs_err_t amiga_read_cdtext(void *ctx, uint8_t **buf_out, /* SCSI helper commands */ /* ------------------------------------------------------------------ */ -/* - * Issue SCSI Test Unit Ready (0x00). - * Returns 1 if drive is ready, 0 otherwise. - */ -static int scsi_test_unit_ready(handler_global_t *g) -{ - uint8_t cmd[6]; - struct SCSICmd scsi; - LONG io_rc; - - memset(cmd, 0, sizeof(cmd)); - memset(&scsi, 0, sizeof(scsi)); - - cmd[0] = 0x00; /* TEST UNIT READY */ - - scsi.scsi_Data = NULL; - scsi.scsi_Length = 0; - scsi.scsi_CmdLength = 6; - scsi.scsi_Command = cmd; - scsi.scsi_Flags = SCSIF_AUTOSENSE; - - g->devreq->io_Command = HD_SCSICMD; - g->devreq->io_Data = &scsi; - g->devreq->io_Length = sizeof(scsi); - - io_rc = DoIO((struct IORequest *)g->devreq); - if (io_rc != 0 || g->devreq->io_Error != 0 || scsi.scsi_Status != 0) { - ODFS_WARN(&g->log, ODFS_SUB_IO, - "TEST UNIT READY failed io_rc=%ld io_Error=%ld " - "scsi_Status=%lu", - (long)io_rc, (long)g->devreq->io_Error, - (unsigned long)scsi.scsi_Status); - return 0; - } - - return 1; -} - /* * Issue SCSI Mode Select (0x15) to set the block size. * @@ -854,6 +908,7 @@ static const odfs_media_ops_t amiga_media_ops = { /* ------------------------------------------------------------------ */ #if ODFS_SERIAL_DEBUG +#if !ODFS_AMIGA_OS4 static inline void raw_putchar(char c) { register char _d0 __asm("d0") = c; @@ -871,6 +926,7 @@ static void serial_puts(const char *s) while (*s) raw_putchar(*s++); } +#endif #if ODFS_PACKET_TRACE static void trace_pkt(handler_global_t *g, const char *tag, struct DosPacket *pkt) @@ -924,8 +980,12 @@ static void log_sink(odfs_log_level_t level, odfs_log_subsys_t subsys, (void)level; (void)subsys; (void)ctx; +#if ODFS_AMIGA_OS4 + DebugPrintF("%s\n", msg); +#else serial_puts(msg); raw_putchar('\n'); +#endif } #else static void log_sink(odfs_log_level_t level, odfs_log_subsys_t subsys, @@ -949,11 +1009,12 @@ static struct DeviceList *volume_node_ptr(const odfs_volume_t *volume) static odfs_entry_t *alloc_entry(odfs_volume_t *volume, const odfs_node_t *fnode, - const odfs_node_t *parent) + const odfs_node_t *parent, + const odfs_node_t *grandparent) { odfs_entry_t *entry; - entry = AllocMem(sizeof(*entry), MEMF_PUBLIC | MEMF_CLEAR); + entry = odfs_amiga_alloc_mem(sizeof(*entry), MEMF_PUBLIC | MEMF_CLEAR); if (!entry) return NULL; @@ -963,6 +1024,13 @@ static odfs_entry_t *alloc_entry(odfs_volume_t *volume, entry->parent_node = *parent; else entry->parent_node = *fnode; + if (grandparent) { + entry->grandparent_node = *grandparent; + entry->has_grandparent = 1; + } else { + entry->grandparent_node = entry->parent_node; + entry->has_grandparent = 0; + } entry->refcount = 1; return entry; } @@ -979,7 +1047,7 @@ static void release_entry(odfs_entry_t *entry) if (!entry) return; if (--entry->refcount == 0) - FreeMem(entry, sizeof(*entry)); + odfs_amiga_free_mem(entry, sizeof(*entry)); } static odfs_node_t *lock_node(odfs_lock_t *ol) @@ -992,6 +1060,13 @@ static odfs_node_t *lock_parent_node(odfs_lock_t *ol) return ol ? &ol->entry->parent_node : NULL; } +static odfs_node_t *lock_grandparent_node(odfs_lock_t *ol) +{ + if (!ol || !ol->entry->has_grandparent) + return NULL; + return &ol->entry->grandparent_node; +} + static odfs_node_t *fh_node(odfs_fh_t *fh) { return fh ? &fh->entry->fnode : NULL; @@ -1002,6 +1077,13 @@ static odfs_node_t *fh_parent_node(odfs_fh_t *fh) return fh ? &fh->entry->parent_node : NULL; } +static odfs_node_t *fh_grandparent_node(odfs_fh_t *fh) +{ + if (!fh || !fh->entry->has_grandparent) + return NULL; + return &fh->entry->grandparent_node; +} + static odfs_volume_t *fh_volume(odfs_fh_t *fh) { return fh ? fh->entry->volume : NULL; @@ -1043,7 +1125,7 @@ static odfs_volume_t *alloc_volume(handler_global_t *g, struct DeviceList *volno { odfs_volume_t *volume; - volume = AllocMem(sizeof(*volume), MEMF_PUBLIC | MEMF_CLEAR); + volume = odfs_amiga_alloc_mem(sizeof(*volume), MEMF_PUBLIC | MEMF_CLEAR); if (!volume) return NULL; @@ -1052,32 +1134,50 @@ static odfs_volume_t *alloc_volume(handler_global_t *g, struct DeviceList *volno return volume; } -static void rebuild_volume_locklist(handler_global_t *g, odfs_volume_t *volume) +static void link_volume_lock(odfs_volume_t *volume, odfs_lock_t *ol) { - odfs_lock_t *ol; - odfs_lock_t *prev = NULL; - BPTR head = 0; - - if (!volume || !volume->volnode) + if (!volume || !ol) return; Forbid(); - for (ol = (odfs_lock_t *)g->locklist.mlh_Head; - ol->node.mln_Succ != NULL; - ol = (odfs_lock_t *)ol->node.mln_Succ) { - if (ol->entry->volume != volume) - continue; + ol->volume_prev = NULL; + ol->volume_next = volume->lock_head; + ODFS_LOCK_DOS(ol)->fl_Link = volume->lock_head ? + LOCK_TO_BPTR(volume->lock_head) : 0; + if (volume->lock_head) + volume->lock_head->volume_prev = ol; + volume->lock_head = ol; + if (volume->volnode) + volume->volnode->dl_LockList = LOCK_TO_BPTR(ol); + Permit(); +} - if (!head) - head = LOCK_TO_BPTR(ol); - if (prev) - prev->lock.fl_Link = LOCK_TO_BPTR(ol); - prev = ol; - } +static void unlink_volume_lock(odfs_volume_t *volume, odfs_lock_t *ol) +{ + odfs_lock_t *prev; + odfs_lock_t *next; + + if (!volume || !ol) + return; - if (prev) - prev->lock.fl_Link = 0; - volume->volnode->dl_LockList = head; + Forbid(); + prev = ol->volume_prev; + next = ol->volume_next; + + if (prev) { + prev->volume_next = next; + ODFS_LOCK_DOS(prev)->fl_Link = next ? LOCK_TO_BPTR(next) : 0; + } else if (volume->lock_head == ol) { + volume->lock_head = next; + if (volume->volnode) + volume->volnode->dl_LockList = next ? LOCK_TO_BPTR(next) : 0; + } + if (next) + next->volume_prev = prev; + + ol->volume_prev = NULL; + ol->volume_next = NULL; + ODFS_LOCK_DOS(ol)->fl_Link = 0; Permit(); } @@ -1110,7 +1210,6 @@ static void release_volume_object(handler_global_t *g, odfs_volume_t *volume) if (volume == g->current_volume) return; - rebuild_volume_locklist(g, volume); if (volume->object_count == 0) destroy_stale_volume(g, volume); } @@ -1136,6 +1235,9 @@ static int nodes_same(const odfs_node_t *a, const odfs_node_t *b) a->extent.length == b->extent.length; } +static int node_is_mount_root(const handler_global_t *g, + const odfs_node_t *fnode); + static ULONG amiga_node_key(const odfs_node_t *node) { ULONG key; @@ -1157,6 +1259,126 @@ static ULONG amiga_node_key(const odfs_node_t *node) return key; } +static ULONG node_protection(const odfs_node_t *node) +{ + ULONG prot = 0; + + if (!node) + return 0; + + if (node->amiga_as.has_protection) { + prot = node->amiga_as.protection[3]; + } else if (node->mode != 0) { + /* MakeCD table 6 default mapping from PX to classic Amiga bits. */ + if ((node->mode & 0200) == 0) + prot |= FIBF_DELETE | FIBF_WRITE; + if ((node->mode & 0100) == 0) + prot |= FIBF_EXECUTE; + if ((node->mode & 0400) == 0) + prot |= FIBF_READ; +#ifdef FIBF_GRP_DELETE + if (node->mode & 0020) + prot |= FIBF_GRP_DELETE; +#endif +#ifdef FIBF_GRP_EXECUTE + if (node->mode & 0010) + prot |= FIBF_GRP_EXECUTE; +#endif +#ifdef FIBF_GRP_WRITE + if (node->mode & 0020) + prot |= FIBF_GRP_WRITE; +#endif +#ifdef FIBF_GRP_READ + if (node->mode & 0040) + prot |= FIBF_GRP_READ; +#endif +#ifdef FIBF_OTR_DELETE + if (node->mode & 0002) + prot |= FIBF_OTR_DELETE; +#endif +#ifdef FIBF_OTR_EXECUTE + if (node->mode & 0001) + prot |= FIBF_OTR_EXECUTE; +#endif +#ifdef FIBF_OTR_WRITE + if (node->mode & 0002) + prot |= FIBF_OTR_WRITE; +#endif +#ifdef FIBF_OTR_READ + if (node->mode & 0004) + prot |= FIBF_OTR_READ; +#endif + } else { + prot = FIBF_WRITE | FIBF_DELETE; + } + + return prot; +} + +static void node_date(const odfs_node_t *node, struct DateStamp *ds) +{ + if (!ds) + return; + + memset(ds, 0, sizeof(*ds)); + if (!node || node->mtime.year < 1978) + return; + + { + LONG days = 0; + int y; + + for (y = 1978; y < node->mtime.year; y++) { + days += 365; + if ((y % 4 == 0 && y % 100 != 0) || y % 400 == 0) + days++; + } + { + static const int mdays[] = {0,31,28,31,30,31,30,31,31,30,31,30,31}; + int m; + for (m = 1; m < node->mtime.month && m <= 12; m++) { + days += mdays[m]; + if (m == 2 && ((node->mtime.year % 4 == 0 && + node->mtime.year % 100 != 0) || + node->mtime.year % 400 == 0)) + days++; + } + } + days += node->mtime.day - 1; + + ds->ds_Days = days; + ds->ds_Minute = node->mtime.hour * 60 + node->mtime.minute; + ds->ds_Tick = node->mtime.second * TICKS_PER_SECOND; + } +} + +void odfs_handler_fill_node_info(handler_global_t *g, + const odfs_node_t *node, + odfs_handler_node_info_t *info) +{ + if (!info) + return; + + memset(info, 0, sizeof(*info)); + info->name = ""; + info->comment = ""; + + if (!node) + return; + + info->name = (g && node_is_mount_root(g, node)) ? g->volname : node->name; + if (node->amiga_as.has_comment) + info->comment = node->amiga_as.comment; + info->key = amiga_node_key(node); + info->protection = node_protection(node); + info->size = node->size; + info->is_dir = (node->kind == ODFS_NODE_DIR); + info->fib_type = info->is_dir ? ST_USERDIR : ST_FILE; + if (g && node_is_mount_root(g, node)) + info->fib_type = ST_ROOT; + node_date(node, &info->date); +} + static odfs_err_t lookup_child_node(handler_global_t *g, const odfs_node_t *dir, const char *name, @@ -1199,9 +1421,12 @@ static odfs_err_t read_file_node(handler_global_t *g, static void free_volume(odfs_volume_t *volume) { if (volume) - FreeMem(volume, sizeof(*volume)); + odfs_amiga_free_mem(volume, sizeof(*volume)); } +static void mount_volume(handler_global_t *g); +static void unmount_volume(handler_global_t *g); + static void drain_all_objects(handler_global_t *g) { struct Node *node; @@ -1210,14 +1435,19 @@ static void drain_all_objects(handler_global_t *g) odfs_fh_t *fh = (odfs_fh_t *)node; release_volume_object(g, fh->entry->volume); release_entry(fh->entry); - FreeMem(fh, sizeof(*fh)); + odfs_amiga_free_mem(fh, sizeof(*fh)); } while ((node = RemHead((struct List *)&g->locklist)) != NULL) { odfs_lock_t *ol = (odfs_lock_t *)node; + unlink_volume_lock(ol->entry->volume, ol); +#if ODFS_AMIGA_OS4 + if (ol->lock) + FreeDosObject(DOS_LOCK, ol->lock); +#endif release_volume_object(g, ol->entry->volume); release_entry(ol->entry); - FreeMem(ol, sizeof(*ol)); + odfs_amiga_free_mem(ol, sizeof(*ol)); } } @@ -1253,37 +1483,56 @@ static int packet_needs_live_mount(const struct DosPacket *pkt) static odfs_lock_t *alloc_lock(handler_global_t *g, const odfs_node_t *fnode, const odfs_node_t *parent, + const odfs_node_t *grandparent, LONG access) { odfs_lock_t *ol; odfs_entry_t *entry; + struct FileLock *lock; if (!g->current_volume) return NULL; - entry = alloc_entry(g->current_volume, fnode, parent); + entry = alloc_entry(g->current_volume, fnode, parent, grandparent); if (!entry) return NULL; - ol = AllocMem(sizeof(*ol), MEMF_PUBLIC | MEMF_CLEAR); + ol = odfs_amiga_alloc_mem(sizeof(*ol), MEMF_PUBLIC | MEMF_CLEAR); if (!ol) { release_entry(entry); return NULL; } +#if ODFS_AMIGA_OS4 + ol->lock = AllocDosObjectTags(DOS_LOCK, + ADO_DOSType, ODFS_OS4_CD_DOSTYPE, + TAG_DONE); + if (!ol->lock) { + odfs_amiga_free_mem(ol, sizeof(*ol)); + release_entry(entry); + return NULL; + } +#endif ol->entry = entry; ol->key = amiga_node_key(fnode); +#if !ODFS_AMIGA_OS4 ol->dos_private[0] = 0; ol->dos_private[1] = 0; +#endif - ol->lock.fl_Link = 0; - ol->lock.fl_Key = ol->key; - ol->lock.fl_Access = access; - ol->lock.fl_Task = g->dosport; - ol->lock.fl_Volume = MKBADDR(volume_node_ptr(entry->volume)); + lock = ODFS_LOCK_DOS(ol); + lock->fl_Link = 0; + lock->fl_Key = ol->key; + lock->fl_Access = access; + lock->fl_Task = g->dosport; + lock->fl_Volume = MKBADDR(volume_node_ptr(entry->volume)); +#if ODFS_AMIGA_OS4 + lock->fl_FSPrivate1 = ol; + lock->fl_FSPrivate2 = entry; +#endif retain_volume_object(entry->volume); AddTail((struct List *)&g->locklist, (struct Node *)&ol->node); - rebuild_volume_locklist(g, entry->volume); + link_volume_lock(entry->volume, ol); return ol; } @@ -1291,36 +1540,57 @@ static void free_lock(handler_global_t *g, odfs_lock_t *ol) { if (!ol) return; + unlink_volume_lock(ol->entry->volume, ol); Remove((struct Node *)&ol->node); - rebuild_volume_locklist(g, ol->entry->volume); +#if ODFS_AMIGA_OS4 + if (ol->lock) + FreeDosObject(DOS_LOCK, ol->lock); +#endif release_volume_object(g, ol->entry->volume); release_entry(ol->entry); - FreeMem(ol, sizeof(*ol)); + odfs_amiga_free_mem(ol, sizeof(*ol)); } static odfs_lock_t *dup_lock(handler_global_t *g, odfs_lock_t *src) { odfs_lock_t *ol; + struct FileLock *lock; if (!src) return NULL; - ol = AllocMem(sizeof(*ol), MEMF_PUBLIC | MEMF_CLEAR); + ol = odfs_amiga_alloc_mem(sizeof(*ol), MEMF_PUBLIC | MEMF_CLEAR); if (!ol) return NULL; +#if ODFS_AMIGA_OS4 + ol->lock = AllocDosObjectTags(DOS_LOCK, + ADO_DOSType, ODFS_OS4_CD_DOSTYPE, + TAG_DONE); + if (!ol->lock) { + odfs_amiga_free_mem(ol, sizeof(*ol)); + return NULL; + } +#endif ol->entry = retain_entry(src->entry); ol->key = src->key; +#if !ODFS_AMIGA_OS4 ol->dos_private[0] = 0; ol->dos_private[1] = 0; - ol->lock.fl_Link = 0; - ol->lock.fl_Key = ol->key; - ol->lock.fl_Access = src->lock.fl_Access; - ol->lock.fl_Task = g->dosport; - ol->lock.fl_Volume = MKBADDR(volume_node_ptr(ol->entry->volume)); +#endif + lock = ODFS_LOCK_DOS(ol); + lock->fl_Link = 0; + lock->fl_Key = ol->key; + lock->fl_Access = ODFS_LOCK_DOS(src)->fl_Access; + lock->fl_Task = g->dosport; + lock->fl_Volume = MKBADDR(volume_node_ptr(ol->entry->volume)); +#if ODFS_AMIGA_OS4 + lock->fl_FSPrivate1 = ol; + lock->fl_FSPrivate2 = ol->entry; +#endif retain_volume_object(ol->entry->volume); AddTail((struct List *)&g->locklist, (struct Node *)&ol->node); - rebuild_volume_locklist(g, ol->entry->volume); + link_volume_lock(ol->entry->volume, ol); return ol; } @@ -1335,7 +1605,7 @@ static odfs_fh_t *alloc_fh(handler_global_t *g, odfs_entry_t *entry, LONG access if (!entry) return NULL; - fh = AllocMem(sizeof(*fh), MEMF_PUBLIC | MEMF_CLEAR); + fh = odfs_amiga_alloc_mem(sizeof(*fh), MEMF_PUBLIC | MEMF_CLEAR); if (!fh) return NULL; @@ -1354,7 +1624,7 @@ static void free_fh(handler_global_t *g, odfs_fh_t *fh) Remove((struct Node *)&fh->node); release_volume_object(g, fh->entry->volume); release_entry(fh->entry); - FreeMem(fh, sizeof(*fh)); + odfs_amiga_free_mem(fh, sizeof(*fh)); } /* ------------------------------------------------------------------ */ @@ -1370,22 +1640,41 @@ static void free_fh(handler_global_t *g, odfs_fh_t *fh) * "//foo" = go to parent, then descend into foo * "" = current node * - * Tracks the current node and its immediate parent. When an ascent needs the - * next ancestor, reconstruct it with an iterative directory walk. + * Tracks the current node, its immediate parent, and a cached parent ancestor + * when available. When an ascent needs an unknown ancestor, reconstruct it with + * an iterative directory walk. */ static odfs_err_t resolve_amiga_path(handler_global_t *g, const odfs_node_t *start, const odfs_node_t *start_parent, + const odfs_node_t *start_grandparent, const char *path, odfs_node_t *result, - odfs_node_t *parent_out) + odfs_node_t *parent_out, + odfs_node_t *grandparent_out, + int *has_grandparent_out) { odfs_node_t cur = *start; odfs_node_t parent = start_parent ? *start_parent : *start; + odfs_node_t grandparent = + start_grandparent ? *start_grandparent : parent; + int has_grandparent = start_grandparent != NULL; const char *p = path; char comp[256]; odfs_err_t err; + if (!start_parent || node_is_mount_root(g, start)) { + parent = g->mount.root; + grandparent = g->mount.root; + has_grandparent = 1; +#if ODFS_FEATURE_CDDA + } else if (g->has_cdda && nodes_same(start, &g->cdda_root)) { + parent = g->mount.root; + grandparent = g->mount.root; + has_grandparent = 1; +#endif + } + /* Handle colons in the path (e.g., "CD0:foo" or "LIBS:foo"). * * DOS resolves device/assign prefixes before the packet reaches the @@ -1404,15 +1693,24 @@ static odfs_err_t resolve_amiga_path(handler_global_t *g, cur = parent; if (node_is_mount_root(g, &cur)) { parent = g->mount.root; + grandparent = g->mount.root; + has_grandparent = 1; #if ODFS_FEATURE_CDDA } else if (g->has_cdda && nodes_same(&cur, &g->cdda_root)) { parent = g->mount.root; + grandparent = g->mount.root; + has_grandparent = 1; #endif + } else if (has_grandparent) { + parent = grandparent; + grandparent = parent; + has_grandparent = 0; } else { err = odfs_resolve_parent_node(&g->mount, &cur, - &parent, NULL); + &parent, &grandparent); if (err != ODFS_OK) return err; + has_grandparent = 1; } } p++; @@ -1438,6 +1736,8 @@ static odfs_err_t resolve_amiga_path(handler_global_t *g, /* intercept "CDDA" virtual directory on mixed-mode discs */ if (g->has_cdda && cur.extent.lba == g->mount.root.extent.lba && odfs_strcasecmp(comp, "CDDA") == 0) { + grandparent = parent; + has_grandparent = 1; parent = cur; cur = g->cdda_root; p = end; @@ -1445,6 +1745,8 @@ static odfs_err_t resolve_amiga_path(handler_global_t *g, } #endif + grandparent = parent; + has_grandparent = 1; parent = cur; err = lookup_child_node(g, &cur, comp, &cur); if (err != ODFS_OK) @@ -1457,6 +1759,10 @@ static odfs_err_t resolve_amiga_path(handler_global_t *g, *result = cur; *parent_out = parent; + if (grandparent_out) + *grandparent_out = grandparent; + if (has_grandparent_out) + *has_grandparent_out = has_grandparent; return ODFS_OK; } @@ -1464,112 +1770,41 @@ static odfs_err_t resolve_amiga_path(handler_global_t *g, /* fill FileInfoBlock from odfs_node_t */ /* ------------------------------------------------------------------ */ -static void fill_fib(struct FileInfoBlock *fib, const odfs_node_t *fnode) +static void fill_fib(handler_global_t *g, struct FileInfoBlock *fib, + const odfs_node_t *fnode) { - ULONG prot = 0; - int comment_len = 0; + odfs_handler_node_info_t info; + int name_len; + int comment_len; + int max_name_len; memset(fib, 0, sizeof(*fib)); - - /* filename — BCPL string (length prefix) */ - { - int len = strlen(fnode->name); - if (len > 106) - len = 106; - fib->fib_FileName[0] = len; - memcpy(&fib->fib_FileName[1], fnode->name, len); - } - - fib->fib_DirEntryType = (fnode->kind == ODFS_NODE_DIR) ? ST_USERDIR : ST_FILE; - fib->fib_EntryType = fib->fib_DirEntryType; - fib->fib_Size = (LONG)fnode->size; - fib->fib_NumBlocks = (fnode->size + 511) / 512; - - if (fnode->amiga_as.has_protection) { - prot = fnode->amiga_as.protection[3]; - } else if (fnode->mode != 0) { - /* MakeCD table 6 default mapping from PX to classic Amiga bits. */ - if ((fnode->mode & 0200) == 0) - prot |= FIBF_DELETE | FIBF_WRITE; - if ((fnode->mode & 0100) == 0) - prot |= FIBF_EXECUTE; - if ((fnode->mode & 0400) == 0) - prot |= FIBF_READ; -#ifdef FIBF_GRP_DELETE - if (fnode->mode & 0020) - prot |= FIBF_GRP_DELETE; -#endif -#ifdef FIBF_GRP_EXECUTE - if (fnode->mode & 0010) - prot |= FIBF_GRP_EXECUTE; -#endif -#ifdef FIBF_GRP_WRITE - if (fnode->mode & 0020) - prot |= FIBF_GRP_WRITE; -#endif -#ifdef FIBF_GRP_READ - if (fnode->mode & 0040) - prot |= FIBF_GRP_READ; -#endif -#ifdef FIBF_OTR_DELETE - if (fnode->mode & 0002) - prot |= FIBF_OTR_DELETE; -#endif -#ifdef FIBF_OTR_EXECUTE - if (fnode->mode & 0001) - prot |= FIBF_OTR_EXECUTE; -#endif -#ifdef FIBF_OTR_WRITE - if (fnode->mode & 0002) - prot |= FIBF_OTR_WRITE; + odfs_handler_fill_node_info(g, fnode, &info); + + max_name_len = (info.fib_type == ST_ROOT) ? 30 : 106; + name_len = strlen(info.name); + if (name_len > max_name_len) + name_len = max_name_len; + fib->fib_FileName[0] = name_len; + memcpy(&fib->fib_FileName[1], info.name, name_len); + + fib->fib_DirEntryType = info.fib_type; +#if !ODFS_AMIGA_OS4 + fib->fib_EntryType = fib->fib_DirEntryType; #endif -#ifdef FIBF_OTR_READ - if (fnode->mode & 0004) - prot |= FIBF_OTR_READ; -#endif - } else { - /* Read-only fallback when there is no RR/AS metadata at all. */ - prot = FIBF_WRITE | FIBF_DELETE; - } - fib->fib_Protection = prot; - - /* date stamp — Amiga epoch is 1978-01-01 */ - if (fnode->mtime.year >= 1978) { - LONG days = 0; - int y; - for (y = 1978; y < fnode->mtime.year; y++) { - days += 365; - if ((y % 4 == 0 && y % 100 != 0) || y % 400 == 0) - days++; - } - { - static const int mdays[] = {0,31,28,31,30,31,30,31,31,30,31,30,31}; - int m; - for (m = 1; m < fnode->mtime.month && m <= 12; m++) { - days += mdays[m]; - if (m == 2 && ((fnode->mtime.year % 4 == 0 && - fnode->mtime.year % 100 != 0) || - fnode->mtime.year % 400 == 0)) - days++; - } - } - days += fnode->mtime.day - 1; - - fib->fib_Date.ds_Days = days; - fib->fib_Date.ds_Minute = fnode->mtime.hour * 60 + fnode->mtime.minute; - fib->fib_Date.ds_Tick = fnode->mtime.second * TICKS_PER_SECOND; - } - - if (fnode->amiga_as.has_comment) { - comment_len = strlen(fnode->amiga_as.comment); - if (comment_len > (int)sizeof(fib->fib_Comment) - 1) - comment_len = (int)sizeof(fib->fib_Comment) - 1; - fib->fib_Comment[0] = comment_len; - if (comment_len > 0) - memcpy(&fib->fib_Comment[1], fnode->amiga_as.comment, comment_len); - } - - fib->fib_DiskKey = (LONG)amiga_node_key(fnode); + fib->fib_Size = (LONG)info.size; + fib->fib_NumBlocks = (info.size + 511) / 512; + fib->fib_Protection = info.protection; + fib->fib_Date = info.date; + + comment_len = strlen(info.comment); + if (comment_len > (int)sizeof(fib->fib_Comment) - 1) + comment_len = (int)sizeof(fib->fib_Comment) - 1; + fib->fib_Comment[0] = comment_len; + if (comment_len > 0) + memcpy(&fib->fib_Comment[1], info.comment, comment_len); + + fib->fib_DiskKey = (LONG)info.key; } static int node_is_mount_root(const handler_global_t *g, const odfs_node_t *fnode) @@ -1580,25 +1815,740 @@ static int node_is_mount_root(const handler_global_t *g, const odfs_node_t *fnod return odfs_node_matches_identity(fnode, &g->mount.root); } -static void fill_root_fib(handler_global_t *g, struct FileInfoBlock *fib, - const odfs_node_t *fnode) +static LONG resolve_object_nodes(handler_global_t *g, + odfs_lock_t *parent_lock, + const char *path, + odfs_node_t *node_out, + odfs_node_t *parent_out, + odfs_node_t *grandparent_out, + int *has_grandparent_out) { - int len; + odfs_err_t err; + const odfs_node_t *start; + const odfs_node_t *start_parent; + const odfs_node_t *start_grandparent; - fill_fib(fib, fnode); + if (has_grandparent_out) + *has_grandparent_out = 0; - fib->fib_DirEntryType = ST_ROOT; - fib->fib_EntryType = ST_ROOT; + if (!g || !path || !node_out || !parent_out) + return ERROR_REQUIRED_ARG_MISSING; - len = strlen(g->volname); - if (len > 30) - len = 30; - fib->fib_FileName[0] = len; - memcpy(&fib->fib_FileName[1], g->volname, len); -} + if (parent_lock) { + LONG err_dos; -/* ------------------------------------------------------------------ */ -/* packet handlers */ + if (!lock_is_active(g, parent_lock)) + return ERROR_INVALID_LOCK; + + err_dos = validate_object_volume(g, parent_lock->entry->volume); + if (err_dos != 0) + return err_dos; + start = lock_node(parent_lock); + start_parent = lock_parent_node(parent_lock); + start_grandparent = lock_grandparent_node(parent_lock); + } else { + if (!g->mounted) + return ERROR_NO_DISK; + start = &g->mount.root; + start_parent = &g->mount.root; + start_grandparent = &g->mount.root; + } + + err = resolve_amiga_path(g, start, start_parent, start_grandparent, + path, node_out, parent_out, grandparent_out, + has_grandparent_out); + if (err != ODFS_OK) + return odfs_err_to_dos(err); + + return 0; +} + +#if ODFS_AMIGA_OS4 +LONG odfs_handler_resolve_object_node(handler_global_t *g, + odfs_lock_t *parent_lock, + const char *path, + odfs_node_t *node_out, + odfs_node_t *parent_out) +{ + return resolve_object_nodes(g, parent_lock, path, node_out, parent_out, + NULL, NULL); +} +#endif + +/* ------------------------------------------------------------------ */ +/* shared frontend operations */ +/* ------------------------------------------------------------------ */ + +LONG odfs_handler_lock_object(handler_global_t *g, + odfs_lock_t *parent_lock, + const char *path, + LONG access, + odfs_lock_t **out) +{ + odfs_node_t result, parent_node, grandparent_node; + int has_grandparent; + LONG err_dos; + odfs_lock_t *ol; + + if (out) + *out = NULL; + if (!g || !path || !out) + return ERROR_REQUIRED_ARG_MISSING; + + err_dos = resolve_object_nodes(g, parent_lock, path, &result, + &parent_node, &grandparent_node, + &has_grandparent); + if (err_dos != 0) + return err_dos; + + if (result.kind == ODFS_NODE_DIR) + access = SHARED_LOCK; + + ol = alloc_lock(g, &result, &parent_node, + has_grandparent ? &grandparent_node : NULL, access); + if (!ol) + return ERROR_NO_FREE_STORE; + + *out = ol; + return 0; +} + +LONG odfs_handler_free_lock_object(handler_global_t *g, odfs_lock_t *ol) +{ + if (!ol) + return 0; + if (!lock_is_active(g, ol)) + return ERROR_INVALID_LOCK; + + free_lock(g, ol); + return 0; +} + +LONG odfs_handler_dup_lock_object(handler_global_t *g, + odfs_lock_t *src, + odfs_lock_t **out) +{ + odfs_lock_t *ol; + + if (out) + *out = NULL; + if (!g || !out) + return ERROR_REQUIRED_ARG_MISSING; + + if (!src) { + if (!g->mounted) + return ERROR_NO_DISK; + ol = alloc_lock(g, &g->mount.root, &g->mount.root, &g->mount.root, + SHARED_LOCK); + } else { + LONG err_dos; + + if (!lock_is_active(g, src)) + return ERROR_INVALID_LOCK; + + err_dos = validate_object_volume(g, src->entry->volume); + if (err_dos != 0) + return err_dos; + ol = dup_lock(g, src); + } + + if (!ol) + return ERROR_NO_FREE_STORE; + + *out = ol; + return 0; +} + +LONG odfs_handler_dup_lock_from_fh(handler_global_t *g, + odfs_fh_t *fh, + odfs_lock_t **out) +{ + odfs_lock_t *ol; + + if (out) + *out = NULL; + if (!g || !out) + return ERROR_REQUIRED_ARG_MISSING; + + if (!fh) { + if (!g->mounted) + return ERROR_NO_DISK; + ol = alloc_lock(g, &g->mount.root, &g->mount.root, &g->mount.root, + SHARED_LOCK); + } else { + LONG err_dos; + + if (!fh_is_active(g, fh)) + return ERROR_OBJECT_NOT_FOUND; + + err_dos = validate_object_volume(g, fh_volume(fh)); + if (err_dos != 0) + return err_dos; + ol = alloc_lock(g, fh_node(fh), fh_parent_node(fh), + fh_grandparent_node(fh), SHARED_LOCK); + } + + if (!ol) + return ERROR_NO_FREE_STORE; + + *out = ol; + return 0; +} + +static LONG resolve_parent_with_cache(handler_global_t *g, + const odfs_node_t *parent_node, + const odfs_node_t *cached_parent, + odfs_node_t *new_parent, + odfs_node_t *new_grandparent, + int *has_new_grandparent) +{ + odfs_err_t err; + + if (has_new_grandparent) + *has_new_grandparent = 0; + + if (node_is_mount_root(g, parent_node)) { + *new_parent = g->mount.root; + *new_grandparent = g->mount.root; + if (has_new_grandparent) + *has_new_grandparent = 1; + return 0; + } + +#if ODFS_FEATURE_CDDA + if (g->has_cdda && nodes_same(parent_node, &g->cdda_root)) { + *new_parent = g->mount.root; + *new_grandparent = g->mount.root; + if (has_new_grandparent) + *has_new_grandparent = 1; + return 0; + } +#endif + + if (cached_parent) { + *new_parent = *cached_parent; + if (node_is_mount_root(g, new_parent)) { + *new_grandparent = g->mount.root; + if (has_new_grandparent) + *has_new_grandparent = 1; + } else { + *new_grandparent = *new_parent; + } + return 0; + } + + err = odfs_resolve_parent_node(&g->mount, parent_node, new_parent, + new_grandparent); + if (err != ODFS_OK) + return odfs_err_to_dos(err); + if (has_new_grandparent) + *has_new_grandparent = 1; + return 0; +} + +static LONG parent_entry_object(handler_global_t *g, odfs_entry_t *entry, + odfs_lock_t **out) +{ + const odfs_node_t *parent_node; + const odfs_node_t *grandparent_node; + odfs_node_t new_parent; + odfs_node_t new_grandparent; + int has_new_grandparent; + LONG err_dos; + odfs_lock_t *parent; + + if (node_is_mount_root(g, &entry->fnode)) + return 0; + + parent_node = &entry->parent_node; + grandparent_node = + entry->has_grandparent ? &entry->grandparent_node : NULL; + err_dos = resolve_parent_with_cache(g, parent_node, grandparent_node, + &new_parent, &new_grandparent, + &has_new_grandparent); + if (err_dos != 0) + return err_dos; + + parent = alloc_lock(g, parent_node, &new_parent, + has_new_grandparent ? &new_grandparent : NULL, + SHARED_LOCK); + if (!parent) + return ERROR_NO_FREE_STORE; + + *out = parent; + return 0; +} + +LONG odfs_handler_parent_lock_object(handler_global_t *g, + odfs_lock_t *ol, + odfs_lock_t **out) +{ + if (out) + *out = NULL; + if (!g || !out) + return ERROR_REQUIRED_ARG_MISSING; + + if (!ol) { + if (!g->mounted) + return ERROR_NO_DISK; + return 0; + } + + if (!lock_is_active(g, ol)) + return ERROR_INVALID_LOCK; + + { + LONG err_dos = validate_object_volume(g, ol->entry->volume); + if (err_dos != 0) + return err_dos; + } + + return parent_entry_object(g, ol->entry, out); +} + +LONG odfs_handler_parent_fh_object(handler_global_t *g, + odfs_fh_t *fh, + odfs_lock_t **out) +{ + if (out) + *out = NULL; + if (!g || !out) + return ERROR_REQUIRED_ARG_MISSING; + + if (!fh) { + if (!g->mounted) + return ERROR_NO_DISK; + return 0; + } + + if (!fh_is_active(g, fh)) + return ERROR_OBJECT_NOT_FOUND; + + { + LONG err_dos = validate_object_volume(g, fh_volume(fh)); + if (err_dos != 0) + return err_dos; + } + + return parent_entry_object(g, fh->entry, out); +} + +LONG odfs_handler_same_lock_object(handler_global_t *g, + odfs_lock_t *l1, + odfs_lock_t *l2, + LONG *same_result) +{ + odfs_volume_t *v1; + odfs_volume_t *v2; + int same = 0; + + if (!g || !same_result) + return ERROR_REQUIRED_ARG_MISSING; + + *same_result = LOCK_DIFFERENT; + + if (l1 && !lock_is_active(g, l1)) + return ERROR_INVALID_LOCK; + if (l2 && !lock_is_active(g, l2)) + return ERROR_INVALID_LOCK; + + if (!g->mounted && (!l1 || !l2)) + return ERROR_DEVICE_NOT_MOUNTED; + + v1 = l1 ? l1->entry->volume : g->current_volume; + v2 = l2 ? l2->entry->volume : g->current_volume; + if (v1 != v2) + return 0; + + *same_result = LOCK_SAME_VOLUME; + if (!l1 && !l2) { + same = 1; + } else if (!l1) { + same = node_is_mount_root(g, lock_node(l2)); + } else if (!l2) { + same = node_is_mount_root(g, lock_node(l1)); + } else { + same = nodes_same(lock_node(l1), lock_node(l2)); + } + + if (same) + *same_result = LOCK_SAME; + return 0; +} + +LONG odfs_handler_same_file_object(handler_global_t *g, + odfs_fh_t *fh1, + odfs_fh_t *fh2, + LONG *same_result) +{ + odfs_volume_t *v1; + odfs_volume_t *v2; + int same; + + if (!g || !fh1 || !fh2 || !same_result) + return ERROR_OBJECT_NOT_FOUND; + + *same_result = LOCK_DIFFERENT; + + if (!fh_is_active(g, fh1) || !fh_is_active(g, fh2)) + return ERROR_OBJECT_NOT_FOUND; + + v1 = fh_volume(fh1); + v2 = fh_volume(fh2); + if (v1 != v2) + return 0; + + *same_result = LOCK_SAME_VOLUME; + same = nodes_same(fh_node(fh1), fh_node(fh2)); + if (same) + *same_result = LOCK_SAME; + return 0; +} + +LONG odfs_handler_open_object(handler_global_t *g, + odfs_lock_t *dirlock, + const char *path, + LONG mode, + odfs_fh_t **out) +{ + odfs_node_t result, parent_node, grandparent_node; + int has_grandparent; + LONG err_dos; + odfs_entry_t *entry; + odfs_fh_t *fh; + + if (out) + *out = NULL; + if (!g || !path || !out) + return ERROR_REQUIRED_ARG_MISSING; + + if (mode != MODE_OLDFILE) + return ERROR_DISK_WRITE_PROTECTED; + + err_dos = resolve_object_nodes(g, dirlock, path, &result, &parent_node, + &grandparent_node, &has_grandparent); + if (err_dos != 0) + return err_dos; + + if (result.kind == ODFS_NODE_DIR) + return ERROR_OBJECT_WRONG_TYPE; + + entry = alloc_entry(g->current_volume, &result, &parent_node, + has_grandparent ? &grandparent_node : NULL); + if (!entry) + return ERROR_NO_FREE_STORE; + + fh = alloc_fh(g, entry, SHARED_LOCK); + release_entry(entry); + if (!fh) + return ERROR_NO_FREE_STORE; + + *out = fh; + return 0; +} + +LONG odfs_handler_open_from_lock_object(handler_global_t *g, + odfs_lock_t *ol, + odfs_fh_t **out) +{ + odfs_fh_t *fh; + + if (out) + *out = NULL; + if (!g || !ol || !out) + return ERROR_OBJECT_NOT_FOUND; + + if (!lock_is_active(g, ol)) + return ERROR_INVALID_LOCK; + + { + LONG err_dos = validate_object_volume(g, ol->entry->volume); + if (err_dos != 0) + return err_dos; + } + + fh = alloc_fh(g, ol->entry, ODFS_LOCK_DOS(ol)->fl_Access); + if (!fh) + return ERROR_NO_FREE_STORE; + + free_lock(g, ol); + *out = fh; + return 0; +} + +LONG odfs_handler_close_object(handler_global_t *g, odfs_fh_t *fh) +{ + if (!fh) + return 0; + if (!fh_is_active(g, fh)) + return ERROR_OBJECT_NOT_FOUND; + + free_fh(g, fh); + return 0; +} + +LONG odfs_handler_read_object(handler_global_t *g, + odfs_fh_t *fh, + void *buf, + LONG len, + LONG *actual_out) +{ + size_t actual; + odfs_err_t err; + + if (actual_out) + *actual_out = 0; + if (!g || !fh || !buf || !actual_out) + return ERROR_OBJECT_NOT_FOUND; + if (len < 0) + return ERROR_BAD_NUMBER; + if (!fh_is_active(g, fh)) + return ERROR_OBJECT_NOT_FOUND; + + { + LONG err_dos = validate_object_volume(g, fh_volume(fh)); + if (err_dos != 0) + return err_dos; + } + + actual = (size_t)len; + err = read_file_node(g, fh_node(fh), fh->pos, buf, &actual); + if (err != ODFS_OK && actual == 0) + return odfs_err_to_dos(err); + + fh->pos += actual; + *actual_out = (LONG)actual; + return 0; +} + +LONG odfs_handler_seek_object(handler_global_t *g, + odfs_fh_t *fh, + int64_t offset, + LONG mode, + int64_t *oldpos_out) +{ + int64_t oldpos; + int64_t newpos; + uint64_t size; + + if (oldpos_out) + *oldpos_out = -1; + if (!g || !fh || !oldpos_out) + return ERROR_OBJECT_NOT_FOUND; + if (!fh_is_active(g, fh)) + return ERROR_OBJECT_NOT_FOUND; + + { + LONG err_dos = validate_object_volume(g, fh_volume(fh)); + if (err_dos != 0) + return err_dos; + } + + oldpos = (int64_t)fh->pos; + size = fh_node(fh)->size; + + switch (mode) { + case OFFSET_BEGINNING: newpos = offset; break; + case OFFSET_CURRENT: newpos = oldpos + offset; break; + case OFFSET_END: newpos = (int64_t)size + offset; break; + default: + return ERROR_SEEK_ERROR; + } + + if (newpos < 0 || (uint64_t)newpos > size) + return ERROR_SEEK_ERROR; + + fh->pos = (uint64_t)newpos; + *oldpos_out = oldpos; + return 0; +} + +LONG odfs_handler_get_file_position(handler_global_t *g, + odfs_fh_t *fh, + int64_t *pos_out) +{ + if (pos_out) + *pos_out = -1; + if (!g || !fh || !pos_out) + return ERROR_OBJECT_NOT_FOUND; + if (!fh_is_active(g, fh)) + return ERROR_OBJECT_NOT_FOUND; + + { + LONG err_dos = validate_object_volume(g, fh_volume(fh)); + if (err_dos != 0) + return err_dos; + } + + *pos_out = (int64_t)fh->pos; + return 0; +} + +LONG odfs_handler_change_lock_mode(handler_global_t *g, + odfs_lock_t *ol, + LONG mode) +{ + if (!g || !ol) + return ERROR_INVALID_LOCK; + if (mode != SHARED_LOCK && mode != EXCLUSIVE_LOCK) + return ERROR_BAD_NUMBER; + if (!lock_is_active(g, ol)) + return ERROR_INVALID_LOCK; + + { + LONG err_dos = validate_object_volume(g, ol->entry->volume); + if (err_dos != 0) + return err_dos; + } + + ODFS_LOCK_DOS(ol)->fl_Access = + (lock_node(ol)->kind == ODFS_NODE_DIR) ? SHARED_LOCK : mode; + return 0; +} + +LONG odfs_handler_change_file_mode(handler_global_t *g, + odfs_fh_t *fh, + LONG mode) +{ + if (!g || !fh) + return ERROR_OBJECT_NOT_FOUND; + if (mode != SHARED_LOCK && mode != EXCLUSIVE_LOCK) + return ERROR_BAD_NUMBER; + if (!fh_is_active(g, fh)) + return ERROR_OBJECT_NOT_FOUND; + + { + LONG err_dos = validate_object_volume(g, fh_volume(fh)); + if (err_dos != 0) + return err_dos; + } + + fh->access = mode; + return 0; +} + +LONG odfs_handler_get_file_size(handler_global_t *g, + odfs_fh_t *fh, + int64_t *size_out) +{ + if (size_out) + *size_out = -1; + if (!g || !fh || !size_out) + return ERROR_OBJECT_NOT_FOUND; + if (!fh_is_active(g, fh)) + return ERROR_OBJECT_NOT_FOUND; + + { + LONG err_dos = validate_object_volume(g, fh_volume(fh)); + if (err_dos != 0) + return err_dos; + } + + *size_out = (int64_t)fh_node(fh)->size; + return 0; +} + +LONG odfs_handler_fill_info(handler_global_t *g, + odfs_lock_t *ol, + struct InfoData *info) +{ + if (!g || !info) + return ERROR_REQUIRED_ARG_MISSING; + + if (ol) { + LONG err_dos; + + if (!lock_is_active(g, ol)) + return ERROR_INVALID_LOCK; + + err_dos = validate_object_volume(g, ol->entry->volume); + if (err_dos != 0) + return err_dos; + } + + memset(info, 0, sizeof(*info)); + info->id_NumSoftErrors = 0; + info->id_UnitNumber = g->devunit; + info->id_DiskState = ID_WRITE_PROTECTED; + info->id_NumBlocks = g->mounted ? g->mount.total_blocks : 0; + info->id_NumBlocksUsed = info->id_NumBlocks; + info->id_BytesPerBlock = g->sector_size; + info->id_DiskType = g->mounted ? ID_DOS_DISK : ID_NO_DISK_PRESENT; + info->id_VolumeNode = MKBADDR(volume_node_ptr(g->current_volume)); + info->id_InUse = (g->current_volume && g->current_volume->volnode && + g->current_volume->volnode->dl_LockList) ? + DOSTRUE : DOSFALSE; + return 0; +} + +LONG odfs_handler_get_lock_node(handler_global_t *g, + odfs_lock_t *ol, + const odfs_node_t **node_out) +{ + if (node_out) + *node_out = NULL; + if (!g || !node_out) + return ERROR_REQUIRED_ARG_MISSING; + + if (ol) { + LONG err_dos; + + if (!lock_is_active(g, ol)) + return ERROR_INVALID_LOCK; + + err_dos = validate_object_volume(g, ol->entry->volume); + if (err_dos != 0) + return err_dos; + *node_out = lock_node(ol); + } else { + if (!g->mounted) + return ERROR_NO_DISK; + *node_out = &g->mount.root; + } + + return 0; +} + +LONG odfs_handler_get_fh_node(handler_global_t *g, + odfs_fh_t *fh, + const odfs_node_t **node_out) +{ + if (node_out) + *node_out = NULL; + if (!g || !fh || !node_out) + return ERROR_OBJECT_NOT_FOUND; + + if (!fh_is_active(g, fh)) + return ERROR_OBJECT_NOT_FOUND; + + { + LONG err_dos = validate_object_volume(g, fh_volume(fh)); + if (err_dos != 0) + return err_dos; + } + + *node_out = fh_node(fh); + return 0; +} + +LONG odfs_handler_inhibit(handler_global_t *g, LONG state) +{ + if (!g) + return ERROR_REQUIRED_ARG_MISSING; + + if (state != DOSFALSE) { + g->inhibited = 1; + unmount_volume(g); + } else { + g->inhibited = 0; + mount_volume(g); + } + + return 0; +} + +/* ------------------------------------------------------------------ */ +/* packet handlers */ /* ------------------------------------------------------------------ */ static void action_locate_object(handler_global_t *g, struct DosPacket *pkt) @@ -1606,10 +2556,8 @@ static void action_locate_object(handler_global_t *g, struct DosPacket *pkt) odfs_lock_t *parent_lock = LOCK_FROM_BPTR(pkt->dp_Arg1); LONG access = pkt->dp_Arg3; char path[512]; - odfs_node_t result, parent_node; - odfs_err_t err; - const odfs_node_t *start; - const odfs_node_t *start_parent; + odfs_lock_t *ol; + LONG err_dos; #if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE trace_pkt(g, "locate-enter", pkt); @@ -1619,30 +2567,10 @@ static void action_locate_object(handler_global_t *g, struct DosPacket *pkt) ODFS_TRACE(&g->log, ODFS_SUB_DOS, "locate-path path=%s", path); #endif - if (parent_lock) { - LONG err_dos = validate_object_volume(g, parent_lock->entry->volume); - if (err_dos != 0) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = err_dos; - return; - } - start = lock_node(parent_lock); - start_parent = lock_parent_node(parent_lock); - } else { - if (!g->mounted) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_NO_DISK; - return; - } - start = &g->mount.root; - start_parent = &g->mount.root; - } - - err = resolve_amiga_path(g, start, start_parent, path, &result, - &parent_node); - if (err != ODFS_OK) { + err_dos = odfs_handler_lock_object(g, parent_lock, path, access, &ol); + if (err_dos != 0) { pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = odfs_err_to_dos(err); + pkt->dp_Res2 = err_dos; #if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE trace_pkt(g, "locate-resolve-fail", pkt); #endif @@ -1650,19 +2578,9 @@ static void action_locate_object(handler_global_t *g, struct DosPacket *pkt) } #if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE - trace_node(g, "locate-node", &result); - trace_node(g, "locate-parent", &parent_node); -#endif - - odfs_lock_t *ol = alloc_lock(g, &result, &parent_node, access); - if (!ol) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_NO_FREE_STORE; -#if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE - trace_pkt(g, "locate-alloc-fail", pkt); + trace_node(g, "locate-node", lock_node(ol)); + trace_node(g, "locate-parent", lock_parent_node(ol)); #endif - return; - } pkt->dp_Res1 = LOCK_TO_BPTR(ol); #if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE @@ -1673,7 +2591,13 @@ static void action_locate_object(handler_global_t *g, struct DosPacket *pkt) static void action_free_lock(handler_global_t *g, struct DosPacket *pkt) { odfs_lock_t *ol = LOCK_FROM_BPTR(pkt->dp_Arg1); - free_lock(g, ol); + LONG err_dos = odfs_handler_free_lock_object(g, ol); + + if (err_dos != 0) { + pkt->dp_Res1 = DOSFALSE; + pkt->dp_Res2 = err_dos; + return; + } pkt->dp_Res1 = DOSTRUE; } @@ -1681,27 +2605,12 @@ static void action_copy_dir(handler_global_t *g, struct DosPacket *pkt) { odfs_lock_t *src = LOCK_FROM_BPTR(pkt->dp_Arg1); odfs_lock_t *ol; + LONG err_dos; - if (!src) { - if (!g->mounted) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_NO_DISK; - return; - } - ol = alloc_lock(g, &g->mount.root, &g->mount.root, SHARED_LOCK); - } else { - LONG err_dos = validate_object_volume(g, src->entry->volume); - if (err_dos != 0) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = err_dos; - return; - } - ol = dup_lock(g, src); - } - - if (!ol) { + err_dos = odfs_handler_dup_lock_object(g, src, &ol); + if (err_dos != 0) { pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_NO_FREE_STORE; + pkt->dp_Res2 = err_dos; return; } pkt->dp_Res1 = LOCK_TO_BPTR(ol); @@ -1711,33 +2620,12 @@ static void action_copy_dir_fh(handler_global_t *g, struct DosPacket *pkt) { odfs_fh_t *fh = (odfs_fh_t *)pkt->dp_Arg1; odfs_lock_t *ol; + LONG err_dos; - if (!fh) { - if (!g->mounted) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_NO_DISK; - return; - } - ol = alloc_lock(g, &g->mount.root, &g->mount.root, SHARED_LOCK); - } else { - if (!fh_is_active(g, fh)) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_OBJECT_NOT_FOUND; - return; - } - LONG err_dos = validate_object_volume(g, fh_volume(fh)); - if (err_dos != 0) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = err_dos; - return; - } - /* DupLockFromFH duplicates the file's lock, not just directory FHs. */ - ol = alloc_lock(g, fh_node(fh), fh_parent_node(fh), SHARED_LOCK); - } - - if (!ol) { + err_dos = odfs_handler_dup_lock_from_fh(g, fh, &ol); + if (err_dos != 0) { pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_NO_FREE_STORE; + pkt->dp_Res2 = err_dos; return; } pkt->dp_Res1 = LOCK_TO_BPTR(ol); @@ -1746,10 +2634,8 @@ static void action_copy_dir_fh(handler_global_t *g, struct DosPacket *pkt) static void action_parent(handler_global_t *g, struct DosPacket *pkt) { odfs_lock_t *ol = LOCK_FROM_BPTR(pkt->dp_Arg1); - const odfs_node_t *parent_node; - odfs_node_t new_parent; - odfs_err_t err; odfs_lock_t *parent; + LONG err_dos; #if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE ODFS_TRACE(&g->log, ODFS_SUB_DOS, @@ -1757,147 +2643,50 @@ static void action_parent(handler_global_t *g, struct DosPacket *pkt) (unsigned long)pkt->dp_Arg1, (unsigned long)ol); #endif - if (!ol) { - if (!g->mounted) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_NO_DISK; - return; - } - /* NULL lock = root — root has no parent */ - pkt->dp_Res1 = 0; - return; - } - - if (!lock_is_active(g, ol)) { -#if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE - ODFS_TRACE(&g->log, ODFS_SUB_DOS, - "parent-invalid-lock lock=%08lx", - (unsigned long)ol); -#endif + err_dos = odfs_handler_parent_lock_object(g, ol, &parent); + if (err_dos != 0) { pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_INVALID_LOCK; - return; - } - - { - LONG err_dos = validate_object_volume(g, ol->entry->volume); - if (err_dos != 0) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = err_dos; - return; - } - } - - /* already at root? */ - if (node_is_mount_root(g, lock_node(ol))) { - pkt->dp_Res1 = 0; + pkt->dp_Res2 = err_dos; return; } - /* Reconstruct the returned lock's immediate parent exactly. */ - parent_node = lock_parent_node(ol); - if (node_is_mount_root(g, parent_node)) { - new_parent = g->mount.root; -#if ODFS_FEATURE_CDDA - } else if (g->has_cdda && nodes_same(parent_node, &g->cdda_root)) { - new_parent = g->mount.root; -#endif - } else { - err = odfs_resolve_parent_node(&g->mount, parent_node, &new_parent, - NULL); - if (err != ODFS_OK) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = odfs_err_to_dos(err); - return; - } - } - #if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE - trace_node(g, "parent-node", lock_node(ol)); - trace_node(g, "parent-result", parent_node); - trace_node(g, "parent-parent", &new_parent); + if (ol) + trace_node(g, "parent-node", lock_node(ol)); + if (parent) { + trace_node(g, "parent-result", lock_node(parent)); + trace_node(g, "parent-parent", lock_parent_node(parent)); + } #endif - parent = alloc_lock(g, parent_node, &new_parent, SHARED_LOCK); - if (!parent) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_NO_FREE_STORE; - return; - } pkt->dp_Res1 = LOCK_TO_BPTR(parent); } static void action_parent_fh(handler_global_t *g, struct DosPacket *pkt) { odfs_fh_t *fh = (odfs_fh_t *)pkt->dp_Arg1; - const odfs_node_t *parent_node; - odfs_node_t new_parent; - odfs_err_t err; odfs_lock_t *parent; + LONG err_dos; #if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE ODFS_TRACE(&g->log, ODFS_SUB_DOS, "parentfh-enter fh=%08lx", (unsigned long)fh); #endif - if (!fh) { - if (!g->mounted) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_NO_DISK; - return; - } - pkt->dp_Res1 = 0; - return; - } - - if (!fh_is_active(g, fh)) { + err_dos = odfs_handler_parent_fh_object(g, fh, &parent); + if (err_dos != 0) { pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_OBJECT_NOT_FOUND; - return; - } - - { - LONG err_dos = validate_object_volume(g, fh_volume(fh)); - if (err_dos != 0) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = err_dos; - return; - } - } - - if (node_is_mount_root(g, fh_node(fh))) { - pkt->dp_Res1 = 0; + pkt->dp_Res2 = err_dos; return; } - parent_node = fh_parent_node(fh); - if (node_is_mount_root(g, parent_node)) { - new_parent = g->mount.root; -#if ODFS_FEATURE_CDDA - } else if (g->has_cdda && nodes_same(parent_node, &g->cdda_root)) { - new_parent = g->mount.root; -#endif - } else { - err = odfs_resolve_parent_node(&g->mount, parent_node, &new_parent, - NULL); - if (err != ODFS_OK) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = odfs_err_to_dos(err); - return; - } - } - #if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE - trace_node(g, "parentfh-node", fh_node(fh)); - trace_node(g, "parentfh-result", parent_node); + if (fh) + trace_node(g, "parentfh-node", fh_node(fh)); + if (parent) + trace_node(g, "parentfh-result", lock_node(parent)); #endif - parent = alloc_lock(g, parent_node, &new_parent, SHARED_LOCK); - if (!parent) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_NO_FREE_STORE; - return; - } pkt->dp_Res1 = LOCK_TO_BPTR(parent); } @@ -1905,45 +2694,24 @@ static void action_same_lock(handler_global_t *g, struct DosPacket *pkt) { odfs_lock_t *l1 = LOCK_FROM_BPTR(pkt->dp_Arg1); odfs_lock_t *l2 = LOCK_FROM_BPTR(pkt->dp_Arg2); - odfs_volume_t *v1; - odfs_volume_t *v2; - int same = 0; + LONG same_result; + LONG err_dos; - if (!g->mounted && (!pkt->dp_Arg1 || !pkt->dp_Arg2)) { + err_dos = odfs_handler_same_lock_object(g, l1, l2, &same_result); + if (err_dos != 0) { pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_DEVICE_NOT_MOUNTED; - return; - } - - v1 = l1 ? l1->entry->volume : g->current_volume; - v2 = l2 ? l2->entry->volume : g->current_volume; - - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = LOCK_DIFFERENT; - - if (v1 != v2) + pkt->dp_Res2 = err_dos; return; - - pkt->dp_Res2 = LOCK_SAME_VOLUME; - if (!pkt->dp_Arg1 && !pkt->dp_Arg2) { - same = 1; - } else if (!pkt->dp_Arg1) { - same = node_is_mount_root(g, lock_node(l2)); - } else if (!pkt->dp_Arg2) { - same = node_is_mount_root(g, lock_node(l1)); - } else { - same = nodes_same(lock_node(l1), lock_node(l2)); } - if (same) { - pkt->dp_Res1 = DOSTRUE; - pkt->dp_Res2 = LOCK_SAME; - } + pkt->dp_Res1 = (same_result == LOCK_SAME) ? DOSTRUE : DOSFALSE; + pkt->dp_Res2 = same_result; } /* ---- examine ---- */ typedef struct exnext_ctx { + handler_global_t *g; struct FileInfoBlock *fib; ULONG previous_key; int first; @@ -1961,9 +2729,152 @@ static odfs_err_t exnext_cb(const odfs_node_t *entry, void *ctx) return ODFS_OK; } - fill_fib(ec->fib, entry); - ec->found = 1; - return ODFS_ERR_EOF; /* stop after one entry */ + fill_fib(ec->g, ec->fib, entry); + ec->found = 1; + return ODFS_ERR_EOF; /* stop after one entry */ +} + +#if !ODFS_AMIGA_OS4 +static odfs_exnext_cursor_t *exnext_cursor_for(handler_global_t *g, + odfs_lock_t *ol) +{ + if (ol) + return &ol->exnext; + return g ? &g->root_exnext : NULL; +} + +static void exnext_cursor_reset(odfs_exnext_cursor_t *cursor, ULONG dir_key) +{ + if (!cursor) + return; + + cursor->dir_key = dir_key; + cursor->previous_key = dir_key; + cursor->resume = 0; + cursor->valid = 1; + cursor->cdda_emitted = 0; +} + +static void exnext_cursor_invalidate(odfs_exnext_cursor_t *cursor) +{ + if (cursor) + cursor->valid = 0; +} + +static int exnext_cursor_matches(const odfs_exnext_cursor_t *cursor, + ULONG dir_key, + ULONG previous_key) +{ + return cursor && cursor->valid && + cursor->dir_key == dir_key && + cursor->previous_key == previous_key; +} + +static void exnext_cursor_update(odfs_exnext_cursor_t *cursor, + ULONG dir_key, + ULONG previous_key, + uint32_t resume, + int cdda_emitted) +{ + if (!cursor) + return; + + cursor->dir_key = dir_key; + cursor->previous_key = previous_key; + cursor->resume = resume; + cursor->valid = 1; + cursor->cdda_emitted = cdda_emitted; +} +#endif + +typedef struct dir_next_ctx { + ULONG previous_key; + int first; + int seen_previous; + int found; + odfs_node_t entry; +} dir_next_ctx_t; + +static odfs_err_t dir_next_cb(const odfs_node_t *entry, void *ctx) +{ + dir_next_ctx_t *dc = ctx; + + if (!dc->first && !dc->seen_previous) { + if (amiga_node_key(entry) == dc->previous_key) + dc->seen_previous = 1; + return ODFS_OK; + } + + dc->entry = *entry; + dc->found = 1; + return ODFS_ERR_EOF; +} + +LONG odfs_handler_next_dir_entry(handler_global_t *g, + odfs_lock_t *ol, + ULONG previous_key, + odfs_node_t *entry_out, + ULONG *key_out) +{ + const odfs_node_t *dir; + ULONG dir_key; + dir_next_ctx_t dc; + uint32_t resume = 0; + + if (!g || !entry_out || !key_out) + return ERROR_REQUIRED_ARG_MISSING; + + if (ol) { + LONG err_dos; + + if (!lock_is_active(g, ol)) + return ERROR_INVALID_LOCK; + + err_dos = validate_object_volume(g, ol->entry->volume); + if (err_dos != 0) + return err_dos; + dir = lock_node(ol); + } else if (!g->mounted) { + return ERROR_NO_DISK; + } else { + dir = &g->mount.root; + } + + if (dir->kind != ODFS_NODE_DIR) + return ERROR_OBJECT_WRONG_TYPE; + + dir_key = ol ? ol->key : amiga_node_key(dir); + memset(&dc, 0, sizeof(dc)); + dc.previous_key = previous_key; + dc.first = (previous_key == 0 || previous_key == dir_key); + +#if ODFS_FEATURE_CDDA + if (g->has_cdda && !dc.first && + previous_key == amiga_node_key(&g->cdda_root)) + return ERROR_NO_MORE_ENTRIES; + + if (g->has_cdda && dir->backend == ODFS_BACKEND_CDDA) { + (void)cdda_backend_ops.readdir(g->cdda_ctx, &g->mount.cache, + &g->log, dir, dir_next_cb, &dc, + &resume); + } else +#endif + { + (void)odfs_readdir(&g->mount, dir, dir_next_cb, &dc, &resume); + +#if ODFS_FEATURE_CDDA + if (!dc.found && g->has_cdda && node_is_mount_root(g, dir) && + previous_key != amiga_node_key(&g->cdda_root)) + (void)dir_next_cb(&g->cdda_root, &dc); +#endif + } + + if (!dc.found) + return ERROR_NO_MORE_ENTRIES; + + *entry_out = dc.entry; + *key_out = amiga_node_key(&dc.entry); + return 0; } static void action_examine_object(handler_global_t *g, struct DosPacket *pkt) @@ -1985,12 +2896,15 @@ static void action_examine_object(handler_global_t *g, struct DosPacket *pkt) return; } - if (node_is_mount_root(g, fnode)) - fill_root_fib(g, fib, fnode); + fill_fib(g, fib, fnode); +#if !ODFS_AMIGA_OS4 + if (fnode->kind == ODFS_NODE_DIR) + exnext_cursor_reset(exnext_cursor_for(g, ol), amiga_node_key(fnode)); else - fill_fib(fib, fnode); + exnext_cursor_invalidate(exnext_cursor_for(g, ol)); if (ol) ol->dos_private[1] = (ULONG)-1; +#endif #if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE trace_node(g, "examine-node", fnode); ODFS_TRACE(&g->log, ODFS_SUB_DOS, @@ -2010,6 +2924,10 @@ static void action_examine_next(handler_global_t *g, struct DosPacket *pkt) ULONG dir_key; uint32_t resume = 0; exnext_ctx_t ec; +#if !ODFS_AMIGA_OS4 + odfs_exnext_cursor_t *cursor = NULL; + int use_cursor = 0; +#endif if (ol) { LONG err_dos = validate_object_volume(g, ol->entry->volume); @@ -2031,21 +2949,41 @@ static void action_examine_next(handler_global_t *g, struct DosPacket *pkt) } dir_key = ol ? ol->key : amiga_node_key(dir); +#if !ODFS_AMIGA_OS4 + cursor = exnext_cursor_for(g, ol); + if (dir->backend != ODFS_BACKEND_CDDA && + exnext_cursor_matches(cursor, dir_key, (ULONG)fib->fib_DiskKey)) { + resume = cursor->resume; + use_cursor = 1; + } +#endif + ec.g = g; ec.fib = fib; ec.previous_key = (ULONG)fib->fib_DiskKey; ec.first = (ec.previous_key == dir_key); +#if !ODFS_AMIGA_OS4 + if (use_cursor) + ec.first = 1; +#endif ec.seen_previous = 0; ec.found = 0; /* * Match the Amiga CD filesystem model: Examine() leaves fib_DiskKey as * the directory key, and ExNext() returns each child's object key. This - * costs a rescan but avoids exposing private iterator offsets to - * Workbench/icon.library. + * keeps the visible key contract while the OS3 lock-private cursor carries + * the backend resume offset when callers preserve fib_DiskKey normally. */ /* check if CDDA virtual dir was already emitted */ #if ODFS_FEATURE_CDDA +#if !ODFS_AMIGA_OS4 + if (use_cursor && cursor->cdda_emitted) { + pkt->dp_Res1 = DOSFALSE; + pkt->dp_Res2 = ERROR_NO_MORE_ENTRIES; + return; + } +#endif if (g->has_cdda && !ec.first && ec.previous_key == amiga_node_key(&g->cdda_root)) { pkt->dp_Res1 = DOSFALSE; @@ -2071,6 +3009,11 @@ static void action_examine_next(handler_global_t *g, struct DosPacket *pkt) (void)odfs_readdir(&g->mount, dir, exnext_cb, &ec, &resume); if (ec.found) { +#if !ODFS_AMIGA_OS4 + if (dir->backend != ODFS_BACKEND_CDDA) + exnext_cursor_update(cursor, dir_key, (ULONG)fib->fib_DiskKey, + resume, 0); +#endif #if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE ODFS_TRACE(&g->log, ODFS_SUB_DOS, "exnext-found key=%08lx type=%ld name=%s", @@ -2084,7 +3027,12 @@ static void action_examine_next(handler_global_t *g, struct DosPacket *pkt) /* data entries exhausted — inject CDDA virtual dir if at root */ if (g->has_cdda && node_is_mount_root(g, dir) && ec.previous_key != amiga_node_key(&g->cdda_root)) { - fill_fib(fib, &g->cdda_root); + fill_fib(g, fib, &g->cdda_root); +#if !ODFS_AMIGA_OS4 + if (dir->backend != ODFS_BACKEND_CDDA) + exnext_cursor_update(cursor, dir_key, + (ULONG)fib->fib_DiskKey, resume, 1); +#endif #if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE ODFS_TRACE(&g->log, ODFS_SUB_DOS, "exnext-inject-cdda key=%08lx", @@ -2096,6 +3044,10 @@ static void action_examine_next(handler_global_t *g, struct DosPacket *pkt) #endif pkt->dp_Res1 = DOSFALSE; pkt->dp_Res2 = ERROR_NO_MORE_ENTRIES; +#if !ODFS_AMIGA_OS4 + if (dir->backend != ODFS_BACKEND_CDDA) + exnext_cursor_update(cursor, dir_key, ec.previous_key, resume, 0); +#endif } } @@ -2122,22 +3074,20 @@ static size_t exall_fixed_size(LONG data) return sizes[data]; } -static int exall_fill_entry(struct ExAllData **cursor, LONG *remaining, - LONG data, const odfs_node_t *entry) +static int exall_fill_entry(handler_global_t *g, struct ExAllData **cursor, + LONG *remaining, LONG data, + const odfs_node_t *entry) { struct ExAllData *ed = *cursor; - struct FileInfoBlock fib; - const char *name = entry->name; - const char *comment = ""; - size_t name_len = strlen(name) + 1u; - size_t comment_len = 1u; + odfs_handler_node_info_t info; + size_t name_len; + size_t comment_len; size_t need; UBYTE *p; - if (entry->amiga_as.has_comment) { - comment = entry->amiga_as.comment; - comment_len = strlen(comment) + 1u; - } + odfs_handler_fill_node_info(g, entry, &info); + name_len = strlen(info.name) + 1u; + comment_len = strlen(info.comment) + 1u; need = exall_fixed_size(data) + name_len; if (data >= ED_COMMENT) @@ -2147,29 +3097,28 @@ static int exall_fill_entry(struct ExAllData **cursor, LONG *remaining, if (need > (size_t)*remaining) return 0; - fill_fib(&fib, entry); memset(ed, 0, exall_fixed_size(data)); p = ((UBYTE *)ed) + exall_fixed_size(data); if (data >= ED_COMMENT) { - ed->ed_Comment = p; - memcpy(p, comment, comment_len); + ed->ed_Comment = (STRPTR)p; + memcpy(p, info.comment, comment_len); p += comment_len; } - ed->ed_Name = p; - memcpy(p, name, name_len); + ed->ed_Name = (STRPTR)p; + memcpy(p, info.name, name_len); if (data >= ED_TYPE) - ed->ed_Type = fib.fib_DirEntryType; + ed->ed_Type = info.fib_type; if (data >= ED_SIZE) - ed->ed_Size = (ULONG)fib.fib_Size; + ed->ed_Size = (ULONG)info.size; if (data >= ED_PROTECTION) - ed->ed_Prot = (ULONG)fib.fib_Protection; + ed->ed_Prot = info.protection; if (data >= ED_DATE) { - ed->ed_Days = (ULONG)fib.fib_Date.ds_Days; - ed->ed_Mins = (ULONG)fib.fib_Date.ds_Minute; - ed->ed_Ticks = (ULONG)fib.fib_Date.ds_Tick; + ed->ed_Days = (ULONG)info.date.ds_Days; + ed->ed_Mins = (ULONG)info.date.ds_Minute; + ed->ed_Ticks = (ULONG)info.date.ds_Tick; } if (data >= ED_OWNER) { ed->ed_OwnerUID = 0; @@ -2183,6 +3132,7 @@ static int exall_fill_entry(struct ExAllData **cursor, LONG *remaining, } typedef struct exall_ctx { + handler_global_t *g; struct ExAllData *cursor; struct ExAllData *last; struct ExAllControl *control; @@ -2215,13 +3165,15 @@ static odfs_err_t exall_cb(const odfs_node_t *entry, void *ctx) cursor_before = ec->cursor; remaining_before = ec->remaining; slot = ec->cursor; - if (!exall_fill_entry(&ec->cursor, &ec->remaining, ec->data, entry)) { + if (!exall_fill_entry(ec->g, &ec->cursor, &ec->remaining, ec->data, + entry)) { ec->full = 1; return ODFS_ERR_EOF; } - if (UtilityBase && ec->control->eac_MatchFunc && - !CallHookPkt(ec->control->eac_MatchFunc, slot, &ec->data)) { + if (ec->control->eac_MatchFunc && + !odfs_amiga_call_hook_pkt(ec->control->eac_MatchFunc, slot, + &ec->data)) { ec->cursor = cursor_before; ec->remaining = remaining_before; return ODFS_OK; @@ -2276,6 +3228,7 @@ static void action_examine_all(handler_global_t *g, struct DosPacket *pkt) } memset(&ec, 0, sizeof(ec)); + ec.g = g; ec.cursor = buf; ec.control = control; ec.remaining = size; @@ -2357,36 +3310,26 @@ static void action_examine_fh(handler_global_t *g, struct DosPacket *pkt) } } - fill_fib(fib, fh_node(fh)); + fill_fib(g, fib, fh_node(fh)); pkt->dp_Res1 = DOSTRUE; } static void action_fh_from_lock(handler_global_t *g, struct DosPacket *pkt) { odfs_lock_t *ol = LOCK_FROM_BPTR(pkt->dp_Arg2); + struct FileHandle *fhandle = (struct FileHandle *)BADDR(pkt->dp_Arg1); + odfs_fh_t *fh; + LONG err_dos; - if (ol) { - LONG err_dos = validate_object_volume(g, ol->entry->volume); - struct FileHandle *fhandle = (struct FileHandle *)BADDR(pkt->dp_Arg1); - odfs_fh_t *fh; - - if (err_dos != 0) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = err_dos; - return; - } - - fh = alloc_fh(g, ol->entry, ol->lock.fl_Access); - if (fh) { - fhandle->fh_Arg1 = (LONG)fh; - free_lock(g, ol); - pkt->dp_Res1 = DOSTRUE; - } else { - pkt->dp_Res2 = ERROR_NO_FREE_STORE; - } - } else { - pkt->dp_Res2 = ERROR_OBJECT_NOT_FOUND; + err_dos = odfs_handler_open_from_lock_object(g, ol, &fh); + if (err_dos != 0) { + pkt->dp_Res1 = DOSFALSE; + pkt->dp_Res2 = err_dos; + return; } + + fhandle->fh_Arg1 = (LONG)fh; + pkt->dp_Res1 = DOSTRUE; } /* ---- file I/O ---- */ @@ -2396,60 +3339,15 @@ static void action_findinput(handler_global_t *g, struct DosPacket *pkt) struct FileHandle *fhandle = (struct FileHandle *)BADDR(pkt->dp_Arg1); odfs_lock_t *dirlock = LOCK_FROM_BPTR(pkt->dp_Arg2); char path[512]; - odfs_node_t result, parent_node; - odfs_err_t err; - const odfs_node_t *start; - const odfs_node_t *start_parent; - - bstr_to_cstr(pkt->dp_Arg3, path, sizeof(path)); - - if (dirlock) { - LONG err_dos = validate_object_volume(g, dirlock->entry->volume); - if (err_dos != 0) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = err_dos; - return; - } - start = lock_node(dirlock); - start_parent = lock_parent_node(dirlock); - } else { - if (!g->mounted) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_NO_DISK; - return; - } - start = &g->mount.root; - start_parent = &g->mount.root; - } - - err = resolve_amiga_path(g, start, start_parent, path, &result, - &parent_node); - if (err != ODFS_OK) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = odfs_err_to_dos(err); - return; - } - - if (result.kind == ODFS_NODE_DIR) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_OBJECT_WRONG_TYPE; - return; - } - - odfs_entry_t *entry = alloc_entry(g->current_volume, &result, &parent_node); odfs_fh_t *fh; + LONG err_dos; - if (!entry) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_NO_FREE_STORE; - return; - } + bstr_to_cstr(pkt->dp_Arg3, path, sizeof(path)); - fh = alloc_fh(g, entry, SHARED_LOCK); - release_entry(entry); - if (!fh) { + err_dos = odfs_handler_open_object(g, dirlock, path, MODE_OLDFILE, &fh); + if (err_dos != 0) { pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_NO_FREE_STORE; + pkt->dp_Res2 = err_dos; return; } @@ -2462,90 +3360,49 @@ static void action_read(handler_global_t *g, struct DosPacket *pkt) odfs_fh_t *fh = (odfs_fh_t *)pkt->dp_Arg1; void *buf = (void *)pkt->dp_Arg2; LONG len = pkt->dp_Arg3; + LONG actual; + LONG err_dos; - if (!fh) { - pkt->dp_Res1 = -1; - pkt->dp_Res2 = ERROR_OBJECT_NOT_FOUND; - return; - } - - { - LONG err_dos = validate_object_volume(g, fh_volume(fh)); - if (err_dos != 0) { - pkt->dp_Res1 = -1; - pkt->dp_Res2 = err_dos; - return; - } - } - - size_t actual = (size_t)len; - odfs_err_t err = read_file_node(g, fh_node(fh), fh->pos, buf, &actual); - if (err != ODFS_OK && actual == 0) { + err_dos = odfs_handler_read_object(g, fh, buf, len, &actual); + if (err_dos != 0) { pkt->dp_Res1 = -1; - pkt->dp_Res2 = odfs_err_to_dos(err); + pkt->dp_Res2 = err_dos; return; } - fh->pos += actual; - pkt->dp_Res1 = (LONG)actual; + pkt->dp_Res1 = actual; } -static void action_seek(handler_global_t *g __attribute__((unused)), - struct DosPacket *pkt) +static void action_seek(handler_global_t *g, struct DosPacket *pkt) { odfs_fh_t *fh = (odfs_fh_t *)pkt->dp_Arg1; LONG offset = pkt->dp_Arg2; LONG mode = pkt->dp_Arg3; - LONG oldpos, newpos; - - if (!fh) { - pkt->dp_Res1 = -1; - pkt->dp_Res2 = ERROR_OBJECT_NOT_FOUND; - return; - } - - { - LONG err_dos = validate_object_volume(g, fh_volume(fh)); - if (err_dos != 0) { - pkt->dp_Res1 = -1; - pkt->dp_Res2 = err_dos; - return; - } - } - - oldpos = (LONG)fh->pos; + int64_t oldpos; + LONG err_dos; - switch (mode) { - case OFFSET_BEGINNING: newpos = offset; break; - case OFFSET_CURRENT: newpos = oldpos + offset; break; - case OFFSET_END: newpos = (LONG)fh_node(fh)->size + offset; break; - default: - pkt->dp_Res1 = -1; - pkt->dp_Res2 = ERROR_SEEK_ERROR; - return; - } - - if (newpos < 0 || (ULONG)newpos > fh_node(fh)->size) { + err_dos = odfs_handler_seek_object(g, fh, offset, mode, &oldpos); + if (err_dos != 0) { pkt->dp_Res1 = -1; - pkt->dp_Res2 = ERROR_SEEK_ERROR; + pkt->dp_Res2 = err_dos; return; } - fh->pos = (ULONG)newpos; - pkt->dp_Res1 = oldpos; + pkt->dp_Res1 = (LONG)oldpos; } static void action_end(handler_global_t *g, struct DosPacket *pkt) { odfs_fh_t *fh = (odfs_fh_t *)pkt->dp_Arg1; + LONG err_dos; - if (fh && !fh_is_active(g, fh)) { + err_dos = odfs_handler_close_object(g, fh); + if (err_dos != 0) { pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_OBJECT_NOT_FOUND; + pkt->dp_Res2 = err_dos; return; } - free_fh(g, fh); pkt->dp_Res1 = DOSTRUE; } @@ -2554,20 +3411,13 @@ static void action_end(handler_global_t *g, struct DosPacket *pkt) static void action_disk_info(handler_global_t *g, struct DosPacket *pkt) { struct InfoData *info = (struct InfoData *)BADDR(pkt->dp_Arg1); + LONG err_dos = odfs_handler_fill_info(g, NULL, info); - memset(info, 0, sizeof(*info)); - info->id_NumSoftErrors = 0; - info->id_UnitNumber = g->devunit; - info->id_DiskState = ID_WRITE_PROTECTED; - info->id_NumBlocks = g->mounted ? g->mount.total_blocks : 0; - info->id_NumBlocksUsed = info->id_NumBlocks; - info->id_BytesPerBlock = g->sector_size; - info->id_DiskType = g->mounted ? ID_DOS_DISK : ID_NO_DISK_PRESENT; - info->id_VolumeNode = MKBADDR(volume_node_ptr(g->current_volume)); - info->id_InUse = (g->current_volume && g->current_volume->volnode && - g->current_volume->volnode->dl_LockList) ? - DOSTRUE : DOSFALSE; - + if (err_dos != 0) { + pkt->dp_Res1 = DOSFALSE; + pkt->dp_Res2 = err_dos; + return; + } pkt->dp_Res1 = DOSTRUE; } @@ -2575,6 +3425,7 @@ static void action_info(handler_global_t *g, struct DosPacket *pkt) { odfs_lock_t *ol = LOCK_FROM_BPTR(pkt->dp_Arg1); struct InfoData *info = (struct InfoData *)BADDR(pkt->dp_Arg2); + LONG err_dos; if (pkt->dp_Arg1 && !ol) { pkt->dp_Res1 = DOSFALSE; @@ -2582,30 +3433,12 @@ static void action_info(handler_global_t *g, struct DosPacket *pkt) return; } - if (ol) { - LONG err_dos = validate_object_volume(g, ol->entry->volume); - if (err_dos != 0) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = err_dos; - return; - } + err_dos = odfs_handler_fill_info(g, ol, info); + if (err_dos != 0) { + pkt->dp_Res1 = DOSFALSE; + pkt->dp_Res2 = err_dos; + return; } - - /* Single mounted volume: the lock only needs to be valid/current. */ - (void)ol; - memset(info, 0, sizeof(*info)); - info->id_NumSoftErrors = 0; - info->id_UnitNumber = g->devunit; - info->id_DiskState = ID_WRITE_PROTECTED; - info->id_NumBlocks = g->mounted ? g->mount.total_blocks : 0; - info->id_NumBlocksUsed = info->id_NumBlocks; - info->id_BytesPerBlock = g->sector_size; - info->id_DiskType = g->mounted ? ID_DOS_DISK : ID_NO_DISK_PRESENT; - info->id_VolumeNode = MKBADDR(volume_node_ptr(g->current_volume)); - info->id_InUse = (g->current_volume && g->current_volume->volnode && - g->current_volume->volnode->dl_LockList) ? - DOSTRUE : DOSFALSE; - pkt->dp_Res1 = DOSTRUE; } @@ -2626,17 +3459,14 @@ static void action_current_volume(handler_global_t *g, static void action_inhibit(handler_global_t *g, struct DosPacket *pkt) { - LONG state = pkt->dp_Arg1; + LONG err_dos = odfs_handler_inhibit(g, pkt->dp_Arg1); - if (state != DOSFALSE) { - /* inhibit on — unmount, stop I/O */ - g->inhibited = 1; - unmount_volume(g); - } else { - /* inhibit off — try to remount */ - g->inhibited = 0; - mount_volume(g); + if (err_dos != 0) { + pkt->dp_Res1 = DOSFALSE; + pkt->dp_Res2 = err_dos; + return; } + pkt->dp_Res1 = DOSTRUE; } @@ -2716,6 +3546,7 @@ static void handle_packet(handler_global_t *g, struct DosPacket *pkt) /* ---- shutdown ---- */ case ACTION_DIE: + case ACTION_SHUTDOWN: pkt->dp_Res1 = DOSTRUE; break; @@ -2811,32 +3642,20 @@ static void sync_device_node(handler_global_t *g, struct DeviceNode *devnode) static struct DeviceNode *create_device_node(handler_global_t *g) { struct DeviceNode *devnode; - UBYTE *namebuf; char name[32]; - int namelen; - size_t alloc_size; device_node_name_from_bstr(g->devnode ? g->devnode->dn_Name : 0, name, sizeof(name)); if (name[0] == '\0') memcpy(name, "ODFS0", 6); - namelen = strlen(name); - if (namelen > 30) - namelen = 30; - - alloc_size = sizeof(*devnode) + 32u; - devnode = AllocMem(alloc_size, MEMF_PUBLIC | MEMF_CLEAR); + devnode = odfs_amiga_create_dos_entry(name, DLT_DEVICE); if (!devnode) return NULL; - namebuf = (UBYTE *)(devnode + 1); - namebuf[0] = (UBYTE)namelen; - memcpy(namebuf + 1, name, (size_t)namelen); - - devnode->dn_Next = 0; +#if !ODFS_AMIGA_OS4 devnode->dn_Lock = g->devnode ? g->devnode->dn_Lock : 0; - devnode->dn_Name = MKBADDR(namebuf); +#endif sync_device_node(g, devnode); return devnode; @@ -2844,9 +3663,7 @@ static struct DeviceNode *create_device_node(handler_global_t *g) static void destroy_device_node(struct DeviceNode *devnode) { - if (!devnode) - return; - FreeMem(devnode, sizeof(*devnode) + 32u); + odfs_amiga_delete_dos_entry(devnode); } static void publish_device_node(handler_global_t *g) @@ -2937,6 +3754,64 @@ static void unpublish_device_node(handler_global_t *g) g->published_devnode_owned = 0; } +#if ODFS_AMIGA_OS4 +static LONG activate_vector_port(handler_global_t *g) +{ + struct FileSystemVectorPort *vp; + LONG sigbit; + + if (!g || !g->process_port) + return ERROR_REQUIRED_ARG_MISSING; + + vp = odfs_os4_alloc_vector_port(g); + if (!vp) + return ERROR_NO_FREE_STORE; + + sigbit = AllocSignal(-1); + if (sigbit < 0) { + odfs_os4_free_vector_port(vp); + return ERROR_NO_FREE_STORE; + } + + vp->MP.mp_Flags = PA_SIGNAL; + vp->MP.mp_SigBit = (UBYTE)sigbit; + vp->MP.mp_SigTask = FindTask(NULL); + + if (GetFileSystemVectorPort(&vp->MP, FS_VECTORPORT_VERSION) != vp) { + FreeSignal(sigbit); + odfs_os4_free_vector_port(vp); + return ERROR_OBJECT_WRONG_TYPE; + } + + g->vector_port = vp; + g->vector_sigbit = sigbit; + g->dosport = &vp->MP; + if (g->devnode) + g->devnode->dn_Task = g->dosport; + + ODFS_INFO(&g->log, ODFS_SUB_CORE, + "OS4 filesystem vector port active"); + return 0; +} + +static void deactivate_vector_port(handler_global_t *g) +{ + if (!g) + return; + + if (g->vector_port) { + odfs_os4_free_vector_port(g->vector_port); + g->vector_port = NULL; + } + if (g->vector_sigbit >= 0) { + FreeSignal((BYTE)g->vector_sigbit); + g->vector_sigbit = -1; + } + if (g->process_port) + g->dosport = g->process_port; +} +#endif + /* ------------------------------------------------------------------ */ /* volume mount / unmount */ /* ------------------------------------------------------------------ */ @@ -2944,36 +3819,13 @@ static void unpublish_device_node(handler_global_t *g) static struct DeviceList *create_volume_node(handler_global_t *g) { struct DeviceList *dl; - UBYTE *namebuf; - int namelen; - size_t alloc_size; - namelen = strlen(g->volname); - if (namelen > 30) - namelen = 30; - - /* - * Build the volume DosList entry directly instead of routing the name - * through MakeDosEntry(), which may normalize names containing AmigaDOS - * metacharacters such as parentheses. - */ - alloc_size = sizeof(*dl) + 32u; - dl = AllocMem(alloc_size, MEMF_PUBLIC | MEMF_CLEAR); + dl = odfs_amiga_create_dos_entry(g->volname, DLT_VOLUME); if (!dl) return NULL; - namebuf = (UBYTE *)(dl + 1); - namebuf[0] = (UBYTE)namelen; - memcpy(namebuf + 1, g->volname, (size_t)namelen); - - dl->dl_Next = 0; - dl->dl_Type = DLT_VOLUME; dl->dl_Task = g->dosport; - dl->dl_Lock = 0; - dl->dl_LockList = 0; dl->dl_DiskType = ID_DOS_DISK; - dl->dl_unused = 0; - dl->dl_Name = MKBADDR(namebuf); fill_volume_date(g, &dl->dl_VolumeDate); return dl; @@ -2981,9 +3833,7 @@ static struct DeviceList *create_volume_node(handler_global_t *g) static void destroy_volume_node(struct DeviceList *volnode) { - if (!volnode) - return; - FreeMem(volnode, sizeof(*volnode) + 32u); + odfs_amiga_delete_dos_entry(volnode); } static void detach_volume_node(struct DeviceList *volnode) @@ -3077,7 +3927,7 @@ static void parse_control_string(handler_global_t *g __attribute__((unused)), return; rdargs->RDA_Flags |= RDAF_NOPROMPT; - rdargs->RDA_Source.CS_Buffer = (UBYTE *)buf; + rdargs->RDA_Source.CS_Buffer = (STRPTR)buf; rdargs->RDA_Source.CS_Length = len + 1; rdargs->RDA_Source.CS_CurChr = 0; @@ -3300,7 +4150,6 @@ static void unmount_volume(handler_global_t *g) if (!volume) return; - rebuild_volume_locklist(g, volume); detach_volume_node(volume->volnode); notify_workbench_disk_change(FALSE); if (volume->object_count != 0) { @@ -3316,23 +4165,23 @@ static void unmount_volume(handler_global_t *g) static void install_media_change(handler_global_t *g) { - g->chgsigbit = AllocSignal(-1); + g->chgsigbit = odfs_amiga_alloc_signal(-1); if (g->chgsigbit == -1) return; - g->chgport = CreateMsgPort(); + g->chgport = odfs_amiga_create_msg_port(); if (!g->chgport) { - FreeSignal(g->chgsigbit); + odfs_amiga_free_signal(g->chgsigbit); g->chgsigbit = -1; return; } - g->chgreq = (struct IOStdReq *)CreateIORequest(g->chgport, - sizeof(struct IOStdReq)); + g->chgreq = (struct IOStdReq *)odfs_amiga_create_io_request( + g->chgport, sizeof(struct IOStdReq)); if (!g->chgreq) { - DeleteMsgPort(g->chgport); + odfs_amiga_delete_msg_port(g->chgport); g->chgport = NULL; - FreeSignal(g->chgsigbit); + odfs_amiga_free_signal(g->chgsigbit); g->chgsigbit = -1; return; } @@ -3342,13 +4191,10 @@ static void install_media_change(handler_global_t *g) g->chgreq->io_Unit = g->devreq->io_Unit; g->chgreq->io_Command = TD_ADDCHANGEINT; - g->changeint.is_Node.ln_Type = NT_INTERRUPT; - g->changeint.is_Node.ln_Pri = 0; - g->changeint.is_Node.ln_Name = (char *)"odfs-mediachange"; g->changeint_data.task = g->dosport->mp_SigTask; g->changeint_data.sigmask = 1UL << g->chgsigbit; - g->changeint.is_Data = &g->changeint_data; - g->changeint.is_Code = (void (*)(void))(APTR)changeint_handler; + odfs_amiga_init_interrupt(&g->changeint, "odfs-mediachange", + &g->changeint_data, changeint_signal); g->chgreq->io_Data = (APTR)&g->changeint; g->chgreq->io_Length = sizeof(g->changeint); g->chgreq->io_Flags = 0; @@ -3372,15 +4218,15 @@ static void remove_media_change(handler_global_t *g) /* don't CloseDevice — we don't own it */ g->chgreq->io_Device = NULL; g->chgreq->io_Unit = NULL; - DeleteIORequest((struct IORequest *)g->chgreq); + odfs_amiga_delete_io_request((struct IORequest *)g->chgreq); g->chgreq = NULL; } if (g->chgport) { - DeleteMsgPort(g->chgport); + odfs_amiga_delete_msg_port(g->chgport); g->chgport = NULL; } if (g->chgsigbit != -1) { - FreeSignal(g->chgsigbit); + odfs_amiga_free_signal(g->chgsigbit); g->chgsigbit = -1; } @@ -3456,26 +4302,42 @@ static void handle_media_change(handler_global_t *g) /* handler main entry point */ /* ------------------------------------------------------------------ */ -void handler_main(void) +void handler_main_startup(struct Message *startup_msg) { handler_global_t *g; struct Message *msg; struct DosPacket *pkt; + struct DosPacket *shutdown_pkt = NULL; struct FileSysStartupMsg *fssm; struct DosEnvec *de; ULONG dossig, chgsig, waitmask; int running = 1; - static amiga_media_ctx_t amctx; (void)version_string; /* ensure $VER is not optimized out */ - SysBase = *((struct ExecBase **)4L); - - g = AllocMem(sizeof(*g), MEMF_PUBLIC | MEMF_CLEAR); - if (!g) + odfs_amiga_init_sysbase(); + + g = odfs_amiga_alloc_mem(sizeof(*g), MEMF_PUBLIC | MEMF_CLEAR); + if (!g) { + /* + * Never exit without answering the startup packet: DOS blocks + * the mounting context (during boot, the whole boot) until the + * packet is replied. + */ + if (startup_msg && startup_msg->mn_Node.ln_Name) { + pkt = (struct DosPacket *)startup_msg->mn_Node.ln_Name; + if (pkt->dp_Port) { + pkt->dp_Res1 = DOSFALSE; + pkt->dp_Res2 = ERROR_NO_FREE_STORE; + startup_msg->mn_Node.ln_Succ = NULL; + startup_msg->mn_Node.ln_Pred = NULL; + PutMsg(pkt->dp_Port, startup_msg); + } + } return; + } - g->sysbase = SysBase; + g->sysbase = odfs_amiga_sysbase(); g->locklist.mlh_Head = (struct MinNode *)&g->locklist.mlh_Tail; g->locklist.mlh_Tail = NULL; g->locklist.mlh_TailPred = (struct MinNode *)&g->locklist.mlh_Head; @@ -3483,6 +4345,10 @@ void handler_main(void) g->fhlist.mlh_Tail = NULL; g->fhlist.mlh_TailPred = (struct MinNode *)&g->fhlist.mlh_Head; g->next_volume_id = 1; +#if ODFS_AMIGA_OS4 + g->vector_sigbit = -1; + InitSemaphore(&g->fs_sem); +#endif g->chgsigbit = -1; g->toc_passthrough = -1; g->last_session_passthrough = -1; @@ -3491,12 +4357,20 @@ void handler_main(void) { struct Process *proc = (struct Process *)FindTask(NULL); - g->dosport = &proc->pr_MsgPort; +#if ODFS_AMIGA_OS4 + g->handler_task = (struct Task *)proc; +#endif + g->process_port = &proc->pr_MsgPort; + g->dosport = g->process_port; } /* wait for startup packet */ - WaitPort(g->dosport); - msg = GetMsg(g->dosport); + if (startup_msg) { + msg = startup_msg; + } else { + WaitPort(g->dosport); + msg = GetMsg(g->dosport); + } pkt = (struct DosPacket *)msg->mn_Node.ln_Name; g->devnode = (struct DeviceNode *)BADDR(pkt->dp_Arg3); @@ -3529,21 +4403,21 @@ void handler_main(void) "ODFileSystem " ODFS_GIT_VERSION " (" ODFS_AMIGA_DATE ") starting..."); - DOSBase = (struct DosLibrary *)OpenLibrary((CONST_STRPTR)"dos.library", 36); - if (!DOSBase) { + if (!odfs_amiga_open_libraries()) { ODFS_ERROR(&g->log, ODFS_SUB_CORE, "open dos.library failed"); pkt->dp_Res1 = DOSFALSE; pkt->dp_Res2 = ERROR_INVALID_RESIDENT_LIBRARY; return_packet(g, pkt); - FreeMem(g, sizeof(*g)); + odfs_amiga_free_mem(g, sizeof(*g)); return; } - g->dosbase = DOSBase; - UtilityBase = OpenLibrary((CONST_STRPTR)"utility.library", 36); + g->dosbase = odfs_amiga_dosbase(); + ODFS_INFO(&g->log, ODFS_SUB_CORE, "libraries open, device=%s unit=%lu", + g->devname, (unsigned long)g->devunit); /* open device */ - g->devport = CreateMsgPort(); + g->devport = odfs_amiga_create_msg_port(); if (!g->devport) { ODFS_ERROR(&g->log, ODFS_SUB_IO, "CreateMsgPort failed for %s unit=%lu", @@ -3554,8 +4428,8 @@ void handler_main(void) goto shutdown; } - g->devreq = (struct IOStdReq *)CreateIORequest(g->devport, - sizeof(struct IOStdReq)); + g->devreq = (struct IOStdReq *)odfs_amiga_create_io_request( + g->devport, sizeof(struct IOStdReq)); if (!g->devreq) { ODFS_ERROR(&g->log, ODFS_SUB_IO, "CreateIORequest failed for %s unit=%lu", @@ -3566,6 +4440,8 @@ void handler_main(void) goto shutdown; } + ODFS_INFO(&g->log, ODFS_SUB_IO, "opening %s unit %lu", + g->devname, (unsigned long)g->devunit); if (OpenDevice((CONST_STRPTR)g->devname, g->devunit, (struct IORequest *)g->devreq, g->devflags) != 0) { ODFS_ERROR(&g->log, ODFS_SUB_IO, @@ -3578,21 +4454,11 @@ void handler_main(void) return_packet(g, pkt); goto shutdown; } + ODFS_INFO(&g->log, ODFS_SUB_IO, "device open"); g->devnode->dn_Startup = MKBADDR(fssm); g->devnode->dn_Task = g->dosport; - /* - * SCSI drive setup: wait for unit ready and set 2048-byte blocks. - * Mode Select may fail on non-SCSI devices (e.g. IDE with - * trackdisk.device) — this is non-fatal. - */ - scsi_test_unit_ready(g); - if (!scsi_mode_select(g, 2048)) { - /* Mode Select failed — drive probably doesn't support it - * or is already in 2048-byte mode. Not fatal. */ - } - /* * Allocate DMA-safe bounce buffer using de_BufMemType. * 16-byte aligned for 68040 DMA performance (CDVDFS pattern). @@ -3602,11 +4468,11 @@ void handler_main(void) #define DMA_BUF_SECTORS 8 ULONG memtype = de->de_BufMemType | MEMF_PUBLIC; ULONG raw_size = DMA_BUF_SECTORS * g->sector_size + 15; - g->dma_buf_raw = (uint8_t *)AllocMem(raw_size, memtype); + g->dma_buf_raw = (uint8_t *)odfs_amiga_alloc_mem(raw_size, memtype); if (!g->dma_buf_raw) { /* fallback: try without specific memory type */ - g->dma_buf_raw = (uint8_t *)AllocMem(raw_size, - MEMF_PUBLIC); + g->dma_buf_raw = (uint8_t *)odfs_amiga_alloc_mem(raw_size, + MEMF_PUBLIC); } if (g->dma_buf_raw) { /* 16-byte align */ @@ -3624,10 +4490,70 @@ void handler_main(void) } } - /* set up media adapter */ - amctx.g = g; + /* + * Probe the drive geometry before committing to the mount. + * TD_GETGEOMETRY uses the native ATA path and returns promptly even + * on a not-ready unit (unlike HD_SCSICMD, which can hang). A failure + * here means the unit has no usable device behind it — e.g. the + * empty/phantom second ATAPI channel that QEMU's peg2ide reports + * from a floating bus. Decline the mount in that case rather than + * publishing a dead drive that DOS would route to and poll. + * + * When geometry succeeds but reports a non-2048 block size, switch + * the drive to 2048-byte CD blocks with MODE SELECT (an HD_SCSICMD, + * issued only for a confirmed present drive so it cannot hang on a + * phantom unit). + */ + { + struct DriveGeometry geom; + LONG geo_rc; + + memset(&geom, 0, sizeof(geom)); + g->devreq->io_Command = TD_GETGEOMETRY; + g->devreq->io_Data = &geom; + g->devreq->io_Length = sizeof(geom); + geo_rc = DoIO((struct IORequest *)g->devreq); + ODFS_INFO(&g->log, ODFS_SUB_IO, + "geometry rc=%ld sector=%lu", (long)geo_rc, + (unsigned long)geom.dg_SectorSize); + + if (geo_rc != 0) { + ODFS_WARN(&g->log, ODFS_SUB_IO, + "no usable device on unit %lu (geometry rc=%ld) - " + "declining mount", + (unsigned long)g->devunit, (long)geo_rc); + pkt->dp_Res1 = DOSFALSE; + pkt->dp_Res2 = ERROR_DEVICE_NOT_MOUNTED; + return_packet(g, pkt); + goto shutdown; + } + + if (geom.dg_SectorSize != 0 && geom.dg_SectorSize != 2048) { + ODFS_INFO(&g->log, ODFS_SUB_IO, "mode select..."); + (void)scsi_mode_select(g, 2048); + } + } + ODFS_INFO(&g->log, ODFS_SUB_IO, "scsi setup done"); + + /* set up media adapter (context lives in g, one per process) */ + g->media_ctx.g = g; g->media.ops = &amiga_media_ops; - g->media.ctx = &amctx; + g->media.ctx = &g->media_ctx; + +#if ODFS_AMIGA_OS4 + { + LONG err_dos = activate_vector_port(g); + if (err_dos != 0) { + ODFS_ERROR(&g->log, ODFS_SUB_CORE, + "OS4 filesystem vector port setup failed: %ld", + (long)err_dos); + pkt->dp_Res1 = DOSFALSE; + pkt->dp_Res2 = err_dos; + return_packet(g, pkt); + goto shutdown; + } + } +#endif /* reply startup packet */ pkt->dp_Res1 = DOSTRUE; @@ -3651,9 +4577,11 @@ void handler_main(void) /* media change */ if ((sigs & chgsig) && !g->inhibited) { + ODFS_FS_LOCK(g); handle_media_change(g); + ODFS_FS_UNLOCK(g); /* re-init media adapter after remount */ - amctx.g = g; + g->media_ctx.g = g; } /* DOS packets */ @@ -3664,10 +4592,19 @@ void handler_main(void) trace_pkt(g, "dequeue", pkt); #endif - if (pkt->dp_Type == ACTION_DIE) { - pkt->dp_Res1 = DOSTRUE; - pkt->dp_Res2 = 0; - return_packet(g, pkt); +#if ODFS_AMIGA_OS4 + /* hand-built packets and private messages reach this + * port directly; validate before trusting the packet */ + if (!pkt || pkt->dp_Link != msg) { + ReplyMsg(msg); + continue; + } + msg->mn_ReplyPort = pkt->dp_Port; +#endif + + if (pkt->dp_Type == ACTION_DIE || + pkt->dp_Type == ACTION_SHUTDOWN) { + shutdown_pkt = pkt; running = 0; break; } @@ -3679,37 +4616,77 @@ void handler_main(void) continue; } + /* + * Service DOS packets with the shared packet dispatcher. + * On OS4 this dos.library drives the handler through the + * classic packet protocol (BPTR locks) for the legacy + * Lock()/Examine()/ExNext() APIs, including the + * deprecated examine actions that DOSEmulatePacket + * answers with ERROR_ACTION_NOT_KNOWN. Hold the + * filesystem semaphore so packet servicing in the + * handler process is serialized against native + * vector-port calls made from caller context. + */ + ODFS_FS_LOCK(g); handle_packet(g, pkt); + ODFS_FS_UNLOCK(g); return_packet(g, pkt); } } } - /* ---- shutdown ---- */ + /* + * ---- shutdown ---- + * Invalidate the vector port first so dos.library stops vectoring + * new callers, then tear down DOS-visible state while holding the + * filesystem semaphore so in-flight vector calls finish first. + * The shutdown packet is replied only after the teardown is done. + */ + ODFS_FS_LOCK(g); +#if ODFS_AMIGA_OS4 + odfs_os4_invalidate_vector_port(g->vector_port); +#endif remove_media_change(g); unmount_volume(g); drain_all_objects(g); unpublish_device_node(g); + ODFS_FS_UNLOCK(g); + + if (shutdown_pkt) { + shutdown_pkt->dp_Res1 = DOSTRUE; + shutdown_pkt->dp_Res2 = 0; + return_packet(g, shutdown_pkt); + } shutdown: +#if ODFS_AMIGA_OS4 + release_vector_io_request(g); +#endif if (g->devreq) { if (g->devreq->io_Device) CloseDevice((struct IORequest *)g->devreq); - DeleteIORequest((struct IORequest *)g->devreq); + odfs_amiga_delete_io_request((struct IORequest *)g->devreq); } if (g->devport) - DeleteMsgPort(g->devport); + odfs_amiga_delete_msg_port(g->devport); /* free DMA bounce buffer */ if (g->dma_buf_raw) - FreeMem(g->dma_buf_raw, DMA_BUF_SECTORS * g->sector_size + 15); + odfs_amiga_free_mem(g->dma_buf_raw, + DMA_BUF_SECTORS * g->sector_size + 15); if (g->devnode) g->devnode->dn_Task = NULL; - if (UtilityBase) - CloseLibrary(UtilityBase); +#if ODFS_AMIGA_OS4 + deactivate_vector_port(g); +#endif + + odfs_amiga_close_libraries(); + odfs_amiga_free_mem(g, sizeof(*g)); +} - CloseLibrary((struct Library *)DOSBase); - FreeMem(g, sizeof(*g)); +void handler_main(void) +{ + handler_main_startup(NULL); } diff --git a/platform/amiga/os3/amiga_target_compat.h b/platform/amiga/os3/amiga_target_compat.h new file mode 100644 index 0000000..d3ab2e7 --- /dev/null +++ b/platform/amiga/os3/amiga_target_compat.h @@ -0,0 +1,20 @@ +/* + * amiga_target_compat.h - AmigaOS 3 / AROS source compatibility + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef ODFS_AMIGA_TARGET_COMPAT_H +#define ODFS_AMIGA_TARGET_COMPAT_H + +#define ODFS_AMIGA_OS4 0 + +/* + * OS4 V51+ shutdown packet. OS3 DOS never sends it, but accepting it + * unconditionally keeps the shared packet loop free of OS conditionals. + */ +#ifndef ACTION_SHUTDOWN +#define ACTION_SHUTDOWN 3000 +#endif + +#endif /* ODFS_AMIGA_TARGET_COMPAT_H */ diff --git a/platform/amiga/os3/sys_compat.c b/platform/amiga/os3/sys_compat.c new file mode 100644 index 0000000..8c42b40 --- /dev/null +++ b/platform/amiga/os3/sys_compat.c @@ -0,0 +1,165 @@ +/* + * sys_compat.c - AmigaOS 3 / AROS integration helpers + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "sys_compat.h" + +#include +#include +#include + +#include + +struct ExecBase *SysBase; +struct DosLibrary *DOSBase; +struct Library *UtilityBase; + +static odfs_amiga_interrupt_fn interrupt_code; + +static LONG odfs_amiga_interrupt_entry(APTR data asm("a1")) +{ + return interrupt_code ? interrupt_code(data) : 0; +} + +void odfs_amiga_init_sysbase(void) +{ + SysBase = *((struct ExecBase **)4L); +} + +struct ExecBase *odfs_amiga_sysbase(void) +{ + return SysBase; +} + +struct DosLibrary *odfs_amiga_dosbase(void) +{ + return DOSBase; +} + +int odfs_amiga_open_libraries(void) +{ + DOSBase = (struct DosLibrary *)OpenLibrary((CONST_STRPTR)"dos.library", + 36); + if (!DOSBase) + return 0; + + UtilityBase = OpenLibrary((CONST_STRPTR)"utility.library", 36); + return 1; +} + +void odfs_amiga_close_libraries(void) +{ + if (UtilityBase) { + CloseLibrary(UtilityBase); + UtilityBase = NULL; + } + if (DOSBase) { + CloseLibrary((struct Library *)DOSBase); + DOSBase = NULL; + } +} + +void *odfs_amiga_alloc_mem(ULONG size, ULONG flags) +{ + return AllocMem(size, flags); +} + +void odfs_amiga_free_mem(void *ptr, ULONG size) +{ + if (ptr) + FreeMem(ptr, size); +} + +struct MsgPort *odfs_amiga_create_msg_port(void) +{ + return CreateMsgPort(); +} + +void odfs_amiga_delete_msg_port(struct MsgPort *port) +{ + if (port) + DeleteMsgPort(port); +} + +struct IORequest *odfs_amiga_create_io_request(struct MsgPort *port, + ULONG size) +{ + return CreateIORequest(port, size); +} + +void odfs_amiga_delete_io_request(struct IORequest *req) +{ + if (req) + DeleteIORequest(req); +} + +LONG odfs_amiga_alloc_signal(LONG num) +{ + return AllocSignal(num); +} + +void odfs_amiga_free_signal(LONG num) +{ + if (num != -1) + FreeSignal(num); +} + +void *odfs_amiga_create_dos_entry(const char *name, LONG type) +{ + struct DosList *dl; + UBYTE *namebuf; + size_t namelen; + + if (!name) + return NULL; + + namelen = strlen(name); + if (namelen > 30) + namelen = 30; + + /* + * Build the DosList entry directly instead of routing the name + * through MakeDosEntry(), which may normalize names containing + * AmigaDOS metacharacters such as parentheses. + */ + dl = AllocMem(sizeof(*dl) + 32u, MEMF_PUBLIC | MEMF_CLEAR); + if (!dl) + return NULL; + + namebuf = (UBYTE *)(dl + 1); + namebuf[0] = (UBYTE)namelen; + memcpy(namebuf + 1, name, namelen); + + dl->dol_Type = type; + dl->dol_Name = MKBADDR(namebuf); + return dl; +} + +void odfs_amiga_delete_dos_entry(void *node) +{ + if (node) + FreeMem(node, sizeof(struct DosList) + 32u); +} + +void odfs_amiga_init_interrupt(struct Interrupt *intr, + const char *name, + APTR data, + odfs_amiga_interrupt_fn code) +{ + interrupt_code = code; + intr->is_Node.ln_Type = NT_INTERRUPT; + intr->is_Node.ln_Pri = 0; + intr->is_Node.ln_Name = (char *)name; + intr->is_Data = data; + intr->is_Code = (void (*)(void))(APTR)odfs_amiga_interrupt_entry; +} + +ULONG odfs_amiga_call_hook_pkt(struct Hook *hook, APTR object, APTR message) +{ + if (!UtilityBase) + return 1; + + return CallHookPkt(hook, object, message); +} diff --git a/platform/amiga/os4/amiga_target_compat.h b/platform/amiga/os4/amiga_target_compat.h new file mode 100644 index 0000000..27be916 --- /dev/null +++ b/platform/amiga/os4/amiga_target_compat.h @@ -0,0 +1,19 @@ +/* + * amiga_target_compat.h - AmigaOS 4 source compatibility + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef ODFS_AMIGA_TARGET_COMPAT_H +#define ODFS_AMIGA_TARGET_COMPAT_H + +#define ODFS_AMIGA_OS4 1 + +/* + * The current shared packet handler still uses classic names such as + * DeviceList and ACTION_DIE. Keep that compatibility local to the OS4 + * frontend while the native vector-port frontend is being split out. + */ +#include + +#endif /* ODFS_AMIGA_TARGET_COMPAT_H */ diff --git a/platform/amiga/os4/freestanding.c b/platform/amiga/os4/freestanding.c new file mode 100644 index 0000000..317992e --- /dev/null +++ b/platform/amiga/os4/freestanding.c @@ -0,0 +1,100 @@ +/* + * freestanding.c - minimal libc for the AmigaOS 4 handler + * + * SPDX-License-Identifier: BSD-2-Clause + * + * The OS4 newlib static libc.a contains only stubs that call through + * the INewlib interface, which is set up by the C runtime startup the + * handler cannot use (see start.c). Provide the handful of string + * functions the handler and core library reference. + * + * Compiled with -fno-builtin so the compiler cannot turn these loops + * back into calls to themselves. + */ + +#include +#include + +void *memcpy(void *dst, const void *src, size_t n) +{ + unsigned char *d = dst; + const unsigned char *s = src; + + if (((size_t)d & 3u) == 0 && ((size_t)s & 3u) == 0) { + while (n >= 4) { + *(unsigned long *)d = *(const unsigned long *)s; + d += 4; + s += 4; + n -= 4; + } + } + while (n--) + *d++ = *s++; + return dst; +} + +void *memset(void *dst, int c, size_t n) +{ + unsigned char *d = dst; + + while (n--) + *d++ = (unsigned char)c; + return dst; +} + +int memcmp(const void *a, const void *b, size_t n) +{ + const unsigned char *pa = a; + const unsigned char *pb = b; + + while (n--) { + if (*pa != *pb) + return *pa - *pb; + pa++; + pb++; + } + return 0; +} + +size_t strlen(const char *s) +{ + const char *p = s; + + while (*p) + p++; + return (size_t)(p - s); +} + +int strcmp(const char *a, const char *b) +{ + while (*a && *a == *b) { + a++; + b++; + } + return (unsigned char)*a - (unsigned char)*b; +} + +char *strchr(const char *s, int c) +{ + char ch = (char)c; + + for (;; s++) { + if (*s == ch) + return (char *)s; + if (*s == '\0') + return NULL; + } +} + +char *strncpy(char *dst, const char *src, size_t n) +{ + char *d = dst; + + while (n && *src) { + *d++ = *src++; + n--; + } + while (n--) + *d++ = '\0'; + return dst; +} diff --git a/platform/amiga/os4/main.c b/platform/amiga/os4/main.c new file mode 100644 index 0000000..2d33415 --- /dev/null +++ b/platform/amiga/os4/main.c @@ -0,0 +1,82 @@ +/* + * main.c - AmigaOS 4 handler entry wrapper + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "handler.h" + +#include +#include + +#include + +static void return_startup_packet(struct DosPacket *pkt, + LONG res1, + LONG res2) +{ + struct MsgPort *replyport; + struct Message *msg; + + if (!pkt || !pkt->dp_Link || !pkt->dp_Port) + return; + + replyport = pkt->dp_Port; + msg = pkt->dp_Link; + + pkt->dp_Res1 = res1; + pkt->dp_Res2 = res2; + msg->mn_Node.ln_Name = (char *)pkt; + msg->mn_Node.ln_Succ = NULL; + msg->mn_Node.ln_Pred = NULL; + PutMsg(replyport, msg); +} + +int odfs_os4_handler_main(void); + +int odfs_os4_handler_main(void) +{ + struct Process *proc; + struct Message *msg; + struct DosPacket *pkt; + + proc = (struct Process *)FindTask(NULL); + if (!proc) + return RETURN_FAIL; + +#if ODFS_SERIAL_DEBUG + DebugPrintF("[ODFS] handler_main: proc=%p port=%p waiting...\n", + proc, &proc->pr_MsgPort); +#endif + WaitPort(&proc->pr_MsgPort); + msg = GetMsg(&proc->pr_MsgPort); +#if ODFS_SERIAL_DEBUG + DebugPrintF("[ODFS] handler_main: msg=%p name=%p\n", + msg, msg ? msg->mn_Node.ln_Name : NULL); +#endif + if (!msg) { + proc->pr_Result2 = ERROR_OBJECT_WRONG_TYPE; + return RETURN_FAIL; + } + + if (!msg->mn_Node.ln_Name) { + ReplyMsg(msg); + proc->pr_Result2 = ERROR_OBJECT_WRONG_TYPE; + return RETURN_FAIL; + } + + pkt = (struct DosPacket *)msg->mn_Node.ln_Name; +#if ODFS_SERIAL_DEBUG + DebugPrintF("[ODFS] handler_main: pkt=%p type=%ld arg1=%lx arg2=%lx " + "arg3=%lx\n", pkt, pkt->dp_Type, pkt->dp_Arg1, + pkt->dp_Arg2, pkt->dp_Arg3); +#endif + if (pkt->dp_Type != ACTION_STARTUP) { + return_startup_packet(pkt, DOSFALSE, ERROR_ACTION_NOT_KNOWN); + proc->pr_Result2 = ERROR_ACTION_NOT_KNOWN; + return RETURN_FAIL; + } + + handler_main_startup(msg); + return RETURN_OK; +} diff --git a/platform/amiga/os4/start.c b/platform/amiga/os4/start.c new file mode 100644 index 0000000..24257e0 --- /dev/null +++ b/platform/amiga/os4/start.c @@ -0,0 +1,173 @@ +/* + * start.c - freestanding AmigaOS 4 handler entry + * + * SPDX-License-Identifier: BSD-2-Clause + * + * A filesystem handler process receives an ACTION_STARTUP DosPacket as + * its first process message. The newlib C runtime consumes the first + * process message while detecting a Workbench start, which deadlocks + * the mount, so the handler must not run any C runtime startup. + * + * The module supports two start paths: + * + * - As a normal disk-based handler, DOS enters the seglist at _start + * with the argument string in r3, its length in r4, and the ExecBase + * pointer in r5. + * + * - As a kickstart module, exec calls the Resident's rt_Init at + * coldstart (with ExecBase as the third argument); the init + * registers a FileSysEntry for DosType 'CD01' in FileSystem.resource + * whose fse_SegList is a fake seglist containing a 68k NOP+JMP gate + * to _start. The 68k gate does not carry the native register + * convention, so rt_Init stores ExecBase for _start to pick up. + * This mirrors the structure of the original CDFileSystem 53.4 + * kickstart module. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "handler.h" +#include "sys_compat.h" + +/* Interface globals normally provided by the C runtime startup. */ +struct ExecIFace *IExec; +struct DOSIFace *IDOS; + +int odfs_os4_handler_main(void); + +int32 _start(STRPTR args, int32 arglen, struct ExecBase *sysbase); + +int32 _start(STRPTR args, int32 arglen, struct ExecBase *sysbase) +{ + (void)args; + (void)arglen; + (void)sysbase; + + /* + * The handler process is entered through the kickstart 68k seglist + * gate (NOP + JMP), so register arguments are undefined here. Use + * the ExecBase rt_Init stored; fall back to the legacy pointer at + * absolute address 4 exactly like the original CDFileSystem module + * (only relevant if the entry ever runs without the resident init). + */ + if (!SysBase) + SysBase = *((struct ExecBase **)4L); + IExec = (struct ExecIFace *)SysBase->MainInterface; + +#if ODFS_SERIAL_DEBUG + DebugPrintF("[ODFS] _start: SysBase=%p IExec=%p\n", SysBase, IExec); +#endif + return odfs_os4_handler_main(); +} + +/* + * Fake seglist for the FileSysEntry. The BPTR points at .next; the + * longword before it conventionally holds the segment size. The + * "code" at BADDR+4 is a 68k NOP and JMP to the native _start; the + * 68k emulator switches to native execution at the jump target. + */ +static const struct { + uint32 size; + uint32 next; + uint16 nop; /* m68k NOP */ + uint16 jmp; /* m68k JMP absolute.l */ + int32 (*target)(STRPTR, int32, struct ExecBase *); + uint32 endmark; +} odfs_ks_seg = { + 0x40, + 0, + 0x4e71, + 0x4ef9, + _start, + 0xffffffff +}; + +static const char odfs_resident_name[] = "CDFileSystem"; +static const char odfs_resident_id[] = + "CDFileSystem 53.4 (ODFileSystem " ODFS_GIT_VERSION ")"; + +static APTR odfs_ks_init(APTR dummy1, APTR dummy2, struct ExecBase *sysbase) +{ + struct FileSysResource *fsr; + struct FileSysEntry *fse; + + (void)dummy1; + (void)dummy2; + + /* exec passes ExecBase as the third argument, like the original. */ + SysBase = sysbase; + IExec = (struct ExecIFace *)SysBase->MainInterface; + +#if ODFS_SERIAL_DEBUG + DebugPrintF("[ODFS] ks_init: SysBase=%p\n", SysBase); +#endif + + fsr = OpenResource((CONST_STRPTR)FSRNAME); +#if ODFS_SERIAL_DEBUG + DebugPrintF("[ODFS] ks_init: fsr=%p\n", fsr); +#endif + if (!fsr) + return NULL; + + /* + * Mirror the original module's plain AllocMem: AllocVecTags is not + * a safe assumption this early in coldstart, and the entry is + * never freed. + */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + fse = AllocMem(sizeof(*fse), MEMF_PUBLIC | MEMF_CLEAR); +#pragma GCC diagnostic pop +#if ODFS_SERIAL_DEBUG + DebugPrintF("[ODFS] ks_init: fse=%p\n", fse); +#endif + if (!fse) + return NULL; + + fse->fse_Node.ln_Name = (char *)odfs_resident_id; + fse->fse_DosType = 0x43443031; /* CD01 */ + fse->fse_Version = (54UL << 16); + fse->fse_PatchFlags = FSEF_SEGLIST | FSEF_GLOBVEC | FSEF_STACKSIZE; + fse->fse_StackSize = 16384; + fse->fse_Priority = 10; + fse->fse_SegList = MKBADDR(&odfs_ks_seg.next); + fse->fse_GlobalVec = -1; + + AddHead(&fsr->fsr_FileSysEntries, &fse->fse_Node); +#if ODFS_SERIAL_DEBUG + DebugPrintF("[ODFS] ks_init: CD01 entry registered, seg=%p\n", + (APTR)fse->fse_SegList); +#endif + return fse; +} + +/* + * Kickstart filesystem modules announce themselves with a Resident + * structure; exec runs rt_Init at coldstart, which registers the + * filesystem. Field values mirror the original CDFileSystem 53.4 + * resident (RTF_NATIVE | RTF_COLDSTART, priority 79). + */ +extern const struct Resident odfs_os4_resident; + +const struct Resident odfs_os4_resident = { + RTC_MATCHWORD, + (struct Resident *)&odfs_os4_resident, + (APTR)(&odfs_os4_resident + 1), + RTF_NATIVE | RTF_COLDSTART, + 53, + NT_UNKNOWN, + 79, + (char *)odfs_resident_name, + (char *)odfs_resident_id, + (APTR)odfs_ks_init +}; diff --git a/platform/amiga/os4/sys_compat.c b/platform/amiga/os4/sys_compat.c new file mode 100644 index 0000000..d0d79af --- /dev/null +++ b/platform/amiga/os4/sys_compat.c @@ -0,0 +1,232 @@ +/* + * sys_compat.c - AmigaOS 4 integration helpers + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "sys_compat.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +struct ExecBase *SysBase; +struct DosLibrary *DOSBase; +struct UtilityBase *UtilityBase; + +static struct DOSIFace *dos_iface; +static struct UtilityIFace *utility_iface; + +static odfs_amiga_interrupt_fn interrupt_code; + +/* + * V50+ interrupt entry. Soft interrupts fired through Cause() receive + * (0, SysBase, is_Data); interrupt servers receive (context, SysBase, + * userData). Both pass is_Data third, so one entry covers either path. + */ +static void odfs_amiga_interrupt_entry(int32 unused, + struct ExecBase *sysbase, + APTR data) +{ + (void)unused; + (void)sysbase; + + if (interrupt_code) + interrupt_code(data); +} + +void odfs_amiga_init_sysbase(void) +{ + /* + * _start establishes SysBase before any other handler code runs. + * As a fallback (e.g. if the entry path ever changes), recover it + * from the classic ExecBase pointer at absolute address 4, which + * the kickstart environment maintains. + */ + if (!SysBase) + SysBase = *((struct ExecBase **)4L); +} + +struct ExecBase *odfs_amiga_sysbase(void) +{ + return SysBase; +} + +struct DosLibrary *odfs_amiga_dosbase(void) +{ + return DOSBase; +} + +int odfs_amiga_open_libraries(void) +{ + DOSBase = (struct DosLibrary *)OpenLibrary((CONST_STRPTR)"dos.library", + 36); + if (!DOSBase) + return 0; + + dos_iface = (struct DOSIFace *)GetInterface((struct Library *)DOSBase, + (CONST_STRPTR)"main", 1, + NULL); + if (!dos_iface) { + CloseLibrary((struct Library *)DOSBase); + DOSBase = NULL; + return 0; + } + IDOS = dos_iface; + + UtilityBase = (struct UtilityBase *)OpenLibrary( + (CONST_STRPTR)"utility.library", 36); + if (UtilityBase) { + utility_iface = (struct UtilityIFace *)GetInterface( + (struct Library *)UtilityBase, (CONST_STRPTR)"main", 1, NULL); + if (!utility_iface) { + CloseLibrary((struct Library *)UtilityBase); + UtilityBase = NULL; + } + } + + return 1; +} + +void odfs_amiga_close_libraries(void) +{ + if (utility_iface) { + DropInterface((struct Interface *)utility_iface); + utility_iface = NULL; + } + if (UtilityBase) { + CloseLibrary((struct Library *)UtilityBase); + UtilityBase = NULL; + } + if (dos_iface) { + if (IDOS == dos_iface) + IDOS = NULL; + DropInterface((struct Interface *)dos_iface); + dos_iface = NULL; + } + if (DOSBase) { + CloseLibrary((struct Library *)DOSBase); + DOSBase = NULL; + } +} + +void *odfs_amiga_alloc_mem(ULONG size, ULONG flags) +{ + ULONG type; + + if (size == 0) + size = 1; + + /* + * AVT_Type only accepts MEMF_PRIVATE, MEMF_SHARED, and + * MEMF_EXECUTABLE. Translate the legacy flags the shared handler + * uses (MEMF_PUBLIC, de_BufMemType bits): handler memory is shared + * with DOS and other processes, so legacy MEMF_PUBLIC maps to + * MEMF_SHARED. + */ + type = (flags & MEMF_PRIVATE) ? MEMF_PRIVATE : MEMF_SHARED; + if (flags & MEMF_EXECUTABLE) + type |= MEMF_EXECUTABLE; + + if (flags & MEMF_CLEAR) { + return AllocVecTags(size, + AVT_Type, type, + AVT_ClearWithValue, 0, + TAG_END); + } + + return AllocVecTags(size, AVT_Type, type, TAG_END); +} + +void odfs_amiga_free_mem(void *ptr, ULONG size) +{ + (void)size; + if (ptr) + FreeVec(ptr); +} + +struct MsgPort *odfs_amiga_create_msg_port(void) +{ + return AllocSysObjectTags(ASOT_PORT, + ASOPORT_AllocSig, TRUE, + ASOPORT_Action, PA_SIGNAL, + ASOPORT_Target, FindTask(NULL), + TAG_END); +} + +void odfs_amiga_delete_msg_port(struct MsgPort *port) +{ + if (port) + FreeSysObject(ASOT_PORT, port); +} + +struct IORequest *odfs_amiga_create_io_request(struct MsgPort *port, + ULONG size) +{ + return AllocSysObjectTags(ASOT_IOREQUEST, + ASOIOR_Size, size, + ASOIOR_ReplyPort, port, + TAG_END); +} + +void odfs_amiga_delete_io_request(struct IORequest *req) +{ + if (req) + FreeSysObject(ASOT_IOREQUEST, req); +} + +LONG odfs_amiga_alloc_signal(LONG num) +{ + return AllocSignal(num); +} + +void odfs_amiga_free_signal(LONG num) +{ + if (num != -1) + FreeSignal(num); +} + +void *odfs_amiga_create_dos_entry(const char *name, LONG type) +{ + if (!name) + return NULL; + + return AllocDosObjectTags(DOS_DOSLIST, + ADO_Type, (ULONG)type, + ADO_Name, (ULONG)name, + TAG_END); +} + +void odfs_amiga_delete_dos_entry(void *node) +{ + if (node) + FreeDosObject(DOS_DOSLIST, node); +} + +void odfs_amiga_init_interrupt(struct Interrupt *intr, + const char *name, + APTR data, + odfs_amiga_interrupt_fn code) +{ + interrupt_code = code; + intr->is_Node.ln_Type = NT_INTERRUPT; + intr->is_Node.ln_Pri = 0; + intr->is_Node.ln_Name = (char *)name; + intr->is_Data = data; + intr->is_Code = (void (*)(void))(APTR)odfs_amiga_interrupt_entry; +} + +ULONG odfs_amiga_call_hook_pkt(struct Hook *hook, APTR object, APTR message) +{ + if (!utility_iface) + return 1; + + return utility_iface->CallHookPkt(hook, object, message); +} diff --git a/platform/amiga/os4/vector_port.c b/platform/amiga/os4/vector_port.c new file mode 100644 index 0000000..7291e95 --- /dev/null +++ b/platform/amiga/os4/vector_port.c @@ -0,0 +1,1229 @@ +/* + * vector_port.c - AmigaOS 4 filesystem vector-port frontend + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "vector_port.h" + +#include "handler.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#ifndef ODFS_GIT_VERSION +#define ODFS_GIT_VERSION "unknown" +#endif + +#define ODFS_OS4_FS_VERSION_NUMBER ((53UL << 16) | 4UL) +#define ODFS_OS4_MAX_FILE_SIZE 0x7fffffffffffffffULL + +static handler_global_t *vp_global(struct FSVP *vp); + +static void set_dos_error(int32 *res2, LONG err) +{ + if (res2) + *res2 = err; +} + +static handler_global_t *vp_require_global(struct FSVP *vp, int32 *res2) +{ + handler_global_t *g = vp_global(vp); + + if (!g) + set_dos_error(res2, ERROR_OBJECT_WRONG_TYPE); + return g; +} + +static void set_unsupported(struct FSVP *vp, int32 *res2) +{ + handler_global_t *g = vp_require_global(vp, res2); + + if (!g) + return; + + ODFS_TRACE(&g->log, ODFS_SUB_DOS, + "unsupported vector hit -> ACTION_NOT_KNOWN"); + set_dos_error(res2, ERROR_ACTION_NOT_KNOWN); +} + +static void set_write_protected(struct FSVP *vp, int32 *res2) +{ + handler_global_t *g = vp_require_global(vp, res2); + + if (!g) + return; + + ODFS_TRACE(&g->log, ODFS_SUB_DOS, "write-protected vector hit"); + set_dos_error(res2, ERROR_DISK_WRITE_PROTECTED); +} + +static handler_global_t *vp_global(struct FSVP *vp) +{ + return vp ? (handler_global_t *)vp->FSV.FSPrivate : NULL; +} + +/* + * Vector callbacks run in the calling process context, so every + * callback that touches handler state must hold the filesystem + * semaphore around the shared-operation call. + */ +static void fs_lock(handler_global_t *g) +{ + if (g) + ObtainSemaphore(&g->fs_sem); +} + +static void fs_unlock(handler_global_t *g) +{ + if (g) + ReleaseSemaphore(&g->fs_sem); +} + +static int32 return_dos_status(int32 *res2, LONG err) +{ + set_dos_error(res2, err); + return err == 0 ? DOSTRUE : DOSFALSE; +} + +static struct TagItem *next_filesystem_attr_tag(struct TagItem **taglist) +{ + struct TagItem *tag; + + if (!taglist) + return NULL; + + tag = *taglist; + while (tag) { + switch (tag->ti_Tag) { + case TAG_DONE: + *taglist = NULL; + return NULL; + case TAG_IGNORE: + tag++; + break; + case TAG_MORE: + tag = (struct TagItem *)tag->ti_Data; + break; + case TAG_SKIP: + tag += tag->ti_Data + 1; + break; + default: + *taglist = tag + 1; + return tag; + } + } + + *taglist = NULL; + return NULL; +} + +static uint32 filesystem_attr_version_buf_size(struct TagItem *taglist) +{ + struct TagItem *state = taglist; + struct TagItem *tag; + uint32 size = 0; + + while ((tag = next_filesystem_attr_tag(&state)) != NULL) { + if (tag->ti_Tag == FSA_VersionStringR_BufSize) + size = tag->ti_Data; + } + + return size; +} + +static void copy_filesystem_version_string(STRPTR buf, uint32 bufsize) +{ + static const char version[] = "ODFileSystem " ODFS_GIT_VERSION; + uint32 i = 0; + + if (!buf || bufsize == 0) + return; + + while (i + 1 < bufsize && version[i] != '\0') { + buf[i] = version[i]; + i++; + } + buf[i] = '\0'; +} + +static odfs_lock_t *lock_from_vector(struct Lock *lock) +{ + return (odfs_lock_t *)LOCK_FROM_PTR(lock); +} + +static odfs_fh_t *fh_from_vector(struct FileHandle *fh) +{ + return fh ? (odfs_fh_t *)fh->fh_Arg2 : NULL; +} + +static struct ExamineData *take_stale_examine_data( + struct PRIVATE_ExamineDirContext *ctx, + size_t name_len, + size_t comment_len) +{ + struct ExamineData *ed; + + if (!ctx) + return NULL; + + while (!IsMinListEmpty(&ctx->StaleNodeList)) { + ed = (struct ExamineData *)RemHead( + (struct List *)&ctx->StaleNodeList); + if (ed->NameSize >= name_len && + ed->CommentSize >= comment_len && + ed->LinkSize >= 1) + return ed; + + FreeDosObject(DOS_EXAMINEDATA, ed); + } + + return NULL; +} + +static struct ExamineData *alloc_examine_data_from_context( + handler_global_t *g, + const odfs_node_t *node, + struct PRIVATE_ExamineDirContext *ctx) +{ + const char *name; + const char *comment = ""; + odfs_handler_node_info_t info; + size_t name_len; + size_t comment_len; + struct ExamineData *ed; + + if (!node) + return NULL; + + odfs_handler_fill_node_info(g, node, &info); + name = info.name; + comment = info.comment; + + name_len = strlen(name) + 1u; + comment_len = strlen(comment) + 1u; + + ed = take_stale_examine_data(ctx, name_len, comment_len); + if (!ed) { + /* + * For the FSExamineDir() path the ExamineData entries must be + * bound to the context's memory pool via ADO_ExamineDir_Context, + * so DOS recycles and frees them through the context. For the + * FSExamineObj() path ctx is NULL, which the tag treats as "not + * specified" — the entry is then standalone and freed by the + * caller with FreeDosObject(). + */ + ed = AllocDosObjectTags(DOS_EXAMINEDATA, + ADO_ExamineData_NameSize, + (ULONG)name_len, + ADO_ExamineData_CommentSize, + (ULONG)comment_len, + ADO_ExamineData_LinkSize, + 1UL, + ADO_ExamineDir_Context, + (ULONG)ctx, + TAG_END); + } + if (!ed) + return NULL; + + ed->EXDinfo = 0; + ed->Type = info.is_dir ? FSO_TYPE_DIRECTORY : FSO_TYPE_FILE; + ed->FileSize = info.is_dir ? -1LL : (int64)info.size; + ed->Date = info.date; + ed->RefCount = 0; + ed->ObjectID = info.key; + ed->Protection = info.protection; + ed->OwnerUID = DOS_OWNER_NONE; + ed->OwnerGID = DOS_OWNER_NONE; + ed->FSPrivate = info.key; + + if (ed->Name && ed->NameSize > 0) { + strncpy(ed->Name, name, ed->NameSize - 1); + ed->Name[ed->NameSize - 1] = '\0'; + } + if (ed->Comment && ed->CommentSize > 0) { + strncpy(ed->Comment, comment, ed->CommentSize - 1); + ed->Comment[ed->CommentSize - 1] = '\0'; + } + if (ed->Link && ed->LinkSize > 0) + ed->Link[0] = '\0'; + + return ed; +} + +static struct ExamineData *alloc_examine_data(handler_global_t *g, + const odfs_node_t *node) +{ + return alloc_examine_data_from_context(g, node, NULL); +} + +static struct Lock *vp_lock(struct FSVP *vp, + int32 *res2, + struct Lock *rel_lock, + CONST_STRPTR obj, + int32 mode) +{ + handler_global_t *g = vp_require_global(vp, res2); + odfs_lock_t *ol = NULL; + LONG err; + + if (!g) + return NULL; + + fs_lock(g); + err = odfs_handler_lock_object(g, lock_from_vector(rel_lock), + obj ? obj : "", mode, &ol); + fs_unlock(g); + ODFS_TRACE(&g->log, ODFS_SUB_DOS, + "FSLock rel=%p obj='%s' mode=%ld -> ol=%p err=%ld", + rel_lock, obj ? (const char *)obj : "", (long)mode, ol, + (long)err); + set_dos_error(res2, err); + return (struct Lock *)LOCK_TO_PTR(ol); +} + +static int32 vp_unlock(struct FSVP *vp, int32 *res2, struct Lock *lock) +{ + handler_global_t *g = vp_require_global(vp, res2); + LONG err; + + if (!g) + return DOSFALSE; + + fs_lock(g); + err = odfs_handler_free_lock_object(g, lock_from_vector(lock)); + fs_unlock(g); + return return_dos_status(res2, err); +} + +static struct Lock *vp_dup_lock(struct FSVP *vp, + int32 *res2, + struct Lock *lock) +{ + handler_global_t *g = vp_require_global(vp, res2); + odfs_lock_t *ol = NULL; + LONG err; + + if (!g) + return NULL; + + fs_lock(g); + err = odfs_handler_dup_lock_object(g, lock_from_vector(lock), &ol); + fs_unlock(g); + ODFS_TRACE(&g->log, ODFS_SUB_DOS, + "FSDupLock lock=%p -> ol=%p err=%ld", lock, ol, (long)err); + set_dos_error(res2, err); + return (struct Lock *)LOCK_TO_PTR(ol); +} + +static struct Lock *vp_create_dir(struct FSVP *vp, + int32 *res2, + struct Lock *rel_lock, + CONST_STRPTR obj) +{ + set_write_protected(vp, res2); + (void)rel_lock; + (void)obj; + return NULL; +} + +static struct Lock *vp_parent_dir(struct FSVP *vp, + int32 *res2, + struct Lock *dirlock) +{ + handler_global_t *g = vp_require_global(vp, res2); + odfs_lock_t *parent = NULL; + LONG err; + + if (!g) + return NULL; + + fs_lock(g); + err = odfs_handler_parent_lock_object(g, lock_from_vector(dirlock), + &parent); + fs_unlock(g); + set_dos_error(res2, err); + return (struct Lock *)LOCK_TO_PTR(parent); +} + +static struct Lock *vp_dup_lock_from_fh(struct FSVP *vp, + int32 *res2, + struct FileHandle *filehandle) +{ + handler_global_t *g = vp_require_global(vp, res2); + odfs_lock_t *ol = NULL; + LONG err; + + if (!g) + return NULL; + + fs_lock(g); + err = odfs_handler_dup_lock_from_fh(g, fh_from_vector(filehandle), &ol); + fs_unlock(g); + set_dos_error(res2, err); + return (struct Lock *)LOCK_TO_PTR(ol); +} + +static int32 vp_open_from_lock(struct FSVP *vp, + int32 *res2, + struct FileHandle *file, + struct Lock *lock) +{ + handler_global_t *g = vp_require_global(vp, res2); + odfs_fh_t *odfs_fh = NULL; + LONG err; + + if (!g) + return DOSFALSE; + + fs_lock(g); + err = odfs_handler_open_from_lock_object(g, lock_from_vector(lock), + &odfs_fh); + fs_unlock(g); + if (err == 0 && file) { + file->fh_Arg1 = (BPTR)odfs_fh; + file->fh_Arg2 = odfs_fh; + } + return return_dos_status(res2, err); +} + +static struct Lock *vp_parent_of_fh(struct FSVP *vp, + int32 *res2, + struct FileHandle *file) +{ + handler_global_t *g = vp_require_global(vp, res2); + odfs_lock_t *parent = NULL; + LONG err; + + if (!g) + return NULL; + + fs_lock(g); + err = odfs_handler_parent_fh_object(g, fh_from_vector(file), &parent); + fs_unlock(g); + set_dos_error(res2, err); + return (struct Lock *)LOCK_TO_PTR(parent); +} + +static int32 vp_open(struct FSVP *vp, + int32 *res2, + struct FileHandle *fh, + struct Lock *rel_dir, + CONST_STRPTR obj, + int32 mode) +{ + handler_global_t *g = vp_require_global(vp, res2); + odfs_fh_t *odfs_fh = NULL; + LONG err; + + if (!g) + return DOSFALSE; + + fs_lock(g); + err = odfs_handler_open_object(g, lock_from_vector(rel_dir), + obj ? obj : "", mode, &odfs_fh); + fs_unlock(g); + ODFS_TRACE(&g->log, ODFS_SUB_DOS, + "FSOpen rel=%p obj='%s' mode=%ld -> err=%ld", + rel_dir, obj ? (const char *)obj : "", (long)mode, + (long)err); + if (err == 0 && fh) { + fh->fh_Arg1 = (BPTR)odfs_fh; + fh->fh_Arg2 = odfs_fh; + } + return return_dos_status(res2, err); +} + +static int32 vp_close(struct FSVP *vp, int32 *res2, struct FileHandle *file) +{ + handler_global_t *g = vp_require_global(vp, res2); + LONG err; + + if (!g) + return DOSFALSE; + + fs_lock(g); + err = odfs_handler_close_object(g, fh_from_vector(file)); + fs_unlock(g); + if (err == 0 && file) { + file->fh_Arg1 = 0; + file->fh_Arg2 = NULL; + } + return return_dos_status(res2, err); +} + +static int32 vp_delete(struct FSVP *vp, + int32 *res2, + struct Lock *rel_dirlock, + CONST_STRPTR obj) +{ + set_write_protected(vp, res2); + (void)rel_dirlock; + (void)obj; + return DOSFALSE; +} + +static int32 vp_read(struct FSVP *vp, + int32 *res2, + struct FileHandle *file, + STRPTR buffer, + int32 numbytes) +{ + handler_global_t *g = vp_require_global(vp, res2); + LONG actual = 0; + LONG err; + + if (!g) + return -1; + + fs_lock(g); + err = odfs_handler_read_object(g, fh_from_vector(file), + buffer, numbytes, &actual); + fs_unlock(g); + set_dos_error(res2, err); + return err == 0 ? actual : -1; +} + +static int32 vp_write(struct FSVP *vp, + int32 *res2, + struct FileHandle *file, + STRPTR buffer, + int32 numbytes) +{ + set_write_protected(vp, res2); + (void)file; + (void)buffer; + (void)numbytes; + return -1; +} + +static int32 vp_flush(struct FSVP *vp, int32 *res2) +{ + handler_global_t *g = vp_require_global(vp, res2); + + if (!g) + return DOSFALSE; + + set_dos_error(res2, 0); + return DOSTRUE; +} + +static int32 vp_change_file_position(struct FSVP *vp, + int32 *res2, + struct FileHandle *file, + int32 mode, + int64 position) +{ + handler_global_t *g = vp_require_global(vp, res2); + int64_t oldpos; + LONG err; + + if (!g) + return DOSFALSE; + + fs_lock(g); + err = odfs_handler_seek_object(g, fh_from_vector(file), + position, mode, &oldpos); + fs_unlock(g); + return return_dos_status(res2, err); +} + +static int32 vp_change_file_size(struct FSVP *vp, + int32 *res2, + struct FileHandle *file, + int32 mode, + int64 size) +{ + set_write_protected(vp, res2); + (void)file; + (void)mode; + (void)size; + return DOSFALSE; +} + +static int64 vp_get_file_position(struct FSVP *vp, + int32 *res2, + struct FileHandle *file) +{ + handler_global_t *g = vp_require_global(vp, res2); + int64_t pos; + LONG err; + + if (!g) + return -1; + + fs_lock(g); + err = odfs_handler_get_file_position(g, fh_from_vector(file), &pos); + fs_unlock(g); + set_dos_error(res2, err); + return err == 0 ? pos : -1; +} + +static int64 vp_get_file_size(struct FSVP *vp, + int32 *res2, + struct FileHandle *file) +{ + handler_global_t *g = vp_require_global(vp, res2); + int64_t size; + LONG err; + + if (!g) + return -1; + + fs_lock(g); + err = odfs_handler_get_file_size(g, fh_from_vector(file), &size); + fs_unlock(g); + set_dos_error(res2, err); + return err == 0 ? size : -1; +} + +static int32 vp_change_lock_mode(struct FSVP *vp, + int32 *res2, + struct Lock *lock, + int32 new_lock_mode) +{ + handler_global_t *g = vp_require_global(vp, res2); + LONG err; + + if (!g) + return DOSFALSE; + + fs_lock(g); + err = odfs_handler_change_lock_mode(g, lock_from_vector(lock), + new_lock_mode); + fs_unlock(g); + return return_dos_status(res2, err); +} + +static int32 vp_change_file_mode(struct FSVP *vp, + int32 *res2, + struct FileHandle *fh, + int32 new_lock_mode) +{ + handler_global_t *g = vp_require_global(vp, res2); + LONG err; + + if (!g) + return DOSFALSE; + + fs_lock(g); + err = odfs_handler_change_file_mode(g, fh_from_vector(fh), + new_lock_mode); + fs_unlock(g); + return return_dos_status(res2, err); +} + +static int32 vp_set_date(struct FSVP *vp, + int32 *res2, + struct Lock *rel_dirlock, + CONST_STRPTR name, + const struct DateStamp *ds) +{ + set_write_protected(vp, res2); + (void)rel_dirlock; + (void)name; + (void)ds; + return DOSFALSE; +} + +static int32 vp_set_protection(struct FSVP *vp, + int32 *res2, + struct Lock *rel_dirlock, + CONST_STRPTR name, + uint32 mask) +{ + set_write_protected(vp, res2); + (void)rel_dirlock; + (void)name; + (void)mask; + return DOSFALSE; +} + +static int32 vp_set_comment(struct FSVP *vp, + int32 *res2, + struct Lock *rel_dirlock, + CONST_STRPTR name, + CONST_STRPTR comment) +{ + set_write_protected(vp, res2); + (void)rel_dirlock; + (void)name; + (void)comment; + return DOSFALSE; +} + +static int32 vp_set_group(struct FSVP *vp, + int32 *res2, + struct Lock *rel_dirlock, + CONST_STRPTR name, + uint32 group) +{ + set_write_protected(vp, res2); + (void)rel_dirlock; + (void)name; + (void)group; + return DOSFALSE; +} + +static int32 vp_set_user(struct FSVP *vp, + int32 *res2, + struct Lock *rel_dirlock, + CONST_STRPTR name, + uint32 user) +{ + set_write_protected(vp, res2); + (void)rel_dirlock; + (void)name; + (void)user; + return DOSFALSE; +} + +static int32 vp_rename(struct FSVP *vp, + int32 *res2, + struct Lock *src_rel, + CONST_STRPTR src, + struct Lock *dst_rel, + CONST_STRPTR dst) +{ + set_write_protected(vp, res2); + (void)src_rel; + (void)src; + (void)dst_rel; + (void)dst; + return DOSFALSE; +} + +static int32 vp_create_soft_link(struct FSVP *vp, + int32 *res2, + struct Lock *rel_dirlock, + CONST_STRPTR linkname, + CONST_STRPTR dest_obj) +{ + set_write_protected(vp, res2); + (void)rel_dirlock; + (void)linkname; + (void)dest_obj; + return DOSFALSE; +} + +static int32 vp_create_hard_link(struct FSVP *vp, + int32 *res2, + struct Lock *rel_dirlock, + CONST_STRPTR linkname, + struct Lock *dest_obj) +{ + set_write_protected(vp, res2); + (void)rel_dirlock; + (void)linkname; + (void)dest_obj; + return DOSFALSE; +} + +static int32 vp_read_soft_link(struct FSVP *vp, + int32 *res2, + struct Lock *rel_dir, + CONST_STRPTR linkname, + STRPTR buf, + int32 bufsize) +{ + set_unsupported(vp, res2); + (void)rel_dir; + (void)linkname; + (void)buf; + (void)bufsize; + return DOSFALSE; +} + +static int32 vp_same_lock(struct FSVP *vp, + int32 *res2, + struct Lock *lock1, + struct Lock *lock2) +{ + handler_global_t *g = vp_require_global(vp, res2); + LONG same = LOCK_DIFFERENT; + LONG err; + + if (!g) + return DOSFALSE; + + fs_lock(g); + err = odfs_handler_same_lock_object(g, + lock_from_vector(lock1), + lock_from_vector(lock2), + &same); + fs_unlock(g); + set_dos_error(res2, err); + return (err == 0 && same == LOCK_SAME) ? DOSTRUE : DOSFALSE; +} + +static int32 vp_same_file(struct FSVP *vp, + int32 *res2, + struct FileHandle *fh1, + struct FileHandle *fh2) +{ + handler_global_t *g = vp_require_global(vp, res2); + LONG same = LOCK_DIFFERENT; + LONG err; + + if (!g) + return DOSFALSE; + + fs_lock(g); + err = odfs_handler_same_file_object(g, + fh_from_vector(fh1), + fh_from_vector(fh2), + &same); + fs_unlock(g); + set_dos_error(res2, err); + return (err == 0 && same == FH_SAME) ? DOSTRUE : DOSFALSE; +} + +static int32 vp_filesystem_attr(struct FSVP *vp, + int32 *res2, + struct TagItem *taglist) +{ + handler_global_t *g = vp_require_global(vp, res2); + struct TagItem *state = taglist; + struct TagItem *tag; + uint32 version_buf_size; + + if (!g) + return DOSFALSE; + + ODFS_TRACE(&g->log, ODFS_SUB_DOS, "FSFileSystemAttr taglist=%p", + taglist); + + version_buf_size = filesystem_attr_version_buf_size(taglist); + + while ((tag = next_filesystem_attr_tag(&state)) != NULL) { + switch (tag->ti_Tag) { + case FSA_StringNameInput: + case FSA_FileHandleInput: + case FSA_LockInput: + case FSA_MsgPortInput: + case FSA_ShowRequesters: + case FSA_VersionStringR_BufSize: + break; + + case FSA_MaxFileNameLengthR: + if (!tag->ti_Data) + return return_dos_status(res2, ERROR_REQUIRED_ARG_MISSING); + *(uint32 *)tag->ti_Data = MAX_VP_FILENAME; + break; + + case FSA_VersionNumberR: + if (!tag->ti_Data) + return return_dos_status(res2, ERROR_REQUIRED_ARG_MISSING); + *(uint32 *)tag->ti_Data = ODFS_OS4_FS_VERSION_NUMBER; + break; + + case FSA_DOSTypeR: + if (!tag->ti_Data) + return return_dos_status(res2, ERROR_REQUIRED_ARG_MISSING); + *(uint32 *)tag->ti_Data = ODFS_OS4_CD_DOSTYPE; + break; + + case FSA_ActivityFlushTimeoutR: + case FSA_InactivityFlushTimeoutR: + case FSA_MaxRecycledEntriesR: + if (!tag->ti_Data) + return return_dos_status(res2, ERROR_REQUIRED_ARG_MISSING); + *(uint32 *)tag->ti_Data = 0; + break; + + case FSA_HasRecycledEntriesR: + if (!tag->ti_Data) + return return_dos_status(res2, ERROR_REQUIRED_ARG_MISSING); + *(int32 *)tag->ti_Data = DOSFALSE; + break; + + case FSA_VersionStringR: + if (!tag->ti_Data || version_buf_size == 0) + return return_dos_status(res2, ERROR_REQUIRED_ARG_MISSING); + copy_filesystem_version_string((STRPTR)tag->ti_Data, + version_buf_size); + break; + + case FSA_MaxFileSizeR: + if (!tag->ti_Data) + return return_dos_status(res2, ERROR_REQUIRED_ARG_MISSING); + *(uint64_t *)tag->ti_Data = ODFS_OS4_MAX_FILE_SIZE; + break; + + case FSA_MaxFileNameLengthW: + case FSA_ActivityFlushTimeoutW: + case FSA_InactivityFlushTimeoutW: + case FSA_MaxRecycledEntriesW: + return return_dos_status(res2, ERROR_DISK_WRITE_PROTECTED); + + default: + return return_dos_status(res2, ERROR_ACTION_NOT_KNOWN); + } + } + + return return_dos_status(res2, 0); +} + +static int32 vp_volume_info_data(struct FSVP *vp, + int32 *res2, + struct InfoData *info) +{ + handler_global_t *g = vp_require_global(vp, res2); + LONG err; + + if (!g) + return DOSFALSE; + + fs_lock(g); + err = odfs_handler_fill_info(g, NULL, info); + fs_unlock(g); + return return_dos_status(res2, err); +} + +static int32 vp_device_info_data(struct FSVP *vp, + int32 *res2, + struct InfoData *info) +{ + handler_global_t *g = vp_require_global(vp, res2); + LONG err; + + if (!g) + return DOSFALSE; + + fs_lock(g); + err = odfs_handler_fill_info(g, NULL, info); + fs_unlock(g); + return return_dos_status(res2, err); +} + +static struct ExamineData *vp_examine_obj(struct FSVP *vp, + int32 *res2, + struct Lock *lock, + CONST_STRPTR object) +{ + handler_global_t *g = vp_require_global(vp, res2); + odfs_node_t node; + odfs_node_t parent; + struct ExamineData *ed; + LONG err; + + if (!g) + return NULL; + + fs_lock(g); + err = odfs_handler_resolve_object_node(g, lock_from_vector(lock), + object ? object : "", &node, + &parent); + ODFS_TRACE(&g->log, ODFS_SUB_DOS, + "FSExamineObj lock=%p obj='%s' -> err=%ld", + lock, object ? (const char *)object : "", (long)err); + if (err != 0) { + fs_unlock(g); + set_dos_error(res2, err); + return NULL; + } + + ed = alloc_examine_data(g, &node); + fs_unlock(g); + set_dos_error(res2, ed ? 0 : ERROR_NO_FREE_STORE); + return ed; +} + +static struct ExamineData *vp_examine_lock(struct FSVP *vp, + int32 *res2, + struct Lock *lock) +{ + handler_global_t *g = vp_require_global(vp, res2); + const odfs_node_t *node = NULL; + struct ExamineData *ed; + LONG err; + + if (!g) + return NULL; + + fs_lock(g); + err = odfs_handler_get_lock_node(g, lock_from_vector(lock), &node); + ODFS_TRACE(&g->log, ODFS_SUB_DOS, + "FSExamineLock lock=%p -> err=%ld name='%s'", + lock, (long)err, (err == 0 && node) ? node->name : ""); + if (err != 0) { + fs_unlock(g); + set_dos_error(res2, err); + return NULL; + } + + ed = alloc_examine_data(g, node); + fs_unlock(g); + set_dos_error(res2, ed ? 0 : ERROR_NO_FREE_STORE); + return ed; +} + +static struct ExamineData *vp_examine_file(struct FSVP *vp, + int32 *res2, + struct FileHandle *file) +{ + handler_global_t *g = vp_require_global(vp, res2); + const odfs_node_t *node = NULL; + struct ExamineData *ed; + LONG err; + + if (!g) + return NULL; + + fs_lock(g); + err = odfs_handler_get_fh_node(g, fh_from_vector(file), &node); + if (err != 0) { + fs_unlock(g); + set_dos_error(res2, err); + return NULL; + } + + ed = alloc_examine_data(g, node); + fs_unlock(g); + set_dos_error(res2, ed ? 0 : ERROR_NO_FREE_STORE); + return ed; +} + +static int32 vp_examine_dir(struct FSVP *vp, + int32 *res2, + struct PRIVATE_ExamineDirContext *ctx) +{ + handler_global_t *g = vp_require_global(vp, res2); + odfs_node_t entry; + ULONG key = 0; + struct ExamineData *ed; + LONG err; + + if (!g) + return DOSFALSE; + + if (!ctx) + return return_dos_status(res2, ERROR_REQUIRED_ARG_MISSING); + + fs_lock(g); + err = odfs_handler_next_dir_entry(g, lock_from_vector(ctx->ReferenceLock), + ctx->FSPrivate[0], &entry, &key); + ODFS_TRACE(&g->log, ODFS_SUB_DOS, + "FSExamineDir ctx=%p refLock=%p prevKey=%lx -> " + "err=%ld key=%lx name='%s'", + ctx, ctx->ReferenceLock, + (unsigned long)ctx->FSPrivate[0], + (long)err, (unsigned long)key, + (err == 0) ? entry.name : ""); + if (err != 0) { + fs_unlock(g); + return return_dos_status(res2, err); + } + + ed = alloc_examine_data_from_context(g, &entry, ctx); + fs_unlock(g); + if (!ed) + return return_dos_status(res2, ERROR_NO_FREE_STORE); + + AddTail((struct List *)&ctx->FreshNodeList, (struct Node *)&ed->EXDnode); + ctx->FSPrivate[0] = key; + return return_dos_status(res2, 0); +} + +static int32 vp_inhibit(struct FSVP *vp, int32 *res2, int32 inhibit_state) +{ + handler_global_t *g = vp_require_global(vp, res2); + LONG err; + + if (!g) + return DOSFALSE; + + fs_lock(g); + err = odfs_handler_inhibit(g, inhibit_state); + fs_unlock(g); + return return_dos_status(res2, err); +} + +static int32 vp_write_protect(struct FSVP *vp, + int32 *res2, + int32 wp_state, + uint32 passkey) +{ + set_write_protected(vp, res2); + (void)wp_state; + (void)passkey; + return DOSFALSE; +} + +static int32 vp_format(struct FSVP *vp, + int32 *res2, + CONST_STRPTR new_volname, + uint32 dostype, + uint32 spare) +{ + set_write_protected(vp, res2); + (void)new_volname; + (void)dostype; + (void)spare; + return DOSFALSE; +} + +static int32 vp_serialize(struct FSVP *vp, int32 *res2) +{ + handler_global_t *g = vp_require_global(vp, res2); + + if (!g) + return DOSFALSE; + + set_dos_error(res2, 0); + return DOSTRUE; +} + +static int32 vp_relabel(struct FSVP *vp, + int32 *res2, + CONST_STRPTR new_volumename) +{ + set_write_protected(vp, res2); + (void)new_volumename; + return DOSFALSE; +} + +static int32 vp_add_notify(struct FSVP *vp, + int32 *res2, + struct NotifyRequest *nr) +{ + set_unsupported(vp, res2); + (void)nr; + return DOSFALSE; +} + +static int32 vp_remove_notify(struct FSVP *vp, + int32 *res2, + struct NotifyRequest *nr) +{ + set_unsupported(vp, res2); + (void)nr; + return DOSFALSE; +} + +static int32 vp_lock_record(struct FSVP *vp, + int32 *res2, + struct FileHandle *file, + int64 offset, + int64 length, + uint32 mode, + uint32 timeout) +{ + set_unsupported(vp, res2); + (void)file; + (void)offset; + (void)length; + (void)mode; + (void)timeout; + return DOSFALSE; +} + +static int32 vp_unlock_record(struct FSVP *vp, + int32 *res2, + struct FileHandle *file, + int64 offset, + int64 length) +{ + set_unsupported(vp, res2); + (void)file; + (void)offset; + (void)length; + return DOSFALSE; +} + +static const struct FileSystemVectors odfs_os4_vectors = { + .StructSize = sizeof(struct FileSystemVectors), + .Version = FS_VECTORPORT_VERSION, + .FSPrivate = NULL, + .Reserved = {0, 0, 0}, + .DOSPrivate = NULL, + .DOSEmulatePacket = NULL, + .FSLock = vp_lock, + .FSUnLock = vp_unlock, + .FSDupLock = vp_dup_lock, + .FSCreateDir = vp_create_dir, + .FSParentDir = vp_parent_dir, + .FSDupLockFromFH = vp_dup_lock_from_fh, + .FSOpenFromLock = vp_open_from_lock, + .FSParentOfFH = vp_parent_of_fh, + .FSOpen = vp_open, + .FSClose = vp_close, + .FSDelete = vp_delete, + .FSRead = vp_read, + .FSWrite = vp_write, + .FSFlush = vp_flush, + .FSChangeFilePosition = vp_change_file_position, + .FSChangeFileSize = vp_change_file_size, + .FSGetFilePosition = vp_get_file_position, + .FSGetFileSize = vp_get_file_size, + .FSChangeLockMode = vp_change_lock_mode, + .FSChangeFileMode = vp_change_file_mode, + .FSSetDate = vp_set_date, + .FSSetProtection = vp_set_protection, + .FSSetComment = vp_set_comment, + .FSSetGroup = vp_set_group, + .FSSetUser = vp_set_user, + .FSRename = vp_rename, + .FSCreateSoftLink = vp_create_soft_link, + .FSCreateHardLink = vp_create_hard_link, + .FSReadSoftLink = vp_read_soft_link, + .FSSameLock = vp_same_lock, + .FSSameFile = vp_same_file, + .FSFileSystemAttr = vp_filesystem_attr, + .FSVolumeInfoData = vp_volume_info_data, + .FSDeviceInfoData = vp_device_info_data, + .FSReserved1 = NULL, + .FSExamineObj = vp_examine_obj, + .FSExamineLock = vp_examine_lock, + .FSExamineFile = vp_examine_file, + .FSExamineDir = vp_examine_dir, + .FSInhibit = vp_inhibit, + .FSWriteProtect = vp_write_protect, + .FSFormat = vp_format, + .FSSerialize = vp_serialize, + .FSRelabel = vp_relabel, + .FSReserved3 = NULL, + .FSAddNotify = vp_add_notify, + .FSRemoveNotify = vp_remove_notify, + .FSLockRecord = vp_lock_record, + .FSUnLockRecord = vp_unlock_record, + .End_Marker = -1 +}; + +struct FileSystemVectorPort *odfs_os4_alloc_vector_port(APTR fs_private) +{ + struct FileSystemVectorPort *vp; + + vp = AllocDosObjectTags(DOS_FSVECTORPORT, + ADO_Vectors, + (ULONG)&odfs_os4_vectors, + TAG_END); + if (!vp) + return NULL; + + vp->MP.mp_Node.ln_Type = NT_FILESYSTEM; + vp->FSV.FSPrivate = fs_private; + return vp; +} + +void odfs_os4_free_vector_port(struct FileSystemVectorPort *vp) +{ + if (vp) + FreeDosObject(DOS_FSVECTORPORT, vp); +} + +void odfs_os4_invalidate_vector_port(struct FileSystemVectorPort *vp) +{ + if (vp) + vp->FSV.Version = 0; +} diff --git a/platform/amiga/os4/vector_port.h b/platform/amiga/os4/vector_port.h new file mode 100644 index 0000000..edf27d2 --- /dev/null +++ b/platform/amiga/os4/vector_port.h @@ -0,0 +1,24 @@ +/* + * vector_port.h - AmigaOS 4 filesystem vector-port frontend + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef ODFS_AMIGA_OS4_VECTOR_PORT_H +#define ODFS_AMIGA_OS4_VECTOR_PORT_H + +#include +#include + +struct DosPacket; + +struct FileSystemVectorPort *odfs_os4_alloc_vector_port(APTR fs_private); +void odfs_os4_free_vector_port(struct FileSystemVectorPort *vp); + +/* + * Stop dos.library from vectoring new callers (sets the vector version + * to zero). Must be called before DOS-visible shutdown teardown. + */ +void odfs_os4_invalidate_vector_port(struct FileSystemVectorPort *vp); + +#endif /* ODFS_AMIGA_OS4_VECTOR_PORT_H */ diff --git a/tests/integration/check_fh_packets.py b/tests/integration/check_fh_packets.py index 0b1981e..005ab9b 100644 --- a/tests/integration/check_fh_packets.py +++ b/tests/integration/check_fh_packets.py @@ -14,8 +14,11 @@ ACTION_PARENT_FH = 1031 ACTION_EXAMINE_FH = 1034 ACTION_EXAMINE_OBJECT = 23 +ACTION_SAME_LOCK = 40 ACTION_PARENT = 29 FILE_LOCK_SIZE = FileLockStruct.get_size() +LOCK_SAME = 0 +LOCK_SAME_VOLUME = 1 def fib_name(bridge: HandlerBridge, fib_addr: int) -> str: @@ -152,6 +155,25 @@ def main() -> int: ) return 1 + res1, res2 = send_and_wait( + bridge, ACTION_SAME_LOCK, [dup_lock, dup_lock] + ) + if res1 != -1 or res2 != LOCK_SAME: + print( + f"FAIL: SAME_LOCK same object returned ({res1}, {res2})" + ) + return 1 + + res1, res2 = send_and_wait( + bridge, ACTION_SAME_LOCK, [dup_lock, parent_lock] + ) + if res1 != 0 or res2 != LOCK_SAME_VOLUME: + print( + "FAIL: SAME_LOCK same-volume objects returned " + f"({res1}, {res2})" + ) + return 1 + res1, res2 = send_and_wait(bridge, ACTION_PARENT, [dup_lock]) if res1 in (None, 0): print( diff --git a/tests/unit/test_cache.c b/tests/unit/test_cache.c index 8e20f17..b2a2a17 100644 --- a/tests/unit/test_cache.c +++ b/tests/unit/test_cache.c @@ -55,6 +55,45 @@ static void make_mock_media(odfs_media_t *m, int *read_count) m->ctx = read_count; } +typedef struct stream_read_counts { + int calls; + int sectors; +} stream_read_counts_t; + +static odfs_err_t stream_mock_read_sectors(void *ctx, uint32_t lba, + uint32_t count, void *buf) +{ + stream_read_counts_t *reads = ctx; + uint8_t *out = buf; + + if (reads) { + reads->calls++; + reads->sectors += (int)count; + } + + for (uint32_t s = 0; s < count; s++) { + uint8_t fill = (uint8_t)((lba + s) & 0xFF); + for (uint32_t i = 0; i < MOCK_SECTOR_SIZE; i++) + out[s * MOCK_SECTOR_SIZE + i] = fill; + } + + return ODFS_OK; +} + +static const odfs_media_ops_t stream_mock_ops = { + .read_sectors = stream_mock_read_sectors, + .sector_size = mock_sector_size, + .sector_count = mock_sector_count, + .read_toc = NULL, + .close = NULL, +}; + +static void make_stream_media(odfs_media_t *m, stream_read_counts_t *reads) +{ + m->ops = &stream_mock_ops; + m->ctx = reads; +} + TEST(cache_init_destroy) { odfs_cache_t cache; @@ -187,4 +226,47 @@ TEST(cache_stats_tracking) odfs_cache_destroy(&cache); } +TEST(cache_read_bytes_batches_aligned_runs) +{ + odfs_cache_t cache; + odfs_media_t media; + stream_read_counts_t reads = {0, 0}; + uint8_t buf[MOCK_SECTOR_SIZE * 6]; + size_t len = sizeof(buf); + + make_stream_media(&media, &reads); + ASSERT_OK(odfs_cache_init(&cache, &media, 4)); + + ASSERT_OK(odfs_cache_read_bytes(&cache, 10, 0, buf, &len)); + ASSERT_EQ(len, sizeof(buf)); + ASSERT_EQ(reads.calls, 1); + ASSERT_EQ(reads.sectors, 6); + ASSERT_EQ(buf[0], 10); + ASSERT_EQ(buf[MOCK_SECTOR_SIZE * 5], 15); + + odfs_cache_destroy(&cache); +} + +TEST(cache_read_bytes_caches_unaligned_edges) +{ + odfs_cache_t cache; + odfs_media_t media; + stream_read_counts_t reads = {0, 0}; + uint8_t buf[MOCK_SECTOR_SIZE * 3]; + size_t len = sizeof(buf); + + make_stream_media(&media, &reads); + ASSERT_OK(odfs_cache_init(&cache, &media, 4)); + + ASSERT_OK(odfs_cache_read_bytes(&cache, 20, 100, buf, &len)); + ASSERT_EQ(len, sizeof(buf)); + ASSERT_EQ(reads.calls, 3); + ASSERT_EQ(reads.sectors, 4); + ASSERT_EQ(buf[0], 20); + ASSERT_EQ(buf[MOCK_SECTOR_SIZE - 100], 21); + ASSERT_EQ(buf[len - 1], 23); + + odfs_cache_destroy(&cache); +} + TEST_MAIN()