From 1da639dfa54e5e8de88eb529a4e4135d01bb5cf2 Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Thu, 21 May 2026 12:46:02 -0700 Subject: [PATCH 01/36] amiga: split OS compatibility layer Start separating Amiga-family OS integration from the packet handler so AmigaOS 4 can grow a native frontend without cloning the OS3 handler. Select an os3 or os4 platform subdirectory from the Makefile and add shared sys_compat declarations. Route allocation, message ports, I/O requests, signals, interrupt setup, library/interface ownership, and utility hook calls through per-OS implementations. Keep -lauto out of the OS4 link. Explicitly open dos.library, obtain IDOS, open utility.library, and obtain its main interface. Move the temporary OS4 main wrapper into the OS4 frontend directory and document the remaining OS4 porting work. --- Makefile | 126 +++++++++++++++----- include/odfs/alloc.h | 10 +- platform/amiga/common/sys_compat.h | 57 +++++++++ platform/amiga/handler.h | 3 + platform/amiga/handler_main.c | 142 +++++++++++----------- platform/amiga/os3/sys_compat.c | 129 ++++++++++++++++++++ platform/amiga/os4/main.c | 13 ++ platform/amiga/os4/sys_compat.c | 185 +++++++++++++++++++++++++++++ 8 files changed, 559 insertions(+), 106 deletions(-) create mode 100644 platform/amiga/common/sys_compat.h create mode 100644 platform/amiga/os3/sys_compat.c create mode 100644 platform/amiga/os4/main.c create mode 100644 platform/amiga/os4/sys_compat.c diff --git a/Makefile b/Makefile index d5bde13..f6cc7e1 100644 --- a/Makefile +++ b/Makefile @@ -5,17 +5,43 @@ # ---- toolchain selection ---- -# Amiga cross-compiler (m68k-amigaos or m68k-aros-gcc) -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) +# Amiga cross-compiler. +# Override with CC=ppc-amigaos-gcc for an AmigaOS 4 PPC build. +CC ?= m68k-amigaos-gcc # 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 +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 +51,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,7 +84,11 @@ 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 +endif ROM_SIZE_LIMIT ?= 32768 SIZE_LIMIT_NAME ?= AMIGA_SIZE_LIMIT SIZE_LIMIT_DESC ?= release Amiga handler @@ -85,26 +120,46 @@ 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 +AMIGA_SYSFLAGS ?= -mcrt=$(AMIGA_CRT) +AMIGA_WARNFLAGS = -Wno-error=deprecated-declarations +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 +HANDLER_LDFLAGS = +HANDLER_LIBS = $(LIBS) 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 +194,20 @@ 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 +else +AMIGA_SRCS += platform/amiga/libc_stubs.c +endif # 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) @@ -258,7 +322,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 +426,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 +447,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,7 +461,7 @@ $(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) $@ diff --git a/include/odfs/alloc.h b/include/odfs/alloc.h index e2ee590..d3faca1 100644 --- a/include/odfs/alloc.h +++ b/include/odfs/alloc.h @@ -13,14 +13,13 @@ #if ODFS_PLATFORM_AMIGA -#include -#include +#include "sys_compat.h" static inline void *odfs_malloc(size_t size) { if (size == 0) size = 1; - return AllocVec((ULONG)size, MEMF_PUBLIC); + return odfs_amiga_alloc_vec((ULONG)size, MEMF_PUBLIC); } static inline void *odfs_calloc(size_t count, size_t size) @@ -34,13 +33,12 @@ static inline void *odfs_calloc(size_t count, size_t size) if (total == 0) total = 1; - return AllocVec((ULONG)total, MEMF_PUBLIC | MEMF_CLEAR); + return odfs_amiga_alloc_vec((ULONG)total, MEMF_PUBLIC | MEMF_CLEAR); } static inline void odfs_free(void *ptr) { - if (ptr) - FreeVec(ptr); + odfs_amiga_free_vec(ptr); } #else diff --git a/platform/amiga/common/sys_compat.h b/platform/amiga/common/sys_compat.h new file mode 100644 index 0000000..85eda5e --- /dev/null +++ b/platform/amiga/common/sys_compat.h @@ -0,0 +1,57 @@ +/* + * 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 + +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); +void *odfs_amiga_alloc_vec(ULONG size, ULONG flags); +void odfs_amiga_free_vec(void *ptr); + +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); + +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..7b8e4cc 100644 --- a/platform/amiga/handler.h +++ b/platform/amiga/handler.h @@ -14,6 +14,9 @@ #include #include #include +#ifdef __amigaos4__ +#include +#endif #include "aros_compat.h" #include "odfs/api.h" diff --git a/platform/amiga/handler_main.c b/platform/amiga/handler_main.c index 703ada0..a45b075 100644 --- a/platform/amiga/handler_main.c +++ b/platform/amiga/handler_main.c @@ -9,6 +9,7 @@ */ #include "handler.h" +#include "sys_compat.h" #if ODFS_FEATURE_CDDA #include "cdda/cdda.h" @@ -25,7 +26,6 @@ #include #include -#include #include @@ -42,11 +42,6 @@ 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); @@ -107,8 +102,16 @@ 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_handler( +#ifdef __amigaos4__ + APTR data +#else + APTR data asm("a1") +#endif +) { + odfs_changeint_data_t *ci = data; + if (ci && ci->task && ci->sigmask) Signal(ci->task, ci->sigmask); return 0; @@ -173,13 +176,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,8 +198,8 @@ 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); } static odfs_err_t amiga_read_sectors(void *ctx, uint32_t lba, @@ -953,7 +956,7 @@ static odfs_entry_t *alloc_entry(odfs_volume_t *volume, { 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; @@ -979,7 +982,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) @@ -1043,7 +1046,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; @@ -1199,7 +1202,7 @@ 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 drain_all_objects(handler_global_t *g) @@ -1210,14 +1213,14 @@ 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; release_volume_object(g, ol->entry->volume); release_entry(ol->entry); - FreeMem(ol, sizeof(*ol)); + odfs_amiga_free_mem(ol, sizeof(*ol)); } } @@ -1265,7 +1268,7 @@ static odfs_lock_t *alloc_lock(handler_global_t *g, 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; @@ -1295,7 +1298,7 @@ static void free_lock(handler_global_t *g, odfs_lock_t *ol) rebuild_volume_locklist(g, ol->entry->volume); 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) @@ -1305,7 +1308,7 @@ static odfs_lock_t *dup_lock(handler_global_t *g, odfs_lock_t *src) 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; @@ -1335,7 +1338,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 +1357,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)); } /* ------------------------------------------------------------------ */ @@ -1481,7 +1484,9 @@ static void fill_fib(struct FileInfoBlock *fib, const odfs_node_t *fnode) } fib->fib_DirEntryType = (fnode->kind == ODFS_NODE_DIR) ? ST_USERDIR : ST_FILE; +#ifndef __amigaos4__ fib->fib_EntryType = fib->fib_DirEntryType; +#endif fib->fib_Size = (LONG)fnode->size; fib->fib_NumBlocks = (fnode->size + 511) / 512; @@ -1588,7 +1593,9 @@ static void fill_root_fib(handler_global_t *g, struct FileInfoBlock *fib, fill_fib(fib, fnode); fib->fib_DirEntryType = ST_ROOT; +#ifndef __amigaos4__ fib->fib_EntryType = ST_ROOT; +#endif len = strlen(g->volname); if (len > 30) @@ -2152,12 +2159,12 @@ static int exall_fill_entry(struct ExAllData **cursor, LONG *remaining, p = ((UBYTE *)ed) + exall_fixed_size(data); if (data >= ED_COMMENT) { - ed->ed_Comment = p; + ed->ed_Comment = (STRPTR)p; memcpy(p, comment, comment_len); p += comment_len; } - ed->ed_Name = p; + ed->ed_Name = (STRPTR)p; memcpy(p, name, name_len); if (data >= ED_TYPE) @@ -2220,8 +2227,9 @@ static odfs_err_t exall_cb(const odfs_node_t *entry, void *ctx) 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; @@ -2826,7 +2834,8 @@ static struct DeviceNode *create_device_node(handler_global_t *g) namelen = 30; alloc_size = sizeof(*devnode) + 32u; - devnode = AllocMem(alloc_size, MEMF_PUBLIC | MEMF_CLEAR); + devnode = odfs_amiga_alloc_mem((ULONG)alloc_size, + MEMF_PUBLIC | MEMF_CLEAR); if (!devnode) return NULL; @@ -2835,7 +2844,9 @@ static struct DeviceNode *create_device_node(handler_global_t *g) memcpy(namebuf + 1, name, (size_t)namelen); devnode->dn_Next = 0; +#ifndef __amigaos4__ devnode->dn_Lock = g->devnode ? g->devnode->dn_Lock : 0; +#endif devnode->dn_Name = MKBADDR(namebuf); sync_device_node(g, devnode); @@ -2846,7 +2857,7 @@ static void destroy_device_node(struct DeviceNode *devnode) { if (!devnode) return; - FreeMem(devnode, sizeof(*devnode) + 32u); + odfs_amiga_free_mem(devnode, sizeof(*devnode) + 32u); } static void publish_device_node(handler_global_t *g) @@ -2958,7 +2969,7 @@ static struct DeviceList *create_volume_node(handler_global_t *g) * metacharacters such as parentheses. */ alloc_size = sizeof(*dl) + 32u; - dl = AllocMem(alloc_size, MEMF_PUBLIC | MEMF_CLEAR); + dl = odfs_amiga_alloc_mem((ULONG)alloc_size, MEMF_PUBLIC | MEMF_CLEAR); if (!dl) return NULL; @@ -2983,7 +2994,7 @@ static void destroy_volume_node(struct DeviceList *volnode) { if (!volnode) return; - FreeMem(volnode, sizeof(*volnode) + 32u); + odfs_amiga_free_mem(volnode, sizeof(*volnode) + 32u); } static void detach_volume_node(struct DeviceList *volnode) @@ -3077,7 +3088,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; @@ -3316,23 +3327,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 +3353,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_handler); g->chgreq->io_Data = (APTR)&g->changeint; g->chgreq->io_Length = sizeof(g->changeint); g->chgreq->io_Flags = 0; @@ -3372,15 +3380,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; } @@ -3469,13 +3477,13 @@ void handler_main(void) (void)version_string; /* ensure $VER is not optimized out */ - SysBase = *((struct ExecBase **)4L); + odfs_amiga_init_sysbase(); - g = AllocMem(sizeof(*g), MEMF_PUBLIC | MEMF_CLEAR); + g = odfs_amiga_alloc_mem(sizeof(*g), MEMF_PUBLIC | MEMF_CLEAR); if (!g) 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; @@ -3529,21 +3537,19 @@ 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(); /* 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 +3560,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", @@ -3602,11 +3608,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 */ @@ -3695,21 +3701,19 @@ void handler_main(void) 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); - - CloseLibrary((struct Library *)DOSBase); - FreeMem(g, sizeof(*g)); + odfs_amiga_close_libraries(); + odfs_amiga_free_mem(g, sizeof(*g)); } diff --git a/platform/amiga/os3/sys_compat.c b/platform/amiga/os3/sys_compat.c new file mode 100644 index 0000000..630a262 --- /dev/null +++ b/platform/amiga/os3/sys_compat.c @@ -0,0 +1,129 @@ +/* + * sys_compat.c - AmigaOS 3 / AROS integration helpers + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "sys_compat.h" + +#include +#include +#include + +struct ExecBase *SysBase; +struct DosLibrary *DOSBase; +struct Library *UtilityBase; + +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); +} + +void *odfs_amiga_alloc_vec(ULONG size, ULONG flags) +{ + return AllocVec(size, flags); +} + +void odfs_amiga_free_vec(void *ptr) +{ + if (ptr) + FreeVec(ptr); +} + +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_init_interrupt(struct Interrupt *intr, + const char *name, + APTR data, + odfs_amiga_interrupt_fn 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)code; +} + +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/main.c b/platform/amiga/os4/main.c new file mode 100644 index 0000000..d13169d --- /dev/null +++ b/platform/amiga/os4/main.c @@ -0,0 +1,13 @@ +/* + * main.c - AmigaOS 4 handler entry wrapper + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "handler.h" + +int main(void) +{ + handler_main(); + return 0; +} diff --git a/platform/amiga/os4/sys_compat.c b/platform/amiga/os4/sys_compat.c new file mode 100644 index 0000000..cc96ab5 --- /dev/null +++ b/platform/amiga/os4/sys_compat.c @@ -0,0 +1,185 @@ +/* + * 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 + +struct ExecBase *SysBase; +struct DosLibrary *DOSBase; +struct UtilityBase *UtilityBase; + +static struct DOSIFace *dos_iface; +static struct UtilityIFace *utility_iface; + +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; + + 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_vec(ULONG size, ULONG flags) +{ + if (size == 0) + size = 1; + + if (flags & MEMF_CLEAR) { + return AllocVecTags(size, + AVT_Type, flags & ~MEMF_CLEAR, + AVT_ClearWithValue, 0, + TAG_END); + } + + return AllocVecTags(size, AVT_Type, flags, TAG_END); +} + +void odfs_amiga_free_vec(void *ptr) +{ + if (ptr) + FreeVec(ptr); +} + +void *odfs_amiga_alloc_mem(ULONG size, ULONG flags) +{ + return odfs_amiga_alloc_vec(size, flags); +} + +void odfs_amiga_free_mem(void *ptr, ULONG size) +{ + (void)size; + odfs_amiga_free_vec(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_init_interrupt(struct Interrupt *intr, + const char *name, + APTR data, + odfs_amiga_interrupt_fn 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)code; +} + +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); +} From e24328cfc3ef0786712a326fbadb687ca076c238 Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Thu, 21 May 2026 13:31:07 -0700 Subject: [PATCH 02/36] amiga: hide OS target conditionals Move the remaining AmigaOS 4 source differences out of the shared packet handler. Add per-target compatibility headers and route the interrupt callback ABI, FileInfoBlock entry type, and DeviceNode lock copying through sys_compat helpers. Document the CD0 default, OD0 fallback, and requester policy for unsafe conflicts. Name fallback should be quiet, while duplicate OD0 or same physical Device/Unit conflicts should fail visibly. --- platform/amiga/common/sys_compat.h | 6 ++++++ platform/amiga/handler.h | 4 +--- platform/amiga/handler_main.c | 22 +++++----------------- platform/amiga/os3/amiga_target_compat.h | 10 ++++++++++ platform/amiga/os3/sys_compat.c | 21 ++++++++++++++++++++- platform/amiga/os4/amiga_target_compat.h | 17 +++++++++++++++++ platform/amiga/os4/sys_compat.c | 13 +++++++++++++ 7 files changed, 72 insertions(+), 21 deletions(-) create mode 100644 platform/amiga/os3/amiga_target_compat.h create mode 100644 platform/amiga/os4/amiga_target_compat.h diff --git a/platform/amiga/common/sys_compat.h b/platform/amiga/common/sys_compat.h index 85eda5e..7f91114 100644 --- a/platform/amiga/common/sys_compat.h +++ b/platform/amiga/common/sys_compat.h @@ -16,10 +16,13 @@ #include #include #include +#include #include struct Hook; +struct FileInfoBlock; +struct DeviceNode; typedef LONG (*odfs_amiga_interrupt_fn)(APTR data); @@ -52,6 +55,9 @@ void odfs_amiga_init_interrupt(struct Interrupt *intr, APTR data, odfs_amiga_interrupt_fn code); +void odfs_amiga_set_fib_entry_type(struct FileInfoBlock *fib, LONG type); +void odfs_amiga_copy_device_lock(struct DeviceNode *dst, + const struct DeviceNode *src); 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 7b8e4cc..f1a33b1 100644 --- a/platform/amiga/handler.h +++ b/platform/amiga/handler.h @@ -14,10 +14,8 @@ #include #include #include -#ifdef __amigaos4__ -#include -#endif +#include "amiga_target_compat.h" #include "aros_compat.h" #include "odfs/api.h" diff --git a/platform/amiga/handler_main.c b/platform/amiga/handler_main.c index a45b075..7c8e174 100644 --- a/platform/amiga/handler_main.c +++ b/platform/amiga/handler_main.c @@ -102,13 +102,7 @@ static int scsi_is_unsupported_command(const uint8_t *sense) return ((sense[2] & 0x0f) == 0x05 && sense[12] == 0x20); } -static LONG changeint_handler( -#ifdef __amigaos4__ - APTR data -#else - APTR data asm("a1") -#endif -) +static LONG changeint_signal(APTR data) { odfs_changeint_data_t *ci = data; @@ -1484,9 +1478,7 @@ static void fill_fib(struct FileInfoBlock *fib, const odfs_node_t *fnode) } fib->fib_DirEntryType = (fnode->kind == ODFS_NODE_DIR) ? ST_USERDIR : ST_FILE; -#ifndef __amigaos4__ - fib->fib_EntryType = fib->fib_DirEntryType; -#endif + odfs_amiga_set_fib_entry_type(fib, fib->fib_DirEntryType); fib->fib_Size = (LONG)fnode->size; fib->fib_NumBlocks = (fnode->size + 511) / 512; @@ -1593,9 +1585,7 @@ static void fill_root_fib(handler_global_t *g, struct FileInfoBlock *fib, fill_fib(fib, fnode); fib->fib_DirEntryType = ST_ROOT; -#ifndef __amigaos4__ - fib->fib_EntryType = ST_ROOT; -#endif + odfs_amiga_set_fib_entry_type(fib, ST_ROOT); len = strlen(g->volname); if (len > 30) @@ -2844,9 +2834,7 @@ static struct DeviceNode *create_device_node(handler_global_t *g) memcpy(namebuf + 1, name, (size_t)namelen); devnode->dn_Next = 0; -#ifndef __amigaos4__ - devnode->dn_Lock = g->devnode ? g->devnode->dn_Lock : 0; -#endif + odfs_amiga_copy_device_lock(devnode, g->devnode); devnode->dn_Name = MKBADDR(namebuf); sync_device_node(g, devnode); @@ -3356,7 +3344,7 @@ static void install_media_change(handler_global_t *g) g->changeint_data.task = g->dosport->mp_SigTask; g->changeint_data.sigmask = 1UL << g->chgsigbit; odfs_amiga_init_interrupt(&g->changeint, "odfs-mediachange", - &g->changeint_data, changeint_handler); + &g->changeint_data, changeint_signal); g->chgreq->io_Data = (APTR)&g->changeint; g->chgreq->io_Length = sizeof(g->changeint); g->chgreq->io_Flags = 0; diff --git a/platform/amiga/os3/amiga_target_compat.h b/platform/amiga/os3/amiga_target_compat.h new file mode 100644 index 0000000..e4a1634 --- /dev/null +++ b/platform/amiga/os3/amiga_target_compat.h @@ -0,0 +1,10 @@ +/* + * 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 + +#endif /* ODFS_AMIGA_TARGET_COMPAT_H */ diff --git a/platform/amiga/os3/sys_compat.c b/platform/amiga/os3/sys_compat.c index 630a262..c4d7a7d 100644 --- a/platform/amiga/os3/sys_compat.c +++ b/platform/amiga/os3/sys_compat.c @@ -14,6 +14,13 @@ 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); @@ -113,11 +120,23 @@ void odfs_amiga_init_interrupt(struct Interrupt *intr, 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)code; + intr->is_Code = (void (*)(void))(APTR)odfs_amiga_interrupt_entry; +} + +void odfs_amiga_set_fib_entry_type(struct FileInfoBlock *fib, LONG type) +{ + fib->fib_EntryType = type; +} + +void odfs_amiga_copy_device_lock(struct DeviceNode *dst, + const struct DeviceNode *src) +{ + dst->dn_Lock = src ? src->dn_Lock : 0; } ULONG odfs_amiga_call_hook_pkt(struct Hook *hook, APTR object, APTR 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..aa053b5 --- /dev/null +++ b/platform/amiga/os4/amiga_target_compat.h @@ -0,0 +1,17 @@ +/* + * 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 + +/* + * 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/sys_compat.c b/platform/amiga/os4/sys_compat.c index cc96ab5..bb7bdd8 100644 --- a/platform/amiga/os4/sys_compat.c +++ b/platform/amiga/os4/sys_compat.c @@ -176,6 +176,19 @@ void odfs_amiga_init_interrupt(struct Interrupt *intr, intr->is_Code = (void (*)(void))(APTR)code; } +void odfs_amiga_set_fib_entry_type(struct FileInfoBlock *fib, LONG type) +{ + (void)fib; + (void)type; +} + +void odfs_amiga_copy_device_lock(struct DeviceNode *dst, + const struct DeviceNode *src) +{ + (void)dst; + (void)src; +} + ULONG odfs_amiga_call_hook_pkt(struct Hook *hook, APTR object, APTR message) { if (!utility_iface) From 1e72834f65dd11a55961940849b092102052c549 Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Thu, 21 May 2026 13:48:06 -0700 Subject: [PATCH 03/36] amiga: scaffold OS4 vector frontend Teach the OS4 entry wrapper to receive and validate the initial ACTION_STARTUP packet before passing it into the shared handler loop. Add a native FileSystemVectorPort template with explicit unsupported-vector stubs. This gives the next slice a real OS4 frontend to map onto shared read-only operations. --- Makefile | 3 +- platform/amiga/handler.h | 1 + platform/amiga/handler_main.c | 15 +- platform/amiga/os4/main.c | 58 ++- platform/amiga/os4/vector_port.c | 621 +++++++++++++++++++++++++++++++ platform/amiga/os4/vector_port.h | 17 + 6 files changed, 709 insertions(+), 6 deletions(-) create mode 100644 platform/amiga/os4/vector_port.c create mode 100644 platform/amiga/os4/vector_port.h diff --git a/Makefile b/Makefile index f6cc7e1..f14e186 100644 --- a/Makefile +++ b/Makefile @@ -197,7 +197,8 @@ AMIGA_SRCS = platform/amiga/handler_main.c \ platform/amiga/printf_local.c \ platform/amiga/$(AMIGA_OSDIR)/sys_compat.c ifeq ($(AMIGA_TARGET),os4) -AMIGA_SRCS += platform/amiga/os4/main.c +AMIGA_SRCS += platform/amiga/os4/main.c \ + platform/amiga/os4/vector_port.c else AMIGA_SRCS += platform/amiga/libc_stubs.c endif diff --git a/platform/amiga/handler.h b/platform/amiga/handler.h index f1a33b1..7393a53 100644 --- a/platform/amiga/handler.h +++ b/platform/amiga/handler.h @@ -199,5 +199,6 @@ static inline LONG odfs_err_to_dos(odfs_err_t err) /* 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 7c8e174..0361c3c 100644 --- a/platform/amiga/handler_main.c +++ b/platform/amiga/handler_main.c @@ -3452,7 +3452,7 @@ 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; @@ -3491,8 +3491,12 @@ void handler_main(void) } /* 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); @@ -3705,3 +3709,8 @@ void handler_main(void) odfs_amiga_close_libraries(); odfs_amiga_free_mem(g, sizeof(*g)); } + +void handler_main(void) +{ + handler_main_startup(NULL); +} diff --git a/platform/amiga/os4/main.c b/platform/amiga/os4/main.c index d13169d..d87114b 100644 --- a/platform/amiga/os4/main.c +++ b/platform/amiga/os4/main.c @@ -6,8 +6,62 @@ #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 main(void) { - handler_main(); - return 0; + struct Process *proc; + struct Message *msg; + struct DosPacket *pkt; + + proc = (struct Process *)FindTask(NULL); + if (!proc) + return RETURN_FAIL; + + WaitPort(&proc->pr_MsgPort); + msg = GetMsg(&proc->pr_MsgPort); + 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 (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/vector_port.c b/platform/amiga/os4/vector_port.c new file mode 100644 index 0000000..0d5d97b --- /dev/null +++ b/platform/amiga/os4/vector_port.c @@ -0,0 +1,621 @@ +/* + * vector_port.c - AmigaOS 4 filesystem vector-port frontend + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "vector_port.h" + +#include +#include +#include +#include + +#include + +#include + +static void set_unsupported(struct FSVP *vp, int32 *res2) +{ + (void)vp; + + if (res2) + *res2 = ERROR_ACTION_NOT_KNOWN; +} + +static struct Lock *vp_lock(struct FSVP *vp, + int32 *res2, + struct Lock *rel_lock, + CONST_STRPTR obj, + int32 mode) +{ + set_unsupported(vp, res2); + (void)rel_lock; + (void)obj; + (void)mode; + return NULL; +} + +static int32 vp_unlock(struct FSVP *vp, int32 *res2, struct Lock *lock) +{ + set_unsupported(vp, res2); + (void)lock; + return DOSFALSE; +} + +static struct Lock *vp_dup_lock(struct FSVP *vp, + int32 *res2, + struct Lock *lock) +{ + set_unsupported(vp, res2); + (void)lock; + return NULL; +} + +static struct Lock *vp_create_dir(struct FSVP *vp, + int32 *res2, + struct Lock *rel_lock, + CONST_STRPTR obj) +{ + set_unsupported(vp, res2); + (void)rel_lock; + (void)obj; + return NULL; +} + +static struct Lock *vp_parent_dir(struct FSVP *vp, + int32 *res2, + struct Lock *dirlock) +{ + set_unsupported(vp, res2); + (void)dirlock; + return NULL; +} + +static struct Lock *vp_dup_lock_from_fh(struct FSVP *vp, + int32 *res2, + struct FileHandle *filehandle) +{ + set_unsupported(vp, res2); + (void)filehandle; + return NULL; +} + +static int32 vp_open_from_lock(struct FSVP *vp, + int32 *res2, + struct FileHandle *file, + struct Lock *lock) +{ + set_unsupported(vp, res2); + (void)file; + (void)lock; + return DOSFALSE; +} + +static struct Lock *vp_parent_of_fh(struct FSVP *vp, + int32 *res2, + struct FileHandle *file) +{ + set_unsupported(vp, res2); + (void)file; + return NULL; +} + +static int32 vp_open(struct FSVP *vp, + int32 *res2, + struct FileHandle *fh, + struct Lock *rel_dir, + CONST_STRPTR obj, + int32 mode) +{ + set_unsupported(vp, res2); + (void)fh; + (void)rel_dir; + (void)obj; + (void)mode; + return DOSFALSE; +} + +static int32 vp_close(struct FSVP *vp, int32 *res2, struct FileHandle *file) +{ + set_unsupported(vp, res2); + (void)file; + return DOSFALSE; +} + +static int32 vp_delete(struct FSVP *vp, + int32 *res2, + struct Lock *rel_dirlock, + CONST_STRPTR obj) +{ + set_unsupported(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) +{ + set_unsupported(vp, res2); + (void)file; + (void)buffer; + (void)numbytes; + return 0; +} + +static int32 vp_write(struct FSVP *vp, + int32 *res2, + struct FileHandle *file, + STRPTR buffer, + int32 numbytes) +{ + set_unsupported(vp, res2); + (void)file; + (void)buffer; + (void)numbytes; + return 0; +} + +static int32 vp_flush(struct FSVP *vp, int32 *res2) +{ + set_unsupported(vp, res2); + return DOSFALSE; +} + +static int32 vp_change_file_position(struct FSVP *vp, + int32 *res2, + struct FileHandle *file, + int32 mode, + int64 position) +{ + set_unsupported(vp, res2); + (void)file; + (void)mode; + (void)position; + return DOSFALSE; +} + +static int32 vp_change_file_size(struct FSVP *vp, + int32 *res2, + struct FileHandle *file, + int32 mode, + int64 size) +{ + set_unsupported(vp, res2); + (void)file; + (void)mode; + (void)size; + return DOSFALSE; +} + +static int64 vp_get_file_position(struct FSVP *vp, + int32 *res2, + struct FileHandle *file) +{ + set_unsupported(vp, res2); + (void)file; + return 0; +} + +static int64 vp_get_file_size(struct FSVP *vp, + int32 *res2, + struct FileHandle *file) +{ + set_unsupported(vp, res2); + (void)file; + return 0; +} + +static int32 vp_change_lock_mode(struct FSVP *vp, + int32 *res2, + struct Lock *lock, + int32 new_lock_mode) +{ + set_unsupported(vp, res2); + (void)lock; + (void)new_lock_mode; + return DOSFALSE; +} + +static int32 vp_change_file_mode(struct FSVP *vp, + int32 *res2, + struct FileHandle *fh, + int32 new_lock_mode) +{ + set_unsupported(vp, res2); + (void)fh; + (void)new_lock_mode; + return DOSFALSE; +} + +static int32 vp_set_date(struct FSVP *vp, + int32 *res2, + struct Lock *rel_dirlock, + CONST_STRPTR name, + const struct DateStamp *ds) +{ + set_unsupported(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_unsupported(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_unsupported(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_unsupported(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_unsupported(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_unsupported(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_unsupported(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_unsupported(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) +{ + set_unsupported(vp, res2); + (void)lock1; + (void)lock2; + return DOSFALSE; +} + +static int32 vp_same_file(struct FSVP *vp, + int32 *res2, + struct FileHandle *fh1, + struct FileHandle *fh2) +{ + set_unsupported(vp, res2); + (void)fh1; + (void)fh2; + return DOSFALSE; +} + +static int32 vp_filesystem_attr(struct FSVP *vp, + int32 *res2, + struct TagItem *taglist) +{ + set_unsupported(vp, res2); + (void)taglist; + return DOSFALSE; +} + +static int32 vp_volume_info_data(struct FSVP *vp, + int32 *res2, + struct InfoData *info) +{ + set_unsupported(vp, res2); + (void)info; + return DOSFALSE; +} + +static int32 vp_device_info_data(struct FSVP *vp, + int32 *res2, + struct InfoData *info) +{ + set_unsupported(vp, res2); + (void)info; + return DOSFALSE; +} + +static struct ExamineData *vp_examine_obj(struct FSVP *vp, + int32 *res2, + struct Lock *lock, + CONST_STRPTR object) +{ + set_unsupported(vp, res2); + (void)lock; + (void)object; + return NULL; +} + +static struct ExamineData *vp_examine_lock(struct FSVP *vp, + int32 *res2, + struct Lock *lock) +{ + set_unsupported(vp, res2); + (void)lock; + return NULL; +} + +static struct ExamineData *vp_examine_file(struct FSVP *vp, + int32 *res2, + struct FileHandle *file) +{ + set_unsupported(vp, res2); + (void)file; + return NULL; +} + +static int32 vp_examine_dir(struct FSVP *vp, + int32 *res2, + struct PRIVATE_ExamineDirContext *ctx) +{ + set_unsupported(vp, res2); + (void)ctx; + return DOSFALSE; +} + +static int32 vp_inhibit(struct FSVP *vp, int32 *res2, int32 inhibit_state) +{ + set_unsupported(vp, res2); + (void)inhibit_state; + return DOSFALSE; +} + +static int32 vp_write_protect(struct FSVP *vp, + int32 *res2, + int32 wp_state, + uint32 passkey) +{ + set_unsupported(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_unsupported(vp, res2); + (void)new_volname; + (void)dostype; + (void)spare; + return DOSFALSE; +} + +static int32 vp_serialize(struct FSVP *vp, int32 *res2) +{ + set_unsupported(vp, res2); + return DOSFALSE; +} + +static int32 vp_relabel(struct FSVP *vp, + int32 *res2, + CONST_STRPTR new_volumename) +{ + set_unsupported(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 +}; + +const struct FileSystemVectors *odfs_os4_vector_template(void) +{ + return &odfs_os4_vectors; +} + +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); +} diff --git a/platform/amiga/os4/vector_port.h b/platform/amiga/os4/vector_port.h new file mode 100644 index 0000000..2b92cb8 --- /dev/null +++ b/platform/amiga/os4/vector_port.h @@ -0,0 +1,17 @@ +/* + * 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 + +const struct FileSystemVectors *odfs_os4_vector_template(void); +struct FileSystemVectorPort *odfs_os4_alloc_vector_port(APTR fs_private); +void odfs_os4_free_vector_port(struct FileSystemVectorPort *vp); + +#endif /* ODFS_AMIGA_OS4_VECTOR_PORT_H */ From 241dcf01ab0034ed4470ca6afa6cb205b847fd72 Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Thu, 21 May 2026 15:23:31 -0700 Subject: [PATCH 04/36] amiga: map OS4 vectors to shared operations Move the first native OS4 vector callbacks onto shared handler operations for locks, file handles, reads, seeks, and info. Keep the packet frontend on the same operations so behavior stays aligned. Add native ExamineData allocation for object, lock, and filehandle examine calls. Leave directory iteration unsupported until the OS4 private context ownership rules are confirmed. --- platform/amiga/handler.h | 76 +- platform/amiga/handler_main.c | 1162 ++++++++++++++++++++---------- platform/amiga/os4/vector_port.c | 330 +++++++-- 3 files changed, 1116 insertions(+), 452 deletions(-) diff --git a/platform/amiga/handler.h b/platform/amiga/handler.h index 7393a53..c45fe6f 100644 --- a/platform/amiga/handler.h +++ b/platform/amiga/handler.h @@ -136,7 +136,7 @@ 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 ---- */ @@ -150,6 +150,15 @@ 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) + /* BCPL string to C string (AROS-compatible) */ static inline void bstr_to_cstr(BSTR bstr, char *buf, int bufsize) { @@ -197,6 +206,71 @@ static inline LONG odfs_err_to_dos(odfs_err_t err) } } +/* shared operations used by packet and OS4 vector frontends */ +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_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); +ULONG odfs_handler_node_key(const odfs_node_t *node); +ULONG odfs_handler_node_protection(const odfs_node_t *node); +void odfs_handler_node_date(const odfs_node_t *node, struct DateStamp *ds); + /* handler entry point (called from startup.S) */ void handler_main(void); void handler_main_startup(struct Message *startup_msg); diff --git a/platform/amiga/handler_main.c b/platform/amiga/handler_main.c index 0361c3c..2b5b09f 100644 --- a/platform/amiga/handler_main.c +++ b/platform/amiga/handler_main.c @@ -1154,6 +1154,104 @@ static ULONG amiga_node_key(const odfs_node_t *node) return key; } +ULONG odfs_handler_node_key(const odfs_node_t *node) +{ + return amiga_node_key(node); +} + +ULONG odfs_handler_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; +} + +void odfs_handler_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; + } +} + static odfs_err_t lookup_child_node(handler_global_t *g, const odfs_node_t *dir, const char *name, @@ -1595,203 +1693,171 @@ static void fill_root_fib(handler_global_t *g, struct FileInfoBlock *fib, } /* ------------------------------------------------------------------ */ -/* packet handlers */ +/* shared frontend operations */ /* ------------------------------------------------------------------ */ -static void action_locate_object(handler_global_t *g, struct DosPacket *pkt) +LONG odfs_handler_lock_object(handler_global_t *g, + odfs_lock_t *parent_lock, + const char *path, + LONG access, + odfs_lock_t **out) { - 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; -#if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE - trace_pkt(g, "locate-enter", pkt); -#endif - bstr_to_cstr(pkt->dp_Arg2, path, sizeof(path)); -#if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE - ODFS_TRACE(&g->log, ODFS_SUB_DOS, "locate-path path=%s", path); -#endif + if (out) + *out = NULL; + if (!g || !path || !out) + return ERROR_REQUIRED_ARG_MISSING; 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; - } + LONG err_dos; + + 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); } else { - if (!g->mounted) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_NO_DISK; - return; - } + if (!g->mounted) + return ERROR_NO_DISK; 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); -#if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE - trace_pkt(g, "locate-resolve-fail", pkt); -#endif - return; - } + if (err != ODFS_OK) + return odfs_err_to_dos(err); -#if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE - trace_node(g, "locate-node", &result); - trace_node(g, "locate-parent", &parent_node); -#endif + if (result.kind == ODFS_NODE_DIR) + access = SHARED_LOCK; - 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); -#endif - return; - } + ol = alloc_lock(g, &result, &parent_node, access); + if (!ol) + return ERROR_NO_FREE_STORE; - pkt->dp_Res1 = LOCK_TO_BPTR(ol); -#if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE - trace_pkt(g, "locate-exit", pkt); -#endif + *out = ol; + return 0; } -static void action_free_lock(handler_global_t *g, struct DosPacket *pkt) +LONG odfs_handler_free_lock_object(handler_global_t *g, odfs_lock_t *ol) { - odfs_lock_t *ol = LOCK_FROM_BPTR(pkt->dp_Arg1); + if (!ol) + return 0; + if (!lock_is_active(g, ol)) + return ERROR_INVALID_LOCK; + free_lock(g, ol); - pkt->dp_Res1 = DOSTRUE; + return 0; } -static void action_copy_dir(handler_global_t *g, struct DosPacket *pkt) +LONG odfs_handler_dup_lock_object(handler_global_t *g, + odfs_lock_t *src, + odfs_lock_t **out) { - odfs_lock_t *src = LOCK_FROM_BPTR(pkt->dp_Arg1); odfs_lock_t *ol; + if (out) + *out = NULL; + if (!g || !out) + return ERROR_REQUIRED_ARG_MISSING; + if (!src) { - if (!g->mounted) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_NO_DISK; - return; - } + if (!g->mounted) + return ERROR_NO_DISK; 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; - } + 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) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_NO_FREE_STORE; - return; - } - pkt->dp_Res1 = LOCK_TO_BPTR(ol); + if (!ol) + return ERROR_NO_FREE_STORE; + + *out = ol; + return 0; } -static void action_copy_dir_fh(handler_global_t *g, struct DosPacket *pkt) +LONG odfs_handler_dup_lock_from_fh(handler_global_t *g, + odfs_fh_t *fh, + odfs_lock_t **out) { - odfs_fh_t *fh = (odfs_fh_t *)pkt->dp_Arg1; odfs_lock_t *ol; + if (out) + *out = NULL; + if (!g || !out) + return ERROR_REQUIRED_ARG_MISSING; + if (!fh) { - if (!g->mounted) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_NO_DISK; - return; - } + if (!g->mounted) + return ERROR_NO_DISK; 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. */ + 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), SHARED_LOCK); } - if (!ol) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_NO_FREE_STORE; - return; - } - pkt->dp_Res1 = LOCK_TO_BPTR(ol); + if (!ol) + return ERROR_NO_FREE_STORE; + + *out = ol; + return 0; } -static void action_parent(handler_global_t *g, struct DosPacket *pkt) +LONG odfs_handler_parent_lock_object(handler_global_t *g, + odfs_lock_t *ol, + odfs_lock_t **out) { - 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; -#if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE - ODFS_TRACE(&g->log, ODFS_SUB_DOS, - "parent-enter arg1=%08lx lock=%08lx", - (unsigned long)pkt->dp_Arg1, (unsigned long)ol); -#endif + if (out) + *out = NULL; + if (!g || !out) + return ERROR_REQUIRED_ARG_MISSING; 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 (!g->mounted) + return ERROR_NO_DISK; + return 0; } - 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 - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_INVALID_LOCK; - return; - } + if (!lock_is_active(g, ol)) + return ERROR_INVALID_LOCK; { LONG err_dos = validate_object_volume(g, ol->entry->volume); - if (err_dos != 0) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = err_dos; - return; - } + if (err_dos != 0) + return err_dos; } - /* already at root? */ - if (node_is_mount_root(g, lock_node(ol))) { - pkt->dp_Res1 = 0; - return; - } + if (node_is_mount_root(g, lock_node(ol))) + return 0; - /* 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; @@ -1802,70 +1868,49 @@ static void action_parent(handler_global_t *g, struct DosPacket *pkt) } 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 (err != ODFS_OK) + return odfs_err_to_dos(err); } -#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); -#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); + if (!parent) + return ERROR_NO_FREE_STORE; + + *out = parent; + return 0; } -static void action_parent_fh(handler_global_t *g, struct DosPacket *pkt) +LONG odfs_handler_parent_fh_object(handler_global_t *g, + odfs_fh_t *fh, + odfs_lock_t **out) { - 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; -#if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE - ODFS_TRACE(&g->log, ODFS_SUB_DOS, - "parentfh-enter fh=%08lx", (unsigned long)fh); -#endif + if (out) + *out = NULL; + if (!g || !out) + return ERROR_REQUIRED_ARG_MISSING; if (!fh) { - if (!g->mounted) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_NO_DISK; - return; - } - pkt->dp_Res1 = 0; - return; + if (!g->mounted) + return ERROR_NO_DISK; + return 0; } - if (!fh_is_active(g, fh)) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_OBJECT_NOT_FOUND; - return; - } + 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) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = err_dos; - return; - } + if (err_dos != 0) + return err_dos; } - if (node_is_mount_root(g, fh_node(fh))) { - pkt->dp_Res1 = 0; - return; - } + if (node_is_mount_root(g, fh_node(fh))) + return 0; parent_node = fh_parent_node(fh); if (node_is_mount_root(g, parent_node)) { @@ -1877,65 +1922,550 @@ static void action_parent_fh(handler_global_t *g, struct DosPacket *pkt) } 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 (err != ODFS_OK) + return odfs_err_to_dos(err); } -#if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE - trace_node(g, "parentfh-node", fh_node(fh)); - trace_node(g, "parentfh-result", parent_node); -#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); + if (!parent) + return ERROR_NO_FREE_STORE; + + *out = parent; + return 0; } -static void action_same_lock(handler_global_t *g, struct DosPacket *pkt) +LONG odfs_handler_same_lock_object(handler_global_t *g, + odfs_lock_t *l1, + odfs_lock_t *l2, + LONG *same_result) { - 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; - if (!g->mounted && (!pkt->dp_Arg1 || !pkt->dp_Arg2)) { - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_DEVICE_NOT_MOUNTED; - return; - } + if (!g || !same_result) + return ERROR_REQUIRED_ARG_MISSING; - v1 = l1 ? l1->entry->volume : g->current_volume; - v2 = l2 ? l2->entry->volume : g->current_volume; + *same_result = LOCK_DIFFERENT; - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = 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; + return 0; - pkt->dp_Res2 = LOCK_SAME_VOLUME; - if (!pkt->dp_Arg1 && !pkt->dp_Arg2) { + *same_result = LOCK_SAME_VOLUME; + if (!l1 && !l2) { same = 1; - } else if (!pkt->dp_Arg1) { + } else if (!l1) { same = node_is_mount_root(g, lock_node(l2)); - } else if (!pkt->dp_Arg2) { + } else if (!l2) { 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; + 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; + odfs_err_t err; + const odfs_node_t *start; + const odfs_node_t *start_parent; + 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; + + if (dirlock) { + LONG err_dos; + + if (!lock_is_active(g, dirlock)) + return ERROR_INVALID_LOCK; + + err_dos = validate_object_volume(g, dirlock->entry->volume); + if (err_dos != 0) + return err_dos; + start = lock_node(dirlock); + start_parent = lock_parent_node(dirlock); + } else { + if (!g->mounted) + return ERROR_NO_DISK; + 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) + return odfs_err_to_dos(err); + + if (result.kind == ODFS_NODE_DIR) + return ERROR_OBJECT_WRONG_TYPE; + + entry = alloc_entry(g->current_volume, &result, &parent_node); + 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, ol->lock.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_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; +} + +/* ------------------------------------------------------------------ */ +/* packet handlers */ +/* ------------------------------------------------------------------ */ + +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_lock_t *ol; + LONG err_dos; + +#if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE + trace_pkt(g, "locate-enter", pkt); +#endif + bstr_to_cstr(pkt->dp_Arg2, path, sizeof(path)); +#if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE + ODFS_TRACE(&g->log, ODFS_SUB_DOS, "locate-path path=%s", path); +#endif + + err_dos = odfs_handler_lock_object(g, parent_lock, path, access, &ol); + if (err_dos != 0) { + pkt->dp_Res1 = DOSFALSE; + pkt->dp_Res2 = err_dos; +#if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE + trace_pkt(g, "locate-resolve-fail", pkt); +#endif + return; + } + +#if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE + trace_node(g, "locate-node", lock_node(ol)); + trace_node(g, "locate-parent", lock_parent_node(ol)); +#endif + + pkt->dp_Res1 = LOCK_TO_BPTR(ol); +#if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE + trace_pkt(g, "locate-exit", pkt); +#endif +} + +static void action_free_lock(handler_global_t *g, struct DosPacket *pkt) +{ + odfs_lock_t *ol = LOCK_FROM_BPTR(pkt->dp_Arg1); + 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; +} + +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; + + err_dos = odfs_handler_dup_lock_object(g, src, &ol); + if (err_dos != 0) { + pkt->dp_Res1 = DOSFALSE; + pkt->dp_Res2 = err_dos; + return; + } + pkt->dp_Res1 = LOCK_TO_BPTR(ol); +} + +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; + + err_dos = odfs_handler_dup_lock_from_fh(g, fh, &ol); + if (err_dos != 0) { + pkt->dp_Res1 = DOSFALSE; + pkt->dp_Res2 = err_dos; + return; + } + pkt->dp_Res1 = LOCK_TO_BPTR(ol); +} + +static void action_parent(handler_global_t *g, struct DosPacket *pkt) +{ + odfs_lock_t *ol = LOCK_FROM_BPTR(pkt->dp_Arg1); + odfs_lock_t *parent; + LONG err_dos; + +#if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE + ODFS_TRACE(&g->log, ODFS_SUB_DOS, + "parent-enter arg1=%08lx lock=%08lx", + (unsigned long)pkt->dp_Arg1, (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 = err_dos; + return; + } + +#if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE + 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 + + 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; + 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 + + err_dos = odfs_handler_parent_fh_object(g, fh, &parent); + if (err_dos != 0) { + pkt->dp_Res1 = DOSFALSE; + pkt->dp_Res2 = err_dos; + return; + } + +#if ODFS_SERIAL_DEBUG && ODFS_PACKET_TRACE + if (fh) + trace_node(g, "parentfh-node", fh_node(fh)); + if (parent) + trace_node(g, "parentfh-result", lock_node(parent)); +#endif + + pkt->dp_Res1 = LOCK_TO_BPTR(parent); +} + +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); + LONG same_result; + LONG err_dos; + + err_dos = odfs_handler_same_lock_object(g, l1, l2, &same_result); + if (err_dos != 0) { + pkt->dp_Res1 = DOSFALSE; + pkt->dp_Res2 = err_dos; + return; + } + + pkt->dp_Res1 = (same_result == LOCK_SAME) ? DOSTRUE : DOSFALSE; + pkt->dp_Res2 = same_result; } /* ---- examine ---- */ @@ -2362,29 +2892,19 @@ static void action_examine_fh(handler_global_t *g, struct DosPacket *pkt) 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 ---- */ @@ -2394,60 +2914,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; } @@ -2460,90 +2935,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) { + err_dos = odfs_handler_read_object(g, fh, buf, len, &actual); + if (err_dos != 0) { pkt->dp_Res1 = -1; - pkt->dp_Res2 = ERROR_OBJECT_NOT_FOUND; + pkt->dp_Res2 = err_dos; 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) { - pkt->dp_Res1 = -1; - pkt->dp_Res2 = odfs_err_to_dos(err); - 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; - - 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; - } + int64_t oldpos; + LONG err_dos; - 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; } @@ -2552,20 +2986,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; } @@ -2573,6 +3000,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; @@ -2580,30 +3008,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; } diff --git a/platform/amiga/os4/vector_port.c b/platform/amiga/os4/vector_port.c index 0d5d97b..c37510c 100644 --- a/platform/amiga/os4/vector_port.c +++ b/platform/amiga/os4/vector_port.c @@ -6,6 +6,8 @@ #include "vector_port.h" +#include "handler.h" + #include #include #include @@ -14,6 +16,7 @@ #include #include +#include static void set_unsupported(struct FSVP *vp, int32 *res2) { @@ -23,33 +26,130 @@ static void set_unsupported(struct FSVP *vp, int32 *res2) *res2 = ERROR_ACTION_NOT_KNOWN; } +static handler_global_t *vp_global(struct FSVP *vp) +{ + return vp ? (handler_global_t *)vp->FSV.FSPrivate : NULL; +} + +static void set_dos_error(int32 *res2, LONG err) +{ + if (res2) + *res2 = err; +} + +static int32 return_dos_status(int32 *res2, LONG err) +{ + set_dos_error(res2, err); + return err == 0 ? DOSTRUE : DOSFALSE; +} + +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 const char *node_name_for_examine(handler_global_t *g, + const odfs_node_t *node) +{ + if (g && node && odfs_node_matches_identity(node, &g->mount.root)) + return g->volname; + return node ? node->name : ""; +} + +static struct ExamineData *alloc_examine_data(handler_global_t *g, + const odfs_node_t *node) +{ + const char *name; + const char *comment = ""; + size_t name_len; + size_t comment_len; + struct ExamineData *ed; + + if (!node) + return NULL; + + name = node_name_for_examine(g, node); + if (node->amiga_as.has_comment) + comment = node->amiga_as.comment; + + name_len = strlen(name) + 1u; + comment_len = strlen(comment) + 1u; + + ed = AllocDosObjectTags(DOS_EXAMINEDATA, + ADO_ExamineData_NameSize, + (ULONG)name_len, + ADO_ExamineData_CommentSize, + (ULONG)comment_len, + ADO_ExamineData_LinkSize, + 1UL, + TAG_END); + if (!ed) + return NULL; + + ed->Type = (node->kind == ODFS_NODE_DIR) ? + FSO_TYPE_DIRECTORY : FSO_TYPE_FILE; + ed->FileSize = (node->kind == ODFS_NODE_DIR) ? -1LL : (int64)node->size; + odfs_handler_node_date(node, &ed->Date); + ed->RefCount = 0; + ed->ObjectID = odfs_handler_node_key(node); + ed->Protection = odfs_handler_node_protection(node); + ed->OwnerUID = DOS_OWNER_NONE; + ed->OwnerGID = DOS_OWNER_NONE; + ed->FSPrivate = odfs_handler_node_key(node); + + 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 Lock *vp_lock(struct FSVP *vp, int32 *res2, struct Lock *rel_lock, CONST_STRPTR obj, int32 mode) { - set_unsupported(vp, res2); - (void)rel_lock; - (void)obj; - (void)mode; - return NULL; + handler_global_t *g = vp_global(vp); + odfs_lock_t *ol = NULL; + LONG err; + + err = odfs_handler_lock_object(g, lock_from_vector(rel_lock), + obj ? obj : "", mode, &ol); + set_dos_error(res2, err); + return (struct Lock *)LOCK_TO_PTR(ol); } static int32 vp_unlock(struct FSVP *vp, int32 *res2, struct Lock *lock) { - set_unsupported(vp, res2); - (void)lock; - return DOSFALSE; + return return_dos_status(res2, + odfs_handler_free_lock_object( + vp_global(vp), lock_from_vector(lock))); } static struct Lock *vp_dup_lock(struct FSVP *vp, int32 *res2, struct Lock *lock) { - set_unsupported(vp, res2); - (void)lock; - return NULL; + odfs_lock_t *ol = NULL; + LONG err; + + err = odfs_handler_dup_lock_object(vp_global(vp), + lock_from_vector(lock), &ol); + set_dos_error(res2, err); + return (struct Lock *)LOCK_TO_PTR(ol); } static struct Lock *vp_create_dir(struct FSVP *vp, @@ -67,18 +167,27 @@ static struct Lock *vp_parent_dir(struct FSVP *vp, int32 *res2, struct Lock *dirlock) { - set_unsupported(vp, res2); - (void)dirlock; - return NULL; + odfs_lock_t *parent = NULL; + LONG err; + + err = odfs_handler_parent_lock_object(vp_global(vp), + lock_from_vector(dirlock), + &parent); + 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) { - set_unsupported(vp, res2); - (void)filehandle; - return NULL; + odfs_lock_t *ol = NULL; + LONG err; + + err = odfs_handler_dup_lock_from_fh(vp_global(vp), + fh_from_vector(filehandle), &ol); + set_dos_error(res2, err); + return (struct Lock *)LOCK_TO_PTR(ol); } static int32 vp_open_from_lock(struct FSVP *vp, @@ -86,19 +195,28 @@ static int32 vp_open_from_lock(struct FSVP *vp, struct FileHandle *file, struct Lock *lock) { - set_unsupported(vp, res2); - (void)file; - (void)lock; - return DOSFALSE; + odfs_fh_t *odfs_fh = NULL; + LONG err; + + err = odfs_handler_open_from_lock_object(vp_global(vp), + lock_from_vector(lock), + &odfs_fh); + if (err == 0 && file) + 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) { - set_unsupported(vp, res2); - (void)file; - return NULL; + odfs_lock_t *parent = NULL; + LONG err; + + err = odfs_handler_parent_fh_object(vp_global(vp), + fh_from_vector(file), &parent); + set_dos_error(res2, err); + return (struct Lock *)LOCK_TO_PTR(parent); } static int32 vp_open(struct FSVP *vp, @@ -108,19 +226,25 @@ static int32 vp_open(struct FSVP *vp, CONST_STRPTR obj, int32 mode) { - set_unsupported(vp, res2); - (void)fh; - (void)rel_dir; - (void)obj; - (void)mode; - return DOSFALSE; + odfs_fh_t *odfs_fh = NULL; + LONG err; + + err = odfs_handler_open_object(vp_global(vp), + lock_from_vector(rel_dir), + obj ? obj : "", mode, &odfs_fh); + if (err == 0 && fh) + fh->fh_Arg2 = odfs_fh; + return return_dos_status(res2, err); } static int32 vp_close(struct FSVP *vp, int32 *res2, struct FileHandle *file) { - set_unsupported(vp, res2); - (void)file; - return DOSFALSE; + LONG err; + + err = odfs_handler_close_object(vp_global(vp), fh_from_vector(file)); + if (err == 0 && file) + file->fh_Arg2 = NULL; + return return_dos_status(res2, err); } static int32 vp_delete(struct FSVP *vp, @@ -140,11 +264,13 @@ static int32 vp_read(struct FSVP *vp, STRPTR buffer, int32 numbytes) { - set_unsupported(vp, res2); - (void)file; - (void)buffer; - (void)numbytes; - return 0; + LONG actual = 0; + LONG err; + + err = odfs_handler_read_object(vp_global(vp), fh_from_vector(file), + buffer, numbytes, &actual); + set_dos_error(res2, err); + return err == 0 ? actual : -1; } static int32 vp_write(struct FSVP *vp, @@ -162,8 +288,9 @@ static int32 vp_write(struct FSVP *vp, static int32 vp_flush(struct FSVP *vp, int32 *res2) { - set_unsupported(vp, res2); - return DOSFALSE; + (void)vp; + set_dos_error(res2, 0); + return DOSTRUE; } static int32 vp_change_file_position(struct FSVP *vp, @@ -172,11 +299,12 @@ static int32 vp_change_file_position(struct FSVP *vp, int32 mode, int64 position) { - set_unsupported(vp, res2); - (void)file; - (void)mode; - (void)position; - return DOSFALSE; + int64_t oldpos; + + return return_dos_status(res2, + odfs_handler_seek_object( + vp_global(vp), fh_from_vector(file), + position, mode, &oldpos)); } static int32 vp_change_file_size(struct FSVP *vp, @@ -196,18 +324,26 @@ static int64 vp_get_file_position(struct FSVP *vp, int32 *res2, struct FileHandle *file) { - set_unsupported(vp, res2); - (void)file; - return 0; + int64_t pos; + LONG err; + + err = odfs_handler_get_file_position(vp_global(vp), + fh_from_vector(file), &pos); + set_dos_error(res2, err); + return err == 0 ? pos : -1; } static int64 vp_get_file_size(struct FSVP *vp, int32 *res2, struct FileHandle *file) { - set_unsupported(vp, res2); - (void)file; - return 0; + int64_t size; + LONG err; + + err = odfs_handler_get_file_size(vp_global(vp), + fh_from_vector(file), &size); + set_dos_error(res2, err); + return err == 0 ? size : -1; } static int32 vp_change_lock_mode(struct FSVP *vp, @@ -358,10 +494,15 @@ static int32 vp_same_lock(struct FSVP *vp, struct Lock *lock1, struct Lock *lock2) { - set_unsupported(vp, res2); - (void)lock1; - (void)lock2; - return DOSFALSE; + LONG same = LOCK_DIFFERENT; + LONG err; + + err = odfs_handler_same_lock_object(vp_global(vp), + lock_from_vector(lock1), + lock_from_vector(lock2), + &same); + set_dos_error(res2, err); + return err == 0 ? same : LOCK_DIFFERENT; } static int32 vp_same_file(struct FSVP *vp, @@ -369,10 +510,15 @@ static int32 vp_same_file(struct FSVP *vp, struct FileHandle *fh1, struct FileHandle *fh2) { - set_unsupported(vp, res2); - (void)fh1; - (void)fh2; - return DOSFALSE; + LONG same = LOCK_DIFFERENT; + LONG err; + + err = odfs_handler_same_file_object(vp_global(vp), + fh_from_vector(fh1), + fh_from_vector(fh2), + &same); + set_dos_error(res2, err); + return err == 0 ? same : LOCK_DIFFERENT; } static int32 vp_filesystem_attr(struct FSVP *vp, @@ -388,18 +534,18 @@ static int32 vp_volume_info_data(struct FSVP *vp, int32 *res2, struct InfoData *info) { - set_unsupported(vp, res2); - (void)info; - return DOSFALSE; + return return_dos_status(res2, + odfs_handler_fill_info(vp_global(vp), NULL, + info)); } static int32 vp_device_info_data(struct FSVP *vp, int32 *res2, struct InfoData *info) { - set_unsupported(vp, res2); - (void)info; - return DOSFALSE; + return return_dos_status(res2, + odfs_handler_fill_info(vp_global(vp), NULL, + info)); } static struct ExamineData *vp_examine_obj(struct FSVP *vp, @@ -407,28 +553,62 @@ static struct ExamineData *vp_examine_obj(struct FSVP *vp, struct Lock *lock, CONST_STRPTR object) { - set_unsupported(vp, res2); - (void)lock; - (void)object; - return NULL; + handler_global_t *g = vp_global(vp); + odfs_lock_t *ol = NULL; + struct ExamineData *ed; + LONG err; + + err = odfs_handler_lock_object(g, lock_from_vector(lock), + object ? object : "", SHARED_LOCK, &ol); + if (err != 0) { + set_dos_error(res2, err); + return NULL; + } + + ed = alloc_examine_data(g, ol->entry ? &ol->entry->fnode : NULL); + (void)odfs_handler_free_lock_object(g, ol); + 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) { - set_unsupported(vp, res2); - (void)lock; - return NULL; + handler_global_t *g = vp_global(vp); + const odfs_node_t *node = NULL; + struct ExamineData *ed; + LONG err; + + err = odfs_handler_get_lock_node(g, lock_from_vector(lock), &node); + if (err != 0) { + set_dos_error(res2, err); + return NULL; + } + + ed = alloc_examine_data(g, node); + 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) { - set_unsupported(vp, res2); - (void)file; - return NULL; + handler_global_t *g = vp_global(vp); + const odfs_node_t *node = NULL; + struct ExamineData *ed; + LONG err; + + err = odfs_handler_get_fh_node(g, fh_from_vector(file), &node); + if (err != 0) { + set_dos_error(res2, err); + return NULL; + } + + ed = alloc_examine_data(g, node); + set_dos_error(res2, ed ? 0 : ERROR_NO_FREE_STORE); + return ed; } static int32 vp_examine_dir(struct FSVP *vp, From f2a0277a810d9c3c33d8bdb5bdd8af8a159aab0a Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Thu, 21 May 2026 15:38:31 -0700 Subject: [PATCH 05/36] amiga: finish read-only OS4 vector stubs Wire native OS4 vector callbacks for inhibit, serialize, and lock or file mode changes into the shared handler operations. Return ERROR_DISK_WRITE_PROTECTED from mutating vectors so DOS sees a deliberate read-only filesystem result instead of missing support. --- platform/amiga/handler.h | 7 +++ platform/amiga/handler_main.c | 77 ++++++++++++++++++++++++++++---- platform/amiga/os4/vector_port.c | 67 +++++++++++++++------------ 3 files changed, 113 insertions(+), 38 deletions(-) diff --git a/platform/amiga/handler.h b/platform/amiga/handler.h index c45fe6f..bb70731 100644 --- a/platform/amiga/handler.h +++ b/platform/amiga/handler.h @@ -252,6 +252,12 @@ LONG odfs_handler_seek_object(handler_global_t *g, 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); @@ -270,6 +276,7 @@ LONG odfs_handler_get_fh_node(handler_global_t *g, ULONG odfs_handler_node_key(const odfs_node_t *node); ULONG odfs_handler_node_protection(const odfs_node_t *node); void odfs_handler_node_date(const odfs_node_t *node, struct DateStamp *ds); +LONG odfs_handler_inhibit(handler_global_t *g, LONG state); /* handler entry point (called from startup.S) */ void handler_main(void); diff --git a/platform/amiga/handler_main.c b/platform/amiga/handler_main.c index 2b5b09f..de5c692 100644 --- a/platform/amiga/handler_main.c +++ b/platform/amiga/handler_main.c @@ -1297,6 +1297,9 @@ static void free_volume(odfs_volume_t *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; @@ -2203,6 +2206,49 @@ LONG odfs_handler_get_file_position(handler_global_t *g, 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; + } + + ol->lock.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) @@ -2307,6 +2353,22 @@ LONG odfs_handler_get_fh_node(handler_global_t *g, 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 */ /* ------------------------------------------------------------------ */ @@ -3034,17 +3096,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; } diff --git a/platform/amiga/os4/vector_port.c b/platform/amiga/os4/vector_port.c index c37510c..adbc867 100644 --- a/platform/amiga/os4/vector_port.c +++ b/platform/amiga/os4/vector_port.c @@ -26,6 +26,14 @@ static void set_unsupported(struct FSVP *vp, int32 *res2) *res2 = ERROR_ACTION_NOT_KNOWN; } +static void set_write_protected(struct FSVP *vp, int32 *res2) +{ + (void)vp; + + if (res2) + *res2 = ERROR_DISK_WRITE_PROTECTED; +} + static handler_global_t *vp_global(struct FSVP *vp) { return vp ? (handler_global_t *)vp->FSV.FSPrivate : NULL; @@ -157,7 +165,7 @@ static struct Lock *vp_create_dir(struct FSVP *vp, struct Lock *rel_lock, CONST_STRPTR obj) { - set_unsupported(vp, res2); + set_write_protected(vp, res2); (void)rel_lock; (void)obj; return NULL; @@ -252,7 +260,7 @@ static int32 vp_delete(struct FSVP *vp, struct Lock *rel_dirlock, CONST_STRPTR obj) { - set_unsupported(vp, res2); + set_write_protected(vp, res2); (void)rel_dirlock; (void)obj; return DOSFALSE; @@ -279,11 +287,11 @@ static int32 vp_write(struct FSVP *vp, STRPTR buffer, int32 numbytes) { - set_unsupported(vp, res2); + set_write_protected(vp, res2); (void)file; (void)buffer; (void)numbytes; - return 0; + return -1; } static int32 vp_flush(struct FSVP *vp, int32 *res2) @@ -313,7 +321,7 @@ static int32 vp_change_file_size(struct FSVP *vp, int32 mode, int64 size) { - set_unsupported(vp, res2); + set_write_protected(vp, res2); (void)file; (void)mode; (void)size; @@ -351,10 +359,10 @@ static int32 vp_change_lock_mode(struct FSVP *vp, struct Lock *lock, int32 new_lock_mode) { - set_unsupported(vp, res2); - (void)lock; - (void)new_lock_mode; - return DOSFALSE; + return return_dos_status(res2, + odfs_handler_change_lock_mode( + vp_global(vp), lock_from_vector(lock), + new_lock_mode)); } static int32 vp_change_file_mode(struct FSVP *vp, @@ -362,10 +370,10 @@ static int32 vp_change_file_mode(struct FSVP *vp, struct FileHandle *fh, int32 new_lock_mode) { - set_unsupported(vp, res2); - (void)fh; - (void)new_lock_mode; - return DOSFALSE; + return return_dos_status(res2, + odfs_handler_change_file_mode( + vp_global(vp), fh_from_vector(fh), + new_lock_mode)); } static int32 vp_set_date(struct FSVP *vp, @@ -374,7 +382,7 @@ static int32 vp_set_date(struct FSVP *vp, CONST_STRPTR name, const struct DateStamp *ds) { - set_unsupported(vp, res2); + set_write_protected(vp, res2); (void)rel_dirlock; (void)name; (void)ds; @@ -387,7 +395,7 @@ static int32 vp_set_protection(struct FSVP *vp, CONST_STRPTR name, uint32 mask) { - set_unsupported(vp, res2); + set_write_protected(vp, res2); (void)rel_dirlock; (void)name; (void)mask; @@ -400,7 +408,7 @@ static int32 vp_set_comment(struct FSVP *vp, CONST_STRPTR name, CONST_STRPTR comment) { - set_unsupported(vp, res2); + set_write_protected(vp, res2); (void)rel_dirlock; (void)name; (void)comment; @@ -413,7 +421,7 @@ static int32 vp_set_group(struct FSVP *vp, CONST_STRPTR name, uint32 group) { - set_unsupported(vp, res2); + set_write_protected(vp, res2); (void)rel_dirlock; (void)name; (void)group; @@ -426,7 +434,7 @@ static int32 vp_set_user(struct FSVP *vp, CONST_STRPTR name, uint32 user) { - set_unsupported(vp, res2); + set_write_protected(vp, res2); (void)rel_dirlock; (void)name; (void)user; @@ -440,7 +448,7 @@ static int32 vp_rename(struct FSVP *vp, struct Lock *dst_rel, CONST_STRPTR dst) { - set_unsupported(vp, res2); + set_write_protected(vp, res2); (void)src_rel; (void)src; (void)dst_rel; @@ -454,7 +462,7 @@ static int32 vp_create_soft_link(struct FSVP *vp, CONST_STRPTR linkname, CONST_STRPTR dest_obj) { - set_unsupported(vp, res2); + set_write_protected(vp, res2); (void)rel_dirlock; (void)linkname; (void)dest_obj; @@ -467,7 +475,7 @@ static int32 vp_create_hard_link(struct FSVP *vp, CONST_STRPTR linkname, struct Lock *dest_obj) { - set_unsupported(vp, res2); + set_write_protected(vp, res2); (void)rel_dirlock; (void)linkname; (void)dest_obj; @@ -622,9 +630,9 @@ static int32 vp_examine_dir(struct FSVP *vp, static int32 vp_inhibit(struct FSVP *vp, int32 *res2, int32 inhibit_state) { - set_unsupported(vp, res2); - (void)inhibit_state; - return DOSFALSE; + return return_dos_status(res2, + odfs_handler_inhibit(vp_global(vp), + inhibit_state)); } static int32 vp_write_protect(struct FSVP *vp, @@ -632,7 +640,7 @@ static int32 vp_write_protect(struct FSVP *vp, int32 wp_state, uint32 passkey) { - set_unsupported(vp, res2); + set_write_protected(vp, res2); (void)wp_state; (void)passkey; return DOSFALSE; @@ -644,7 +652,7 @@ static int32 vp_format(struct FSVP *vp, uint32 dostype, uint32 spare) { - set_unsupported(vp, res2); + set_write_protected(vp, res2); (void)new_volname; (void)dostype; (void)spare; @@ -653,15 +661,16 @@ static int32 vp_format(struct FSVP *vp, static int32 vp_serialize(struct FSVP *vp, int32 *res2) { - set_unsupported(vp, res2); - return DOSFALSE; + (void)vp; + set_dos_error(res2, 0); + return DOSTRUE; } static int32 vp_relabel(struct FSVP *vp, int32 *res2, CONST_STRPTR new_volumename) { - set_unsupported(vp, res2); + set_write_protected(vp, res2); (void)new_volumename; return DOSFALSE; } From fff73ce24b5e93a5a964789a69a1e17c8190d4b8 Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Thu, 21 May 2026 15:57:08 -0700 Subject: [PATCH 06/36] amiga: implement OS4 directory examine vector Add a shared handler operation that returns the next directory entry by resume key, matching the existing packet iteration rules. Use it from FSExamineDir to allocate native ExamineData entries, recycle stale context nodes when possible, and append results to the OS4 examine context FreshNodeList. --- platform/amiga/handler.h | 5 ++ platform/amiga/handler_main.c | 84 ++++++++++++++++++++++++++++++++ platform/amiga/os4/vector_port.c | 82 ++++++++++++++++++++++++++----- 3 files changed, 158 insertions(+), 13 deletions(-) diff --git a/platform/amiga/handler.h b/platform/amiga/handler.h index bb70731..a6e3a59 100644 --- a/platform/amiga/handler.h +++ b/platform/amiga/handler.h @@ -273,6 +273,11 @@ LONG odfs_handler_get_lock_node(handler_global_t *g, 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); ULONG odfs_handler_node_key(const odfs_node_t *node); ULONG odfs_handler_node_protection(const odfs_node_t *node); void odfs_handler_node_date(const odfs_node_t *node, struct DateStamp *ds); diff --git a/platform/amiga/handler_main.c b/platform/amiga/handler_main.c index de5c692..bd09f0c 100644 --- a/platform/amiga/handler_main.c +++ b/platform/amiga/handler_main.c @@ -2555,6 +2555,90 @@ static odfs_err_t exnext_cb(const odfs_node_t *entry, void *ctx) return ODFS_ERR_EOF; /* stop after one entry */ } +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; + + dir = ol ? lock_node(ol) : &g->mount.root; + + if (ol) { + LONG err_dos = validate_object_volume(g, ol->entry->volume); + if (err_dos != 0) + return err_dos; + } else if (!g->mounted) { + return ERROR_NO_DISK; + } + + 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) { odfs_lock_t *ol = LOCK_FROM_BPTR(pkt->dp_Arg1); diff --git a/platform/amiga/os4/vector_port.c b/platform/amiga/os4/vector_port.c index adbc867..beff972 100644 --- a/platform/amiga/os4/vector_port.c +++ b/platform/amiga/os4/vector_port.c @@ -10,10 +10,12 @@ #include #include +#include #include #include #include +#include #include #include @@ -69,8 +71,34 @@ static const char *node_name_for_examine(handler_global_t *g, return node ? node->name : ""; } -static struct ExamineData *alloc_examine_data(handler_global_t *g, - const odfs_node_t *node) +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 = ""; @@ -88,17 +116,21 @@ static struct ExamineData *alloc_examine_data(handler_global_t *g, name_len = strlen(name) + 1u; comment_len = strlen(comment) + 1u; - ed = AllocDosObjectTags(DOS_EXAMINEDATA, - ADO_ExamineData_NameSize, - (ULONG)name_len, - ADO_ExamineData_CommentSize, - (ULONG)comment_len, - ADO_ExamineData_LinkSize, - 1UL, - TAG_END); + ed = take_stale_examine_data(ctx, name_len, comment_len); + if (!ed) { + ed = AllocDosObjectTags(DOS_EXAMINEDATA, + ADO_ExamineData_NameSize, + (ULONG)name_len, + ADO_ExamineData_CommentSize, + (ULONG)comment_len, + ADO_ExamineData_LinkSize, + 1UL, + TAG_END); + } if (!ed) return NULL; + ed->EXDinfo = 0; ed->Type = (node->kind == ODFS_NODE_DIR) ? FSO_TYPE_DIRECTORY : FSO_TYPE_FILE; ed->FileSize = (node->kind == ODFS_NODE_DIR) ? -1LL : (int64)node->size; @@ -124,6 +156,12 @@ static struct ExamineData *alloc_examine_data(handler_global_t *g, 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, @@ -623,9 +661,27 @@ static int32 vp_examine_dir(struct FSVP *vp, int32 *res2, struct PRIVATE_ExamineDirContext *ctx) { - set_unsupported(vp, res2); - (void)ctx; - return DOSFALSE; + handler_global_t *g = vp_global(vp); + odfs_node_t entry; + ULONG key = 0; + struct ExamineData *ed; + LONG err; + + if (!ctx) + return return_dos_status(res2, ERROR_REQUIRED_ARG_MISSING); + + err = odfs_handler_next_dir_entry(g, lock_from_vector(ctx->ReferenceLock), + ctx->FSPrivate[0], &entry, &key); + if (err != 0) + return return_dos_status(res2, err); + + ed = alloc_examine_data_from_context(g, &entry, ctx); + 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) From e7f6199a2bb98562ba2c62140de6345da21f18d8 Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Thu, 11 Jun 2026 21:31:43 -0700 Subject: [PATCH 07/36] amiga: tighten Amiga build configuration Make always defines CC, defaulting to "cc", so the conditional assignment "CC ?= m68k-amigaos-gcc" never took effect and a plain "make amiga" tried to build the handler with the host compiler. Assign the m68k cross-compiler only when CC still carries make's built-in default, so command-line and environment overrides such as CC=ppc-amigaos-gcc keep selecting their own toolchain unchanged. The OS4 build no longer needs -Wno-error=deprecated-declarations now that deprecated Exec and DOS calls go through the per-OS sys_compat wrappers. Remove the suppression so the build fails again if a deprecated interface member sneaks back in. --- Makefile | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index f14e186..f6c9ef4 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,9 @@ # Amiga cross-compiler. # Override with CC=ppc-amigaos-gcc for an AmigaOS 4 PPC build. -CC ?= m68k-amigaos-gcc +ifeq ($(origin CC),default) +CC = m68k-amigaos-gcc +endif # AROS cross-compiler (override: make CC=m68k-aros-gcc AROS=1) # When AROS=1, uses -static instead of -noixemul and defines __AROS__ @@ -19,6 +21,7 @@ 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)),) @@ -133,7 +136,7 @@ else ifeq ($(AMIGA_TARGET),os4) AMIGA_CRT ?= newlib AMIGA_CPUFLAGS ?= -mcpu=powerpc AMIGA_SYSFLAGS ?= -mcrt=$(AMIGA_CRT) -AMIGA_WARNFLAGS = -Wno-error=deprecated-declarations +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. @@ -241,6 +244,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 @@ -259,7 +268,7 @@ all: host host: lib tests tools -amiga: $(HANDLER) +amiga: $(AMIGA_ARTIFACTS) @echo " $(HANDLER) built successfully" @size=$$(wc -c < "$(HANDLER)"); \ echo " Handler size: $$size bytes"; \ @@ -267,6 +276,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: @@ -466,6 +480,16 @@ $(HANDLER): $(AMIGA_ASM_OBJS) $(AMIGA_BUILD)/libodfs.a @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 ---- From 3b73730d66ed211a748826963a4e7ea83ba1ec72 Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Thu, 11 Jun 2026 21:32:17 -0700 Subject: [PATCH 08/36] amiga: publish OS4 vector port as the handler port On AmigaOS 4, dos.library only calls a filesystem through its native FileSystemVectors when the handler's port validates as a vector port. The handler replied to ACTION_STARTUP with the plain process message port, so even with the vector callbacks implemented, every DOS call was forced through legacy packet emulation. Allocate the FileSystemVectorPort during startup, attach a dedicated signal to its embedded message port, and verify the object with GetFileSystemVectorPort() before relying on it. Switch the handler port to the vector port before replying to the startup packet, so the device node, volume node, locks, and the packet loop all reference the port DOS validates. If activation fails, the startup packet is rejected and the port and signal are released; on shutdown the process port is restored before the handler exits. Track the owning process port separately from the active handler port in handler_global, and introduce an ODFS_AMIGA_OS4 macro in the per-target compat headers so shared code can test the build target without scattering __amigaos4__ checks. --- platform/amiga/handler.h | 8 +++ platform/amiga/handler_main.c | 91 +++++++++++++++++++++++- platform/amiga/os3/amiga_target_compat.h | 2 + platform/amiga/os4/amiga_target_compat.h | 2 + 4 files changed, 102 insertions(+), 1 deletion(-) diff --git a/platform/amiga/handler.h b/platform/amiga/handler.h index a6e3a59..462afee 100644 --- a/platform/amiga/handler.h +++ b/platform/amiga/handler.h @@ -24,6 +24,9 @@ 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; @@ -33,7 +36,12 @@ struct odfs_changeint_data { /* ---- 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 FileSystemVectorPort *vector_port; /* native OS4 vector port */ + LONG vector_sigbit; /* signal bit used by vector port */ +#endif struct DeviceNode *devnode; /* startup packet device node */ struct FileSysStartupMsg *fssm; /* startup packet FSSM */ struct DeviceNode *published_devnode; /* DOS device-list entry */ diff --git a/platform/amiga/handler_main.c b/platform/amiga/handler_main.c index bd09f0c..a56171b 100644 --- a/platform/amiga/handler_main.c +++ b/platform/amiga/handler_main.c @@ -11,6 +11,10 @@ #include "handler.h" #include "sys_compat.h" +#if ODFS_AMIGA_OS4 +#include "vector_port.h" +#endif + #if ODFS_FEATURE_CDDA #include "cdda/cdda.h" #endif @@ -47,6 +51,10 @@ 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); @@ -3489,6 +3497,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 */ /* ------------------------------------------------------------------ */ @@ -4032,6 +4098,9 @@ void handler_main_startup(struct Message *startup_msg) 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; +#endif g->chgsigbit = -1; g->toc_passthrough = -1; g->last_session_passthrough = -1; @@ -4040,7 +4109,8 @@ void handler_main_startup(struct Message *startup_msg) { struct Process *proc = (struct Process *)FindTask(NULL); - g->dosport = &proc->pr_MsgPort; + g->process_port = &proc->pr_MsgPort; + g->dosport = g->process_port; } /* wait for startup packet */ @@ -4180,6 +4250,21 @@ void handler_main_startup(struct Message *startup_msg) g->media.ops = &amiga_media_ops; g->media.ctx = &amctx; +#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; pkt->dp_Res2 = 0; @@ -4259,6 +4344,10 @@ void handler_main_startup(struct Message *startup_msg) if (g->devnode) g->devnode->dn_Task = NULL; +#if ODFS_AMIGA_OS4 + deactivate_vector_port(g); +#endif + odfs_amiga_close_libraries(); odfs_amiga_free_mem(g, sizeof(*g)); } diff --git a/platform/amiga/os3/amiga_target_compat.h b/platform/amiga/os3/amiga_target_compat.h index e4a1634..b02e803 100644 --- a/platform/amiga/os3/amiga_target_compat.h +++ b/platform/amiga/os3/amiga_target_compat.h @@ -7,4 +7,6 @@ #ifndef ODFS_AMIGA_TARGET_COMPAT_H #define ODFS_AMIGA_TARGET_COMPAT_H +#define ODFS_AMIGA_OS4 0 + #endif /* ODFS_AMIGA_TARGET_COMPAT_H */ diff --git a/platform/amiga/os4/amiga_target_compat.h b/platform/amiga/os4/amiga_target_compat.h index aa053b5..27be916 100644 --- a/platform/amiga/os4/amiga_target_compat.h +++ b/platform/amiga/os4/amiga_target_compat.h @@ -7,6 +7,8 @@ #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 From 01e0317907555e1bfe01a0a7e9b828b07c4c0c6d Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Thu, 11 Jun 2026 21:33:33 -0700 Subject: [PATCH 09/36] amiga: route OS4 serial debug logging through DebugPrintF The SERIAL_DEBUG log sink wrote bytes to the serial port with inline m68k assembly calling exec's RawPutChar vector, which cannot build for the PPC OS4 target. Keep the raw serial path for OS3 and use exec's DebugPrintF() on OS4, which ends up in the same serial/Sashimi debug stream. This makes the SERIAL_DEBUG=1 and PACKET_TRACE=1 build variants compile for OS4. --- platform/amiga/handler_main.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/platform/amiga/handler_main.c b/platform/amiga/handler_main.c index a56171b..20dafda 100644 --- a/platform/amiga/handler_main.c +++ b/platform/amiga/handler_main.c @@ -859,6 +859,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; @@ -876,6 +877,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) @@ -929,8 +931,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, From dfd86b16ff94996c529333c0555f4cffe574343a Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Thu, 11 Jun 2026 21:43:20 -0700 Subject: [PATCH 10/36] amiga: exit the packet loop on OS4 ACTION_SHUTDOWN AmigaOS 4 deprecates ACTION_DIE in favor of ACTION_SHUTDOWN (V51+), which DismountDevice() sends to ask a filesystem to remove its volume node and terminate. The handler only recognized ACTION_DIE, so an OS4 dismount would have been answered with ERROR_ACTION_NOT_KNOWN and the handler process would have stayed alive. Accept ACTION_SHUTDOWN wherever ACTION_DIE is accepted: reply DOSTRUE and leave the packet loop, which already unmounts the volume, drains outstanding locks and filehandles, and unpublishes the device node. The OS4 SDK defines no shutdown member in struct FileSystemVectors, so the packet loop remains the correct delivery path even with the native vector port active. The OS3 NDK does not define the packet number, so the OS3 target compat header supplies it; OS3 DOS never sends it, and accepting it unconditionally keeps the shared loop free of OS conditionals. --- platform/amiga/handler_main.c | 4 +++- platform/amiga/os3/amiga_target_compat.h | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/platform/amiga/handler_main.c b/platform/amiga/handler_main.c index 20dafda..b206ed4 100644 --- a/platform/amiga/handler_main.c +++ b/platform/amiga/handler_main.c @@ -3281,6 +3281,7 @@ static void handle_packet(handler_global_t *g, struct DosPacket *pkt) /* ---- shutdown ---- */ case ACTION_DIE: + case ACTION_SHUTDOWN: pkt->dp_Res1 = DOSTRUE; break; @@ -4306,7 +4307,8 @@ void handler_main_startup(struct Message *startup_msg) trace_pkt(g, "dequeue", pkt); #endif - if (pkt->dp_Type == ACTION_DIE) { + if (pkt->dp_Type == ACTION_DIE || + pkt->dp_Type == ACTION_SHUTDOWN) { pkt->dp_Res1 = DOSTRUE; pkt->dp_Res2 = 0; return_packet(g, pkt); diff --git a/platform/amiga/os3/amiga_target_compat.h b/platform/amiga/os3/amiga_target_compat.h index b02e803..d3ab2e7 100644 --- a/platform/amiga/os3/amiga_target_compat.h +++ b/platform/amiga/os3/amiga_target_compat.h @@ -9,4 +9,12 @@ #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 */ From ad16a04c310ef398c6531d4fa6bbf519285749d2 Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Thu, 11 Jun 2026 22:07:54 -0700 Subject: [PATCH 11/36] amiga: fix OS4 media-change interrupt calling convention The OS4 frontend cast the portable one-argument media-change callback directly into is_Code. On AmigaOS 4 the V50+ conventions documented in the exec autodocs pass is_Data as the third argument: Cause() soft interrupts: void handler(int32 unused, struct ExecBase *sysbase, APTR is_data); interrupt servers (AddIntServer): ULONG handler(struct ExceptionContext *ctx, struct ExecBase *sysbase, APTR userData); With the old cast, the callback's data parameter received zero (or the exception context) instead of the change-interrupt data, so the NULL check in the callback made the handler silently miss every media-change event fired through TD_ADDCHANGEINT. Install a V50-style trampoline that ignores the first two arguments and forwards is_Data to the portable callback. Both documented conventions place is_Data third, so one entry point covers either delivery path. This mirrors the a1 register-binding trampoline the OS3 frontend already uses. --- platform/amiga/os4/sys_compat.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/platform/amiga/os4/sys_compat.c b/platform/amiga/os4/sys_compat.c index bb7bdd8..28dd4ad 100644 --- a/platform/amiga/os4/sys_compat.c +++ b/platform/amiga/os4/sys_compat.c @@ -23,6 +23,24 @@ 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) { SysBase = *((struct ExecBase **)4L); @@ -169,11 +187,12 @@ void odfs_amiga_init_interrupt(struct Interrupt *intr, 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)code; + intr->is_Code = (void (*)(void))(APTR)odfs_amiga_interrupt_entry; } void odfs_amiga_set_fib_entry_type(struct FileInfoBlock *fib, LONG type) From 3bab958fe633b299b96b3f1ad234ef85f467e0e7 Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Thu, 11 Jun 2026 23:17:16 -0700 Subject: [PATCH 12/36] amiga: allocate DOS list nodes through the sys_compat boundary The handler built its published DeviceNode and the volume DeviceList node by hand: AllocMem of the structure plus a name buffer, then filling every field. On AmigaOS 4 those are compatibility layouts whose real definitions (struct DeviceNode, struct VolumeNode) carry a StructSize field and reserved members that DOS expects the allocator to initialize; the manual path left them zero. Add odfs_amiga_create_dos_entry()/odfs_amiga_delete_dos_entry() to the per-OS compatibility layer. The OS3 implementation keeps the manual allocation, including its deliberate avoidance of MakeDosEntry() so volume names with AmigaDOS metacharacters such as parentheses are not normalized. The OS4 implementation uses AllocDosObject(DOS_DOSLIST, ADO_Type, ADO_Name), which the dos autodoc documents as the supported allocator (MakeDosEntry() itself is a stub over it since V52.16) and which fills dol_StructSize and the V52 fields. AddDosEntry()/RemDosEntry()/AttemptLockDosList() are unchanged; they remain current API on OS4. --- platform/amiga/common/sys_compat.h | 9 ++++++ platform/amiga/handler_main.c | 50 +++--------------------------- platform/amiga/os3/sys_compat.c | 39 +++++++++++++++++++++++ platform/amiga/os4/sys_compat.c | 18 +++++++++++ 4 files changed, 70 insertions(+), 46 deletions(-) diff --git a/platform/amiga/common/sys_compat.h b/platform/amiga/common/sys_compat.h index 7f91114..52d06c8 100644 --- a/platform/amiga/common/sys_compat.h +++ b/platform/amiga/common/sys_compat.h @@ -50,6 +50,15 @@ 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, diff --git a/platform/amiga/handler_main.c b/platform/amiga/handler_main.c index b206ed4..76078b4 100644 --- a/platform/amiga/handler_main.c +++ b/platform/amiga/handler_main.c @@ -3377,33 +3377,18 @@ 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 = odfs_amiga_alloc_mem((ULONG)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; odfs_amiga_copy_device_lock(devnode, g->devnode); - devnode->dn_Name = MKBADDR(namebuf); sync_device_node(g, devnode); return devnode; @@ -3411,9 +3396,7 @@ static struct DeviceNode *create_device_node(handler_global_t *g) static void destroy_device_node(struct DeviceNode *devnode) { - if (!devnode) - return; - odfs_amiga_free_mem(devnode, sizeof(*devnode) + 32u); + odfs_amiga_delete_dos_entry(devnode); } static void publish_device_node(handler_global_t *g) @@ -3569,36 +3552,13 @@ static void deactivate_vector_port(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 = odfs_amiga_alloc_mem((ULONG)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; @@ -3606,9 +3566,7 @@ static struct DeviceList *create_volume_node(handler_global_t *g) static void destroy_volume_node(struct DeviceList *volnode) { - if (!volnode) - return; - odfs_amiga_free_mem(volnode, sizeof(*volnode) + 32u); + odfs_amiga_delete_dos_entry(volnode); } static void detach_volume_node(struct DeviceList *volnode) diff --git a/platform/amiga/os3/sys_compat.c b/platform/amiga/os3/sys_compat.c index c4d7a7d..8216352 100644 --- a/platform/amiga/os3/sys_compat.c +++ b/platform/amiga/os3/sys_compat.c @@ -10,6 +10,8 @@ #include #include +#include + struct ExecBase *SysBase; struct DosLibrary *DOSBase; struct Library *UtilityBase; @@ -115,6 +117,43 @@ void odfs_amiga_free_signal(LONG num) 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, diff --git a/platform/amiga/os4/sys_compat.c b/platform/amiga/os4/sys_compat.c index 28dd4ad..dbe6f9f 100644 --- a/platform/amiga/os4/sys_compat.c +++ b/platform/amiga/os4/sys_compat.c @@ -6,6 +6,7 @@ #include "sys_compat.h" +#include #include #include #include @@ -182,6 +183,23 @@ void odfs_amiga_free_signal(LONG num) 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, From 81b33c5b76aad109e8d498b6370d11a7cc468380 Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Thu, 11 Jun 2026 23:32:28 -0700 Subject: [PATCH 13/36] amiga: serialize OS4 vector callbacks and emulate direct packets The OS4 dospackets autodoc states that vector-port functions are invoked from the calling process context, like library calls, and that all access to filesystem state from them must be protected by a semaphore. The vector callbacks previously called the shared handler operations with no locking, racing against each other and against the handler process (packet dispatch and media-change handling) over the lock list, filehandle list, block cache, and the single device IORequest and DMA bounce buffer. Add a SignalSemaphore to the handler globals on OS4. Every vector callback that touches handler state now holds it across the shared operation, and the handler process takes it around media-change handling and shutdown teardown. Pure stub callbacks that only return an error do not take it. Direct legacy packets are now routed through the DOSEmulatePacket() function that AllocDosObject() installs in the vector port, as the autodoc prescribes. The emulator performs the equivalent vector call under the same serialization as native DOS callers, so the handler process no longer mutates filesystem state through a second unsynchronized dispatch path. Packets are validated (non-null, dp_Link == msg) before being trusted, since hand-built packets and private messages can reach the port directly. The shared handle_packet() dispatch remains the OS3/AROS path and the OS4 fallback if the vector port is absent. Shutdown now follows the documented sequence: invalidate the vector port by zeroing FSV.Version so dos.library stops vectoring new callers, tear down DOS-visible state while holding the semaphore so in-flight calls finish first, and reply the shutdown packet only after teardown. This also moves the OS3 ACTION_DIE reply after teardown, which matches common classic handler practice. --- platform/amiga/handler.h | 7 ++ platform/amiga/handler_main.c | 59 ++++++++- platform/amiga/os4/vector_port.c | 198 ++++++++++++++++++++++++------- platform/amiga/os4/vector_port.h | 16 +++ 4 files changed, 231 insertions(+), 49 deletions(-) diff --git a/platform/amiga/handler.h b/platform/amiga/handler.h index 462afee..a4d44bf 100644 --- a/platform/amiga/handler.h +++ b/platform/amiga/handler.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,12 @@ typedef struct handler_global { #if ODFS_AMIGA_OS4 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 struct DeviceNode *devnode; /* startup packet device node */ struct FileSysStartupMsg *fssm; /* startup packet FSSM */ diff --git a/platform/amiga/handler_main.c b/platform/amiga/handler_main.c index 76078b4..c9cb004 100644 --- a/platform/amiga/handler_main.c +++ b/platform/amiga/handler_main.c @@ -13,6 +13,15 @@ #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 @@ -4041,6 +4050,7 @@ 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; @@ -4065,6 +4075,7 @@ void handler_main_startup(struct Message *startup_msg) 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; @@ -4252,7 +4263,9 @@ void handler_main_startup(struct Message *startup_msg) /* 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; } @@ -4265,15 +4278,36 @@ void handler_main_startup(struct Message *startup_msg) trace_pkt(g, "dequeue", pkt); #endif +#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) { - pkt->dp_Res1 = DOSTRUE; - pkt->dp_Res2 = 0; - return_packet(g, pkt); + shutdown_pkt = pkt; running = 0; break; } +#if ODFS_AMIGA_OS4 + if (g->vector_port) { + /* + * Route direct legacy packets through the DOS packet + * emulator, which performs the equivalent vector-port + * call exactly like a native DOS caller would. + */ + odfs_os4_emulate_packet(g->vector_port, pkt); + return_packet(g, pkt); + continue; + } +#endif + if (!g->mounted && packet_needs_live_mount(pkt)) { pkt->dp_Res1 = DOSFALSE; pkt->dp_Res2 = ERROR_NO_DISK; @@ -4287,11 +4321,28 @@ void handler_main_startup(struct Message *startup_msg) } } - /* ---- 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 (g->devreq) { diff --git a/platform/amiga/os4/vector_port.c b/platform/amiga/os4/vector_port.c index beff972..4eccf8e 100644 --- a/platform/amiga/os4/vector_port.c +++ b/platform/amiga/os4/vector_port.c @@ -41,6 +41,23 @@ 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 void set_dos_error(int32 *res2, LONG err) { if (res2) @@ -172,28 +189,36 @@ static struct Lock *vp_lock(struct FSVP *vp, odfs_lock_t *ol = NULL; LONG err; + fs_lock(g); err = odfs_handler_lock_object(g, lock_from_vector(rel_lock), obj ? obj : "", mode, &ol); + fs_unlock(g); set_dos_error(res2, err); return (struct Lock *)LOCK_TO_PTR(ol); } static int32 vp_unlock(struct FSVP *vp, int32 *res2, struct Lock *lock) { - return return_dos_status(res2, - odfs_handler_free_lock_object( - vp_global(vp), lock_from_vector(lock))); + handler_global_t *g = vp_global(vp); + LONG err; + + 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_global(vp); odfs_lock_t *ol = NULL; LONG err; - err = odfs_handler_dup_lock_object(vp_global(vp), - lock_from_vector(lock), &ol); + fs_lock(g); + err = odfs_handler_dup_lock_object(g, lock_from_vector(lock), &ol); + fs_unlock(g); set_dos_error(res2, err); return (struct Lock *)LOCK_TO_PTR(ol); } @@ -213,12 +238,14 @@ static struct Lock *vp_parent_dir(struct FSVP *vp, int32 *res2, struct Lock *dirlock) { + handler_global_t *g = vp_global(vp); odfs_lock_t *parent = NULL; LONG err; - err = odfs_handler_parent_lock_object(vp_global(vp), - lock_from_vector(dirlock), + 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); } @@ -227,11 +254,13 @@ static struct Lock *vp_dup_lock_from_fh(struct FSVP *vp, int32 *res2, struct FileHandle *filehandle) { + handler_global_t *g = vp_global(vp); odfs_lock_t *ol = NULL; LONG err; - err = odfs_handler_dup_lock_from_fh(vp_global(vp), - fh_from_vector(filehandle), &ol); + 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); } @@ -241,12 +270,14 @@ static int32 vp_open_from_lock(struct FSVP *vp, struct FileHandle *file, struct Lock *lock) { + handler_global_t *g = vp_global(vp); odfs_fh_t *odfs_fh = NULL; LONG err; - err = odfs_handler_open_from_lock_object(vp_global(vp), - lock_from_vector(lock), + 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_Arg2 = odfs_fh; return return_dos_status(res2, err); @@ -256,11 +287,13 @@ static struct Lock *vp_parent_of_fh(struct FSVP *vp, int32 *res2, struct FileHandle *file) { + handler_global_t *g = vp_global(vp); odfs_lock_t *parent = NULL; LONG err; - err = odfs_handler_parent_fh_object(vp_global(vp), - fh_from_vector(file), &parent); + 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); } @@ -272,12 +305,14 @@ static int32 vp_open(struct FSVP *vp, CONST_STRPTR obj, int32 mode) { + handler_global_t *g = vp_global(vp); odfs_fh_t *odfs_fh = NULL; LONG err; - err = odfs_handler_open_object(vp_global(vp), - lock_from_vector(rel_dir), + fs_lock(g); + err = odfs_handler_open_object(g, lock_from_vector(rel_dir), obj ? obj : "", mode, &odfs_fh); + fs_unlock(g); if (err == 0 && fh) fh->fh_Arg2 = odfs_fh; return return_dos_status(res2, err); @@ -285,9 +320,12 @@ static int32 vp_open(struct FSVP *vp, static int32 vp_close(struct FSVP *vp, int32 *res2, struct FileHandle *file) { + handler_global_t *g = vp_global(vp); LONG err; - err = odfs_handler_close_object(vp_global(vp), fh_from_vector(file)); + fs_lock(g); + err = odfs_handler_close_object(g, fh_from_vector(file)); + fs_unlock(g); if (err == 0 && file) file->fh_Arg2 = NULL; return return_dos_status(res2, err); @@ -310,11 +348,14 @@ static int32 vp_read(struct FSVP *vp, STRPTR buffer, int32 numbytes) { + handler_global_t *g = vp_global(vp); LONG actual = 0; LONG err; - err = odfs_handler_read_object(vp_global(vp), fh_from_vector(file), + 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; } @@ -345,12 +386,15 @@ static int32 vp_change_file_position(struct FSVP *vp, int32 mode, int64 position) { + handler_global_t *g = vp_global(vp); int64_t oldpos; + LONG err; - return return_dos_status(res2, - odfs_handler_seek_object( - vp_global(vp), fh_from_vector(file), - position, mode, &oldpos)); + 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, @@ -370,11 +414,13 @@ static int64 vp_get_file_position(struct FSVP *vp, int32 *res2, struct FileHandle *file) { + handler_global_t *g = vp_global(vp); int64_t pos; LONG err; - err = odfs_handler_get_file_position(vp_global(vp), - fh_from_vector(file), &pos); + 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; } @@ -383,11 +429,13 @@ static int64 vp_get_file_size(struct FSVP *vp, int32 *res2, struct FileHandle *file) { + handler_global_t *g = vp_global(vp); int64_t size; LONG err; - err = odfs_handler_get_file_size(vp_global(vp), - fh_from_vector(file), &size); + 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; } @@ -397,10 +445,14 @@ static int32 vp_change_lock_mode(struct FSVP *vp, struct Lock *lock, int32 new_lock_mode) { - return return_dos_status(res2, - odfs_handler_change_lock_mode( - vp_global(vp), lock_from_vector(lock), - new_lock_mode)); + handler_global_t *g = vp_global(vp); + LONG err; + + 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, @@ -408,10 +460,14 @@ static int32 vp_change_file_mode(struct FSVP *vp, struct FileHandle *fh, int32 new_lock_mode) { - return return_dos_status(res2, - odfs_handler_change_file_mode( - vp_global(vp), fh_from_vector(fh), - new_lock_mode)); + handler_global_t *g = vp_global(vp); + LONG err; + + 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, @@ -540,13 +596,16 @@ static int32 vp_same_lock(struct FSVP *vp, struct Lock *lock1, struct Lock *lock2) { + handler_global_t *g = vp_global(vp); LONG same = LOCK_DIFFERENT; LONG err; - err = odfs_handler_same_lock_object(vp_global(vp), + 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_DIFFERENT; } @@ -556,13 +615,16 @@ static int32 vp_same_file(struct FSVP *vp, struct FileHandle *fh1, struct FileHandle *fh2) { + handler_global_t *g = vp_global(vp); LONG same = LOCK_DIFFERENT; LONG err; - err = odfs_handler_same_file_object(vp_global(vp), + 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 : LOCK_DIFFERENT; } @@ -580,18 +642,26 @@ static int32 vp_volume_info_data(struct FSVP *vp, int32 *res2, struct InfoData *info) { - return return_dos_status(res2, - odfs_handler_fill_info(vp_global(vp), NULL, - info)); + handler_global_t *g = vp_global(vp); + LONG err; + + 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) { - return return_dos_status(res2, - odfs_handler_fill_info(vp_global(vp), NULL, - info)); + handler_global_t *g = vp_global(vp); + LONG err; + + 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, @@ -604,15 +674,18 @@ static struct ExamineData *vp_examine_obj(struct FSVP *vp, struct ExamineData *ed; LONG err; + fs_lock(g); err = odfs_handler_lock_object(g, lock_from_vector(lock), object ? object : "", SHARED_LOCK, &ol); if (err != 0) { + fs_unlock(g); set_dos_error(res2, err); return NULL; } ed = alloc_examine_data(g, ol->entry ? &ol->entry->fnode : NULL); (void)odfs_handler_free_lock_object(g, ol); + fs_unlock(g); set_dos_error(res2, ed ? 0 : ERROR_NO_FREE_STORE); return ed; } @@ -626,13 +699,16 @@ static struct ExamineData *vp_examine_lock(struct FSVP *vp, struct ExamineData *ed; LONG err; + fs_lock(g); err = odfs_handler_get_lock_node(g, lock_from_vector(lock), &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; } @@ -646,13 +722,16 @@ static struct ExamineData *vp_examine_file(struct FSVP *vp, struct ExamineData *ed; LONG err; + 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; } @@ -670,12 +749,16 @@ static int32 vp_examine_dir(struct FSVP *vp, 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); - if (err != 0) + 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); @@ -686,9 +769,13 @@ static int32 vp_examine_dir(struct FSVP *vp, static int32 vp_inhibit(struct FSVP *vp, int32 *res2, int32 inhibit_state) { - return return_dos_status(res2, - odfs_handler_inhibit(vp_global(vp), - inhibit_state)); + handler_global_t *g = vp_global(vp); + LONG err; + + 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, @@ -864,3 +951,24 @@ void odfs_os4_free_vector_port(struct FileSystemVectorPort *vp) if (vp) FreeDosObject(DOS_FSVECTORPORT, vp); } + +void odfs_os4_emulate_packet(struct FileSystemVectorPort *vp, + struct DosPacket *pkt) +{ + if (!pkt) + return; + + if (vp && vp->FSV.DOSEmulatePacket) { + vp->FSV.DOSEmulatePacket(vp, pkt); + return; + } + + pkt->dp_Res1 = DOSFALSE; + pkt->dp_Res2 = ERROR_ACTION_NOT_KNOWN; +} + +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 index 2b92cb8..33fb66f 100644 --- a/platform/amiga/os4/vector_port.h +++ b/platform/amiga/os4/vector_port.h @@ -10,8 +10,24 @@ #include #include +struct DosPacket; + const struct FileSystemVectors *odfs_os4_vector_template(void); struct FileSystemVectorPort *odfs_os4_alloc_vector_port(APTR fs_private); void odfs_os4_free_vector_port(struct FileSystemVectorPort *vp); +/* + * Route a direct legacy DosPacket through the DOS packet emulator that + * AllocDosObject() installed in the vector port. Results are placed in + * the packet; the caller still replies it. + */ +void odfs_os4_emulate_packet(struct FileSystemVectorPort *vp, + struct DosPacket *pkt); + +/* + * 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 */ From af1dcbeefe7060883a789cd7a26753a9e999fb8c Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Sat, 13 Jun 2026 16:34:30 -0700 Subject: [PATCH 14/36] amiga: add freestanding OS4 handler startup The OS4 filesystem handler must receive ACTION_STARTUP as its first process message. Newlib startup consumes that message while probing for a Workbench launch, which can deadlock the boot mount. Link the handler without C runtime startup, provide a small _start entry that initializes the Exec interface, and add the Kickstart Resident plus FileSysEntry registration used when the module loads from Kickstart.zip. Provide the small libc surface the freestanding link still needs and translate legacy allocation flags before passing them to AllocVecTags(). --- Makefile | 19 +++- include/odfs/alloc.h | 28 ++++- platform/amiga/common/sys_compat.h | 2 - platform/amiga/os3/sys_compat.c | 11 -- platform/amiga/os4/freestanding.c | 100 +++++++++++++++++ platform/amiga/os4/main.c | 17 ++- platform/amiga/os4/start.c | 173 +++++++++++++++++++++++++++++ platform/amiga/os4/sys_compat.c | 42 ++++--- 8 files changed, 355 insertions(+), 37 deletions(-) create mode 100644 platform/amiga/os4/freestanding.c create mode 100644 platform/amiga/os4/start.c diff --git a/Makefile b/Makefile index f6c9ef4..294d8da 100644 --- a/Makefile +++ b/Makefile @@ -135,15 +135,22 @@ HANDLER_LIBS = -nostdlib -Wl,-u,_exit -lgcc -lc -lgcc -lamiga -ramiga-dev else ifeq ($(AMIGA_TARGET),os4) AMIGA_CRT ?= newlib AMIGA_CPUFLAGS ?= -mcpu=powerpc -AMIGA_SYSFLAGS ?= -mcrt=$(AMIGA_CRT) +# 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 -HANDLER_LDFLAGS = -HANDLER_LIBS = $(LIBS) +# 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 AMIGA_CPUFLAGS ?= -m68000 -mtune=68020-60 -msoft-float AMIGA_SYSFLAGS ?= -noixemul @@ -201,11 +208,17 @@ AMIGA_SRCS = platform/amiga/handler_main.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 = diff --git a/include/odfs/alloc.h b/include/odfs/alloc.h index d3faca1..cdf372c 100644 --- a/include/odfs/alloc.h +++ b/include/odfs/alloc.h @@ -13,13 +13,25 @@ #if ODFS_PLATFORM_AMIGA -#include "sys_compat.h" +#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; - return odfs_amiga_alloc_vec((ULONG)size, MEMF_PUBLIC); +#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) @@ -33,12 +45,20 @@ static inline void *odfs_calloc(size_t count, size_t size) if (total == 0) total = 1; - return odfs_amiga_alloc_vec((ULONG)total, MEMF_PUBLIC | MEMF_CLEAR); +#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) { - odfs_amiga_free_vec(ptr); + if (ptr) + FreeVec(ptr); } #else diff --git a/platform/amiga/common/sys_compat.h b/platform/amiga/common/sys_compat.h index 52d06c8..b7efcd9 100644 --- a/platform/amiga/common/sys_compat.h +++ b/platform/amiga/common/sys_compat.h @@ -38,8 +38,6 @@ void odfs_amiga_close_libraries(void); void *odfs_amiga_alloc_mem(ULONG size, ULONG flags); void odfs_amiga_free_mem(void *ptr, ULONG size); -void *odfs_amiga_alloc_vec(ULONG size, ULONG flags); -void odfs_amiga_free_vec(void *ptr); struct MsgPort *odfs_amiga_create_msg_port(void); void odfs_amiga_delete_msg_port(struct MsgPort *port); diff --git a/platform/amiga/os3/sys_compat.c b/platform/amiga/os3/sys_compat.c index 8216352..f85ccff 100644 --- a/platform/amiga/os3/sys_compat.c +++ b/platform/amiga/os3/sys_compat.c @@ -72,17 +72,6 @@ void odfs_amiga_free_mem(void *ptr, ULONG size) FreeMem(ptr, size); } -void *odfs_amiga_alloc_vec(ULONG size, ULONG flags) -{ - return AllocVec(size, flags); -} - -void odfs_amiga_free_vec(void *ptr) -{ - if (ptr) - FreeVec(ptr); -} - struct MsgPort *odfs_amiga_create_msg_port(void) { return CreateMsgPort(); 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 index d87114b..2d33415 100644 --- a/platform/amiga/os4/main.c +++ b/platform/amiga/os4/main.c @@ -32,7 +32,9 @@ static void return_startup_packet(struct DosPacket *pkt, PutMsg(replyport, msg); } -int main(void) +int odfs_os4_handler_main(void); + +int odfs_os4_handler_main(void) { struct Process *proc; struct Message *msg; @@ -42,8 +44,16 @@ int main(void) 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; @@ -56,6 +66,11 @@ int main(void) } 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; 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 index dbe6f9f..68a2aa6 100644 --- a/platform/amiga/os4/sys_compat.c +++ b/platform/amiga/os4/sys_compat.c @@ -44,7 +44,14 @@ static void odfs_amiga_interrupt_entry(int32 unused, void odfs_amiga_init_sysbase(void) { - SysBase = *((struct ExecBase **)4L); + /* + * _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) @@ -110,36 +117,39 @@ void odfs_amiga_close_libraries(void) } } -void *odfs_amiga_alloc_vec(ULONG size, ULONG flags) +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, flags & ~MEMF_CLEAR, + AVT_Type, type, AVT_ClearWithValue, 0, TAG_END); } - return AllocVecTags(size, AVT_Type, flags, TAG_END); -} - -void odfs_amiga_free_vec(void *ptr) -{ - if (ptr) - FreeVec(ptr); -} - -void *odfs_amiga_alloc_mem(ULONG size, ULONG flags) -{ - return odfs_amiga_alloc_vec(size, flags); + return AllocVecTags(size, AVT_Type, type, TAG_END); } void odfs_amiga_free_mem(void *ptr, ULONG size) { (void)size; - odfs_amiga_free_vec(ptr); + if (ptr) + FreeVec(ptr); } struct MsgPort *odfs_amiga_create_msg_port(void) From dd2453cfa84cccbb98c09bdb5d693847b23b906a Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Sat, 13 Jun 2026 16:39:19 -0700 Subject: [PATCH 15/36] amiga: keep media state per handler Diskboot starts one filesystem handler process per CD unit. The media callback context was static, so a later process could replace the device binding used by an earlier mounted volume. Move the media callback context into handler_global and pass that per-process instance to the core media layer. Reinitialize the same context after media-change remounts. --- platform/amiga/handler.h | 13 +++++++++++++ platform/amiga/handler_main.c | 12 +++++------- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/platform/amiga/handler.h b/platform/amiga/handler.h index a4d44bf..0a4aeba 100644 --- a/platform/amiga/handler.h +++ b/platform/amiga/handler.h @@ -34,6 +34,18 @@ struct odfs_changeint_data { ULONG sigmask; }; +/* + * 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; + /* ---- handler globals ---- */ typedef struct handler_global { @@ -49,6 +61,7 @@ typedef struct handler_global { */ 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 */ diff --git a/platform/amiga/handler_main.c b/platform/amiga/handler_main.c index c9cb004..e38a881 100644 --- a/platform/amiga/handler_main.c +++ b/platform/amiga/handler_main.c @@ -107,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) { @@ -4055,7 +4054,6 @@ void handler_main_startup(struct Message *startup_msg) 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 */ @@ -4222,9 +4220,9 @@ void handler_main_startup(struct Message *startup_msg) } /* set up media adapter */ - amctx.g = g; + 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 { @@ -4267,7 +4265,7 @@ void handler_main_startup(struct Message *startup_msg) handle_media_change(g); ODFS_FS_UNLOCK(g); /* re-init media adapter after remount */ - amctx.g = g; + g->media_ctx.g = g; } /* DOS packets */ From ca688c0e3c5e0701598463ddfa8b11620c3abb9d Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Sat, 13 Jun 2026 16:43:40 -0700 Subject: [PATCH 16/36] amiga: probe OS4 CD units before mounting Some OS4 device paths report phantom ATAPI units that open but hang when probed through SCSI commands. Mounting those units publishes a dead volume that DOS can keep polling. Answer allocation failures on the startup packet, remove the TEST UNIT READY probe, and use TD_GETGEOMETRY before publishing the volume. Only issue MODE SELECT after geometry proves that a usable drive is present. --- platform/amiga/handler_main.c | 119 +++++++++++++++++++--------------- 1 file changed, 68 insertions(+), 51 deletions(-) diff --git a/platform/amiga/handler_main.c b/platform/amiga/handler_main.c index e38a881..1195bf4 100644 --- a/platform/amiga/handler_main.c +++ b/platform/amiga/handler_main.c @@ -752,44 +752,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. * @@ -4060,8 +4022,24 @@ void handler_main_startup(struct Message *startup_msg) odfs_amiga_init_sysbase(); g = odfs_amiga_alloc_mem(sizeof(*g), MEMF_PUBLIC | MEMF_CLEAR); - if (!g) + 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 = odfs_amiga_sysbase(); g->locklist.mlh_Head = (struct MinNode *)&g->locklist.mlh_Tail; @@ -4136,6 +4114,8 @@ void handler_main_startup(struct Message *startup_msg) return; } 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 = odfs_amiga_create_msg_port(); @@ -4161,6 +4141,8 @@ void handler_main_startup(struct Message *startup_msg) 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, @@ -4173,21 +4155,11 @@ void handler_main_startup(struct Message *startup_msg) 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). @@ -4219,7 +4191,52 @@ void handler_main_startup(struct Message *startup_msg) } } - /* set up media adapter */ + /* + * 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 = &g->media_ctx; From 1f9f6d4a3b734fce6a71bc10081da0a2aa7bb30a Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Sat, 13 Jun 2026 16:44:26 -0700 Subject: [PATCH 17/36] amiga: allocate OS4 locks through DOS Workbench and native OS4 filesystem vectors expect lock objects with the OS4 DOS lock layout. The classic handler embedded a FileLock in its private lock wrapper, which leaves OS4-only fields uninitialized. Store a DOS-allocated struct Lock in each ODFS lock on OS4, keep the classic embedded FileLock on OS3, and convert through fl_FSPrivate1. Free the DOS lock when the ODFS lock is released or drained. --- platform/amiga/handler.h | 33 ++++++++++++++++ platform/amiga/handler_main.c | 73 ++++++++++++++++++++++++++++------- 2 files changed, 92 insertions(+), 14 deletions(-) diff --git a/platform/amiga/handler.h b/platform/amiga/handler.h index 0a4aeba..dcaaa8b 100644 --- a/platform/amiga/handler.h +++ b/platform/amiga/handler.h @@ -34,6 +34,9 @@ struct odfs_changeint_data { 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 @@ -144,12 +147,17 @@ 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 */ +#endif odfs_entry_t *entry; /* shared object metadata */ ULONG key; /* unique key */ }; +#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 @@ -157,6 +165,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 ---- */ @@ -169,6 +178,29 @@ struct odfs_fh { /* ---- 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) - \ @@ -186,6 +218,7 @@ struct odfs_fh { /* 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) diff --git a/platform/amiga/handler_main.c b/platform/amiga/handler_main.c index 1195bf4..3d80084 100644 --- a/platform/amiga/handler_main.c +++ b/platform/amiga/handler_main.c @@ -1052,12 +1052,12 @@ static void rebuild_volume_locklist(handler_global_t *g, odfs_volume_t *volume) if (!head) head = LOCK_TO_BPTR(ol); if (prev) - prev->lock.fl_Link = LOCK_TO_BPTR(ol); + ODFS_LOCK_DOS(prev)->fl_Link = LOCK_TO_BPTR(ol); prev = ol; } if (prev) - prev->lock.fl_Link = 0; + ODFS_LOCK_DOS(prev)->fl_Link = 0; volume->volnode->dl_LockList = head; Permit(); } @@ -1297,6 +1297,10 @@ static void drain_all_objects(handler_global_t *g) while ((node = RemHead((struct List *)&g->locklist)) != NULL) { odfs_lock_t *ol = (odfs_lock_t *)node; +#if ODFS_AMIGA_OS4 + if (ol->lock) + FreeDosObject(DOS_LOCK, ol->lock); +#endif release_volume_object(g, ol->entry->volume); release_entry(ol->entry); odfs_amiga_free_mem(ol, sizeof(*ol)); @@ -1339,6 +1343,7 @@ static odfs_lock_t *alloc_lock(handler_global_t *g, { odfs_lock_t *ol; odfs_entry_t *entry; + struct FileLock *lock; if (!g->current_volume) return NULL; @@ -1352,16 +1357,33 @@ static odfs_lock_t *alloc_lock(handler_global_t *g, 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); @@ -1375,6 +1397,10 @@ static void free_lock(handler_global_t *g, odfs_lock_t *ol) return; 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); odfs_amiga_free_mem(ol, sizeof(*ol)); @@ -1383,6 +1409,7 @@ static void free_lock(handler_global_t *g, odfs_lock_t *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; @@ -1391,15 +1418,31 @@ static odfs_lock_t *dup_lock(handler_global_t *g, odfs_lock_t *src) 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); @@ -2073,7 +2116,7 @@ LONG odfs_handler_open_from_lock_object(handler_global_t *g, return err_dos; } - fh = alloc_fh(g, ol->entry, ol->lock.fl_Access); + fh = alloc_fh(g, ol->entry, ODFS_LOCK_DOS(ol)->fl_Access); if (!fh) return ERROR_NO_FREE_STORE; @@ -2207,7 +2250,7 @@ LONG odfs_handler_change_lock_mode(handler_global_t *g, return err_dos; } - ol->lock.fl_Access = + ODFS_LOCK_DOS(ol)->fl_Access = (lock_node(ol)->kind == ODFS_NODE_DIR) ? SHARED_LOCK : mode; return 0; } @@ -2646,8 +2689,10 @@ static void action_examine_object(handler_global_t *g, struct DosPacket *pkt) fill_root_fib(g, fib, fnode); else fill_fib(fib, fnode); +#if !ODFS_AMIGA_OS4 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, From 1143853be2730f3e759fb8be40de3fbf32fdf914 Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Sat, 13 Jun 2026 16:47:05 -0700 Subject: [PATCH 18/36] amiga: complete OS4 Workbench vector semantics Workbench exercises native filesystem vectors more strictly than shell packet paths. Several callbacks returned classic packet values, left fh_Arg1 empty, or allocated ExamineData outside the directory context that DOS owns. Return native boolean SameLock and SameFile values, preserve fh_Arg1 together with fh_Arg2, implement FileSystemAttr queries, bind ExamineDir data to the DOS context, and reject inactive directory locks before iteration. --- platform/amiga/handler_main.c | 12 +- platform/amiga/os4/vector_port.c | 374 ++++++++++++++++++++++---- tests/integration/check_fh_packets.py | 22 ++ 3 files changed, 359 insertions(+), 49 deletions(-) diff --git a/platform/amiga/handler_main.c b/platform/amiga/handler_main.c index 3d80084..1a650f0 100644 --- a/platform/amiga/handler_main.c +++ b/platform/amiga/handler_main.c @@ -2619,14 +2619,20 @@ LONG odfs_handler_next_dir_entry(handler_global_t *g, if (!g || !entry_out || !key_out) return ERROR_REQUIRED_ARG_MISSING; - dir = ol ? lock_node(ol) : &g->mount.root; - if (ol) { - LONG err_dos = validate_object_volume(g, ol->entry->volume); + 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) diff --git a/platform/amiga/os4/vector_port.c b/platform/amiga/os4/vector_port.c index 4eccf8e..b11b163 100644 --- a/platform/amiga/os4/vector_port.c +++ b/platform/amiga/os4/vector_port.c @@ -20,20 +20,51 @@ #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) { - (void)vp; + handler_global_t *g = vp_require_global(vp, res2); - if (res2) - *res2 = ERROR_ACTION_NOT_KNOWN; + 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) { - (void)vp; + handler_global_t *g = vp_require_global(vp, res2); - if (res2) - *res2 = ERROR_DISK_WRITE_PROTECTED; + 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) @@ -58,18 +89,73 @@ static void fs_unlock(handler_global_t *g) ReleaseSemaphore(&g->fs_sem); } -static void set_dos_error(int32 *res2, LONG err) -{ - if (res2) - *res2 = err; -} - 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); @@ -135,6 +221,14 @@ static struct ExamineData *alloc_examine_data_from_context( 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, @@ -142,6 +236,8 @@ static struct ExamineData *alloc_examine_data_from_context( (ULONG)comment_len, ADO_ExamineData_LinkSize, 1UL, + ADO_ExamineDir_Context, + (ULONG)ctx, TAG_END); } if (!ed) @@ -185,23 +281,33 @@ static struct Lock *vp_lock(struct FSVP *vp, CONST_STRPTR obj, int32 mode) { - handler_global_t *g = vp_global(vp); + 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_global(vp); + 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); @@ -212,13 +318,18 @@ static struct Lock *vp_dup_lock(struct FSVP *vp, int32 *res2, struct Lock *lock) { - handler_global_t *g = vp_global(vp); + 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); } @@ -238,10 +349,13 @@ static struct Lock *vp_parent_dir(struct FSVP *vp, int32 *res2, struct Lock *dirlock) { - handler_global_t *g = vp_global(vp); + 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); @@ -254,10 +368,13 @@ static struct Lock *vp_dup_lock_from_fh(struct FSVP *vp, int32 *res2, struct FileHandle *filehandle) { - handler_global_t *g = vp_global(vp); + 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); @@ -270,16 +387,21 @@ static int32 vp_open_from_lock(struct FSVP *vp, struct FileHandle *file, struct Lock *lock) { - handler_global_t *g = vp_global(vp); + 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) + if (err == 0 && file) { + file->fh_Arg1 = (BPTR)odfs_fh; file->fh_Arg2 = odfs_fh; + } return return_dos_status(res2, err); } @@ -287,10 +409,13 @@ static struct Lock *vp_parent_of_fh(struct FSVP *vp, int32 *res2, struct FileHandle *file) { - handler_global_t *g = vp_global(vp); + 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); @@ -305,29 +430,43 @@ static int32 vp_open(struct FSVP *vp, CONST_STRPTR obj, int32 mode) { - handler_global_t *g = vp_global(vp); + 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); - if (err == 0 && fh) + 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_global(vp); + 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) + if (err == 0 && file) { + file->fh_Arg1 = 0; file->fh_Arg2 = NULL; + } return return_dos_status(res2, err); } @@ -348,10 +487,13 @@ static int32 vp_read(struct FSVP *vp, STRPTR buffer, int32 numbytes) { - handler_global_t *g = vp_global(vp); + 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); @@ -375,7 +517,11 @@ static int32 vp_write(struct FSVP *vp, static int32 vp_flush(struct FSVP *vp, int32 *res2) { - (void)vp; + handler_global_t *g = vp_require_global(vp, res2); + + if (!g) + return DOSFALSE; + set_dos_error(res2, 0); return DOSTRUE; } @@ -386,10 +532,13 @@ static int32 vp_change_file_position(struct FSVP *vp, int32 mode, int64 position) { - handler_global_t *g = vp_global(vp); + 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); @@ -414,10 +563,13 @@ static int64 vp_get_file_position(struct FSVP *vp, int32 *res2, struct FileHandle *file) { - handler_global_t *g = vp_global(vp); + 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); @@ -429,10 +581,13 @@ static int64 vp_get_file_size(struct FSVP *vp, int32 *res2, struct FileHandle *file) { - handler_global_t *g = vp_global(vp); + 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); @@ -445,9 +600,12 @@ static int32 vp_change_lock_mode(struct FSVP *vp, struct Lock *lock, int32 new_lock_mode) { - handler_global_t *g = vp_global(vp); + 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); @@ -460,9 +618,12 @@ static int32 vp_change_file_mode(struct FSVP *vp, struct FileHandle *fh, int32 new_lock_mode) { - handler_global_t *g = vp_global(vp); + 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); @@ -596,10 +757,13 @@ static int32 vp_same_lock(struct FSVP *vp, struct Lock *lock1, struct Lock *lock2) { - handler_global_t *g = vp_global(vp); + 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), @@ -607,7 +771,7 @@ static int32 vp_same_lock(struct FSVP *vp, &same); fs_unlock(g); set_dos_error(res2, err); - return err == 0 ? same : LOCK_DIFFERENT; + return (err == 0 && same == LOCK_SAME) ? DOSTRUE : DOSFALSE; } static int32 vp_same_file(struct FSVP *vp, @@ -615,10 +779,13 @@ static int32 vp_same_file(struct FSVP *vp, struct FileHandle *fh1, struct FileHandle *fh2) { - handler_global_t *g = vp_global(vp); + 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), @@ -626,25 +793,105 @@ static int32 vp_same_file(struct FSVP *vp, &same); fs_unlock(g); set_dos_error(res2, err); - return err == 0 ? same : LOCK_DIFFERENT; + return (err == 0 && same == FH_SAME) ? DOSTRUE : DOSFALSE; } static int32 vp_filesystem_attr(struct FSVP *vp, int32 *res2, struct TagItem *taglist) { - set_unsupported(vp, res2); - (void)taglist; - return DOSFALSE; + 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_global(vp); + 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); @@ -655,9 +902,12 @@ static int32 vp_device_info_data(struct FSVP *vp, int32 *res2, struct InfoData *info) { - handler_global_t *g = vp_global(vp); + 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); @@ -669,14 +919,20 @@ static struct ExamineData *vp_examine_obj(struct FSVP *vp, struct Lock *lock, CONST_STRPTR object) { - handler_global_t *g = vp_global(vp); + handler_global_t *g = vp_require_global(vp, res2); odfs_lock_t *ol = NULL; struct ExamineData *ed; LONG err; + if (!g) + return NULL; + fs_lock(g); err = odfs_handler_lock_object(g, lock_from_vector(lock), object ? object : "", SHARED_LOCK, &ol); + 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); @@ -694,13 +950,19 @@ static struct ExamineData *vp_examine_lock(struct FSVP *vp, int32 *res2, struct Lock *lock) { - handler_global_t *g = vp_global(vp); + 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); @@ -717,11 +979,14 @@ static struct ExamineData *vp_examine_file(struct FSVP *vp, int32 *res2, struct FileHandle *file) { - handler_global_t *g = vp_global(vp); + 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) { @@ -740,18 +1005,28 @@ static int32 vp_examine_dir(struct FSVP *vp, int32 *res2, struct PRIVATE_ExamineDirContext *ctx) { - handler_global_t *g = vp_global(vp); + 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); @@ -769,9 +1044,12 @@ static int32 vp_examine_dir(struct FSVP *vp, static int32 vp_inhibit(struct FSVP *vp, int32 *res2, int32 inhibit_state) { - handler_global_t *g = vp_global(vp); + 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); @@ -804,7 +1082,11 @@ static int32 vp_format(struct FSVP *vp, static int32 vp_serialize(struct FSVP *vp, int32 *res2) { - (void)vp; + handler_global_t *g = vp_require_global(vp, res2); + + if (!g) + return DOSFALSE; + set_dos_error(res2, 0); return DOSTRUE; } 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( From 7bc6501a85d113671c835c03dc09b4713f71ea76 Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Sat, 13 Jun 2026 17:06:49 -0700 Subject: [PATCH 19/36] amiga: keep OS4 packets on classic dispatcher OS4 still sends legacy packet APIs such as Lock(), Examine(), and ExNext() to the handler port. Routing those packets through DOSEmulatePacket returns vector-era semantics and can reject classic examine actions that callers still use. Leave direct packets on the shared packet dispatcher and serialize that dispatcher with the OS4 filesystem semaphore. Native vector calls and legacy packets now share the same handler state without using the vector packet emulator as an extra dispatch path. --- platform/amiga/handler_main.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/platform/amiga/handler_main.c b/platform/amiga/handler_main.c index 1a650f0..39e7512 100644 --- a/platform/amiga/handler_main.c +++ b/platform/amiga/handler_main.c @@ -4361,19 +4361,6 @@ void handler_main_startup(struct Message *startup_msg) break; } -#if ODFS_AMIGA_OS4 - if (g->vector_port) { - /* - * Route direct legacy packets through the DOS packet - * emulator, which performs the equivalent vector-port - * call exactly like a native DOS caller would. - */ - odfs_os4_emulate_packet(g->vector_port, pkt); - return_packet(g, pkt); - continue; - } -#endif - if (!g->mounted && packet_needs_live_mount(pkt)) { pkt->dp_Res1 = DOSFALSE; pkt->dp_Res2 = ERROR_NO_DISK; @@ -4381,7 +4368,20 @@ void handler_main_startup(struct Message *startup_msg) 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); } } From 62bf45bfab957750cd801e824924bccfe5bda2a0 Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Sat, 13 Jun 2026 17:07:08 -0700 Subject: [PATCH 20/36] amiga: use caller-owned IO requests for OS4 reads Native OS4 vector callbacks run in the caller task, but the handler reused its device IORequest whose reply port belongs to the handler task. If a caller-task DoIO() waited for completion, the device signaled the wrong task and Workbench could hang while opening a drawer. Record the handler task at startup. When media reads run from another task, allocate a temporary reply port and IORequest for that caller while reusing the same device and unit binding. --- platform/amiga/handler.h | 1 + platform/amiga/handler_main.c | 77 +++++++++++++++++++++++++++-------- 2 files changed, 62 insertions(+), 16 deletions(-) diff --git a/platform/amiga/handler.h b/platform/amiga/handler.h index dcaaa8b..7e1d01b 100644 --- a/platform/amiga/handler.h +++ b/platform/amiga/handler.h @@ -55,6 +55,7 @@ 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 FileSystemVectorPort *vector_port; /* native OS4 vector port */ LONG vector_sigbit; /* signal bit used by vector port */ /* diff --git a/platform/amiga/handler_main.c b/platform/amiga/handler_main.c index 39e7512..eae013f 100644 --- a/platform/amiga/handler_main.c +++ b/platform/amiga/handler_main.c @@ -217,9 +217,41 @@ static odfs_err_t amiga_read_sectors(void *ctx, uint32_t lba, { amiga_media_ctx_t *am = ctx; handler_global_t *g = am->g; + struct IOStdReq *req = g->devreq; +#if ODFS_AMIGA_OS4 + struct MsgPort *tmp_port = NULL; + struct IOStdReq *tmp_req = NULL; +#endif 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. + */ + if (FindTask(NULL) != g->handler_task) { + tmp_port = odfs_amiga_create_msg_port(); + if (!tmp_port) + return ODFS_ERR_NOMEM; + + tmp_req = (struct IOStdReq *)odfs_amiga_create_io_request( + tmp_port, sizeof(*tmp_req)); + if (!tmp_req) { + odfs_amiga_delete_msg_port(tmp_port); + return ODFS_ERR_NOMEM; + } + + tmp_req->io_Device = g->devreq->io_Device; + tmp_req->io_Unit = g->devreq->io_Unit; + req = tmp_req; + } +#endif /* * Read through the DMA-safe bounce buffer, one chunk at a time. @@ -245,36 +277,46 @@ 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: +#if ODFS_AMIGA_OS4 + if (tmp_req) + odfs_amiga_delete_io_request((struct IORequest *)tmp_req); + if (tmp_port) + odfs_amiga_delete_msg_port(tmp_port); +#endif + return ret; } static uint32_t amiga_sector_size(void *ctx) @@ -4112,6 +4154,9 @@ void handler_main_startup(struct Message *startup_msg) { struct Process *proc = (struct Process *)FindTask(NULL); +#if ODFS_AMIGA_OS4 + g->handler_task = (struct Task *)proc; +#endif g->process_port = &proc->pr_MsgPort; g->dosport = g->process_port; } From bd06967e367bbd29212b3ac5c7af7ff8c5c19940 Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Sun, 14 Jun 2026 08:08:38 -0700 Subject: [PATCH 21/36] docs: document AmigaOS 3 and 4 builds Add explicit AmigaOS 3 and AmigaOS 4 build instructions to the README. Describe compiler target selection, separate build directories, explicit tool overrides, and per-target size limits. --- Makefile | 64 +++++++++++++- README.md | 138 +++++++++++++++++++++++++++-- docs/ODFileSystem.readme | 131 ++++++++++++++++++++++++++++ docs/ODFileSystem_OS4.readme | 162 +++++++++++++++++++++++++++++++++++ 4 files changed, 487 insertions(+), 8 deletions(-) create mode 100644 docs/ODFileSystem.readme create mode 100644 docs/ODFileSystem_OS4.readme diff --git a/Makefile b/Makefile index 294d8da..a483d6b 100644 --- a/Makefile +++ b/Makefile @@ -270,12 +270,23 @@ 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 @@ -317,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: 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/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 From da5db582464883727fd08c096b26443e18a3a480 Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Sun, 14 Jun 2026 08:09:25 -0700 Subject: [PATCH 22/36] ci: build AmigaOS 4 handler artifacts Add a PPC CI job that runs in the amigappc-gcc container. Put /opt/amigappc/bin on PATH, verify ppc-amigaos-gcc, and build both release and serial-debug handler variants. --- .github/workflows/ci-build.yml | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) 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 From d0b8e3fd79cf387905ab8b01eb59bd259460e148 Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Sun, 14 Jun 2026 08:09:41 -0700 Subject: [PATCH 23/36] release: publish AmigaOS 4 handler artifacts Split the draft release workflow into OS3 and OS4 builders so each target uses its matching container and toolchain. Download both artifact sets before creating the draft release, and attach the PPC release and serial-debug handlers alongside the OS3 files. --- .github/workflows/release.yml | 112 ++++++++++++++++++++++++++++------ 1 file changed, 94 insertions(+), 18 deletions(-) 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 From 5804d8db64bfb18f9c786ed6985c21850837a0d1 Mon Sep 17 00:00:00 2001 From: Stefan Date: Mon, 15 Jun 2026 17:58:45 -0700 Subject: [PATCH 24/36] amiga: cache OS4 vector read IO requests Vector callbacks can run on a caller task, so reusing the handler task device request makes DoIO() wait on the wrong reply port. The previous path avoided that by allocating a temporary request for each read, but paid that allocation cost on every sector batch. Keep one cached vector I/O request per caller task in the handler global state. Refresh the device and unit from the handler request before reuse and free the cached request during shutdown. --- platform/amiga/handler.h | 3 ++ platform/amiga/handler_main.c | 88 +++++++++++++++++++++++++---------- 2 files changed, 67 insertions(+), 24 deletions(-) diff --git a/platform/amiga/handler.h b/platform/amiga/handler.h index 7e1d01b..29b2976 100644 --- a/platform/amiga/handler.h +++ b/platform/amiga/handler.h @@ -56,6 +56,9 @@ typedef struct handler_global { 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 */ /* diff --git a/platform/amiga/handler_main.c b/platform/amiga/handler_main.c index eae013f..167a889 100644 --- a/platform/amiga/handler_main.c +++ b/platform/amiga/handler_main.c @@ -212,16 +212,69 @@ static void notify_workbench_disk_change(BOOL inserted) 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; -#if ODFS_AMIGA_OS4 - struct MsgPort *tmp_port = NULL; - struct IOStdReq *tmp_req = NULL; -#endif uint32_t total_bytes = count * g->sector_size; uint8_t *out = buf; uint32_t done = 0; @@ -233,23 +286,13 @@ static odfs_err_t amiga_read_sectors(void *ctx, uint32_t lba, * 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. + * 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) { - tmp_port = odfs_amiga_create_msg_port(); - if (!tmp_port) + req = vector_io_request_for_current_task(g); + if (!req) return ODFS_ERR_NOMEM; - - tmp_req = (struct IOStdReq *)odfs_amiga_create_io_request( - tmp_port, sizeof(*tmp_req)); - if (!tmp_req) { - odfs_amiga_delete_msg_port(tmp_port); - return ODFS_ERR_NOMEM; - } - - tmp_req->io_Device = g->devreq->io_Device; - tmp_req->io_Unit = g->devreq->io_Unit; - req = tmp_req; } #endif @@ -310,12 +353,6 @@ static odfs_err_t amiga_read_sectors(void *ctx, uint32_t lba, } out: -#if ODFS_AMIGA_OS4 - if (tmp_req) - odfs_amiga_delete_io_request((struct IORequest *)tmp_req); - if (tmp_port) - odfs_amiga_delete_msg_port(tmp_port); -#endif return ret; } @@ -4456,6 +4493,9 @@ void handler_main_startup(struct Message *startup_msg) } shutdown: +#if ODFS_AMIGA_OS4 + release_vector_io_request(g); +#endif if (g->devreq) { if (g->devreq->io_Device) CloseDevice((struct IORequest *)g->devreq); From ed163eeaabe34825a0e725d5daa1ac7b99d9afc5 Mon Sep 17 00:00:00 2001 From: Stefan Date: Mon, 15 Jun 2026 17:59:23 -0700 Subject: [PATCH 25/36] amiga: examine OS4 vector objects without locks FSExamineObj only needs metadata for the requested object, but it was going through the lock path and then immediately freeing the lock. That adds DOS lock churn to vector examine calls. Split path resolution into a shared node resolver and use it from the OS4 vector path. The packet lock path still allocates real locks, while FSExamineObj formats metadata directly from the resolved node. --- platform/amiga/handler.h | 5 ++++ platform/amiga/handler_main.c | 43 +++++++++++++++++++++++--------- platform/amiga/os4/vector_port.c | 11 ++++---- 3 files changed, 42 insertions(+), 17 deletions(-) diff --git a/platform/amiga/handler.h b/platform/amiga/handler.h index 29b2976..677da80 100644 --- a/platform/amiga/handler.h +++ b/platform/amiga/handler.h @@ -272,6 +272,11 @@ static inline LONG odfs_err_to_dos(odfs_err_t err) } /* shared operations used by packet and OS4 vector frontends */ +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); LONG odfs_handler_lock_object(handler_global_t *g, odfs_lock_t *parent_lock, const char *path, diff --git a/platform/amiga/handler_main.c b/platform/amiga/handler_main.c index 167a889..e80dda5 100644 --- a/platform/amiga/handler_main.c +++ b/platform/amiga/handler_main.c @@ -1805,21 +1805,17 @@ static void fill_root_fib(handler_global_t *g, struct FileInfoBlock *fib, /* 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) +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) { - 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; - if (out) - *out = NULL; - if (!g || !path || !out) + if (!g || !path || !node_out || !parent_out) return ERROR_REQUIRED_ARG_MISSING; if (parent_lock) { @@ -1840,11 +1836,34 @@ LONG odfs_handler_lock_object(handler_global_t *g, start_parent = &g->mount.root; } - err = resolve_amiga_path(g, start, start_parent, path, &result, - &parent_node); + err = resolve_amiga_path(g, start, start_parent, path, node_out, + parent_out); if (err != ODFS_OK) return odfs_err_to_dos(err); + return 0; +} + +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; + LONG err_dos; + odfs_lock_t *ol; + + if (out) + *out = NULL; + if (!g || !path || !out) + return ERROR_REQUIRED_ARG_MISSING; + + err_dos = odfs_handler_resolve_object_node(g, parent_lock, path, + &result, &parent_node); + if (err_dos != 0) + return err_dos; + if (result.kind == ODFS_NODE_DIR) access = SHARED_LOCK; diff --git a/platform/amiga/os4/vector_port.c b/platform/amiga/os4/vector_port.c index b11b163..daf3ce2 100644 --- a/platform/amiga/os4/vector_port.c +++ b/platform/amiga/os4/vector_port.c @@ -920,7 +920,8 @@ static struct ExamineData *vp_examine_obj(struct FSVP *vp, CONST_STRPTR object) { handler_global_t *g = vp_require_global(vp, res2); - odfs_lock_t *ol = NULL; + odfs_node_t node; + odfs_node_t parent; struct ExamineData *ed; LONG err; @@ -928,8 +929,9 @@ static struct ExamineData *vp_examine_obj(struct FSVP *vp, return NULL; fs_lock(g); - err = odfs_handler_lock_object(g, lock_from_vector(lock), - object ? object : "", SHARED_LOCK, &ol); + 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); @@ -939,8 +941,7 @@ static struct ExamineData *vp_examine_obj(struct FSVP *vp, return NULL; } - ed = alloc_examine_data(g, ol->entry ? &ol->entry->fnode : NULL); - (void)odfs_handler_free_lock_object(g, ol); + ed = alloc_examine_data(g, &node); fs_unlock(g); set_dos_error(res2, ed ? 0 : ERROR_NO_FREE_STORE); return ed; From ac42550a19f1b41c60edd59b7a8d154427910357 Mon Sep 17 00:00:00 2001 From: Stefan Date: Mon, 15 Jun 2026 18:02:05 -0700 Subject: [PATCH 26/36] amiga: share node metadata formatting The packet and OS4 vector frontends were formatting name, type, date, protection, and comment metadata independently. Keeping those paths separate made it easy for root names and Amiga AS metadata to drift. Introduce a shared node-info filler for handler metadata. Use it from FIB, ExAll, and ExamineData formatting so both frontends apply the same key, protection, date, size, comment, and root-volume name rules. --- platform/amiga/handler.h | 17 ++- platform/amiga/handler_main.c | 234 +++++++++++-------------------- platform/amiga/os4/vector_port.c | 28 ++-- 3 files changed, 108 insertions(+), 171 deletions(-) diff --git a/platform/amiga/handler.h b/platform/amiga/handler.h index 677da80..b893616 100644 --- a/platform/amiga/handler.h +++ b/platform/amiga/handler.h @@ -271,7 +271,21 @@ 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); LONG odfs_handler_resolve_object_node(handler_global_t *g, odfs_lock_t *parent_lock, const char *path, @@ -348,9 +362,6 @@ LONG odfs_handler_next_dir_entry(handler_global_t *g, ULONG previous_key, odfs_node_t *entry_out, ULONG *key_out); -ULONG odfs_handler_node_key(const odfs_node_t *node); -ULONG odfs_handler_node_protection(const odfs_node_t *node); -void odfs_handler_node_date(const odfs_node_t *node, struct DateStamp *ds); LONG odfs_handler_inhibit(handler_global_t *g, LONG state); /* handler entry point (called from startup.S) */ diff --git a/platform/amiga/handler_main.c b/platform/amiga/handler_main.c index e80dda5..82a6072 100644 --- a/platform/amiga/handler_main.c +++ b/platform/amiga/handler_main.c @@ -1196,6 +1196,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; @@ -1217,12 +1220,7 @@ static ULONG amiga_node_key(const odfs_node_t *node) return key; } -ULONG odfs_handler_node_key(const odfs_node_t *node) -{ - return amiga_node_key(node); -} - -ULONG odfs_handler_node_protection(const odfs_node_t *node) +static ULONG node_protection(const odfs_node_t *node) { ULONG prot = 0; @@ -1278,7 +1276,7 @@ ULONG odfs_handler_node_protection(const odfs_node_t *node) return prot; } -void odfs_handler_node_date(const odfs_node_t *node, struct DateStamp *ds) +static void node_date(const odfs_node_t *node, struct DateStamp *ds) { if (!ds) return; @@ -1315,6 +1313,33 @@ void odfs_handler_node_date(const odfs_node_t *node, struct DateStamp *ds) } } +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, @@ -1668,112 +1693,39 @@ 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)); + odfs_handler_fill_node_info(g, fnode, &info); - /* 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); - } + 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 = (fnode->kind == ODFS_NODE_DIR) ? ST_USERDIR : ST_FILE; + fib->fib_DirEntryType = info.fib_type; odfs_amiga_set_fib_entry_type(fib, 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; -#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_Size = (LONG)info.size; + fib->fib_NumBlocks = (info.size + 511) / 512; + fib->fib_Protection = info.protection; + fib->fib_Date = info.date; - 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; - } + 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); - 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_DiskKey = (LONG)info.key; } static int node_is_mount_root(const handler_global_t *g, const odfs_node_t *fnode) @@ -1784,23 +1736,6 @@ 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) -{ - int len; - - fill_fib(fib, fnode); - - fib->fib_DirEntryType = ST_ROOT; - odfs_amiga_set_fib_entry_type(fib, ST_ROOT); - - len = strlen(g->volname); - if (len > 30) - len = 30; - fib->fib_FileName[0] = len; - memcpy(&fib->fib_FileName[1], g->volname, len); -} - /* ------------------------------------------------------------------ */ /* shared frontend operations */ /* ------------------------------------------------------------------ */ @@ -2658,6 +2593,7 @@ static void action_same_lock(handler_global_t *g, struct DosPacket *pkt) /* ---- examine ---- */ typedef struct exnext_ctx { + handler_global_t *g; struct FileInfoBlock *fib; ULONG previous_key; int first; @@ -2675,7 +2611,7 @@ static odfs_err_t exnext_cb(const odfs_node_t *entry, void *ctx) return ODFS_OK; } - fill_fib(ec->fib, entry); + fill_fib(ec->g, ec->fib, entry); ec->found = 1; return ODFS_ERR_EOF; /* stop after one entry */ } @@ -2789,10 +2725,7 @@ 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); - else - fill_fib(fib, fnode); + fill_fib(g, fib, fnode); #if !ODFS_AMIGA_OS4 if (ol) ol->dos_private[1] = (ULONG)-1; @@ -2837,6 +2770,7 @@ static void action_examine_next(handler_global_t *g, struct DosPacket *pkt) } dir_key = ol ? ol->key : amiga_node_key(dir); + ec.g = g; ec.fib = fib; ec.previous_key = (ULONG)fib->fib_DiskKey; ec.first = (ec.previous_key == dir_key); @@ -2890,7 +2824,7 @@ 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_SERIAL_DEBUG && ODFS_PACKET_TRACE ODFS_TRACE(&g->log, ODFS_SUB_DOS, "exnext-inject-cdda key=%08lx", @@ -2928,22 +2862,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) @@ -2953,29 +2885,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 = (STRPTR)p; - memcpy(p, comment, comment_len); + memcpy(p, info.comment, comment_len); p += comment_len; } ed->ed_Name = (STRPTR)p; - memcpy(p, name, name_len); + 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; @@ -2989,6 +2920,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; @@ -3021,7 +2953,8 @@ 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; } @@ -3083,6 +3016,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; @@ -3164,7 +3098,7 @@ 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; } diff --git a/platform/amiga/os4/vector_port.c b/platform/amiga/os4/vector_port.c index daf3ce2..9a9a35a 100644 --- a/platform/amiga/os4/vector_port.c +++ b/platform/amiga/os4/vector_port.c @@ -166,14 +166,6 @@ static odfs_fh_t *fh_from_vector(struct FileHandle *fh) return fh ? (odfs_fh_t *)fh->fh_Arg2 : NULL; } -static const char *node_name_for_examine(handler_global_t *g, - const odfs_node_t *node) -{ - if (g && node && odfs_node_matches_identity(node, &g->mount.root)) - return g->volname; - return node ? node->name : ""; -} - static struct ExamineData *take_stale_examine_data( struct PRIVATE_ExamineDirContext *ctx, size_t name_len, @@ -205,6 +197,7 @@ static struct ExamineData *alloc_examine_data_from_context( { const char *name; const char *comment = ""; + odfs_handler_node_info_t info; size_t name_len; size_t comment_len; struct ExamineData *ed; @@ -212,9 +205,9 @@ static struct ExamineData *alloc_examine_data_from_context( if (!node) return NULL; - name = node_name_for_examine(g, node); - if (node->amiga_as.has_comment) - comment = node->amiga_as.comment; + odfs_handler_fill_node_info(g, node, &info); + name = info.name; + comment = info.comment; name_len = strlen(name) + 1u; comment_len = strlen(comment) + 1u; @@ -244,16 +237,15 @@ static struct ExamineData *alloc_examine_data_from_context( return NULL; ed->EXDinfo = 0; - ed->Type = (node->kind == ODFS_NODE_DIR) ? - FSO_TYPE_DIRECTORY : FSO_TYPE_FILE; - ed->FileSize = (node->kind == ODFS_NODE_DIR) ? -1LL : (int64)node->size; - odfs_handler_node_date(node, &ed->Date); + 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 = odfs_handler_node_key(node); - ed->Protection = odfs_handler_node_protection(node); + ed->ObjectID = info.key; + ed->Protection = info.protection; ed->OwnerUID = DOS_OWNER_NONE; ed->OwnerGID = DOS_OWNER_NONE; - ed->FSPrivate = odfs_handler_node_key(node); + ed->FSPrivate = info.key; if (ed->Name && ed->NameSize > 0) { strncpy(ed->Name, name, ed->NameSize - 1); From a5f08758cdfffe0e5602a303b2b4dca469d0805c Mon Sep 17 00:00:00 2001 From: Stefan Date: Mon, 15 Jun 2026 18:02:30 -0700 Subject: [PATCH 27/36] amiga: remove unused OS4 vector packet API The OS4 vector template and packet emulation helpers no longer had any callers after direct packet handling moved back to the classic dispatcher. Leaving them exported made the vector port API look broader than it is. Remove the unused declarations and definitions. The vector module now only exposes allocation, teardown, and invalidation entry points. --- platform/amiga/os4/vector_port.c | 20 -------------------- platform/amiga/os4/vector_port.h | 9 --------- 2 files changed, 29 deletions(-) diff --git a/platform/amiga/os4/vector_port.c b/platform/amiga/os4/vector_port.c index 9a9a35a..7291e95 100644 --- a/platform/amiga/os4/vector_port.c +++ b/platform/amiga/os4/vector_port.c @@ -1200,11 +1200,6 @@ static const struct FileSystemVectors odfs_os4_vectors = { .End_Marker = -1 }; -const struct FileSystemVectors *odfs_os4_vector_template(void) -{ - return &odfs_os4_vectors; -} - struct FileSystemVectorPort *odfs_os4_alloc_vector_port(APTR fs_private) { struct FileSystemVectorPort *vp; @@ -1227,21 +1222,6 @@ void odfs_os4_free_vector_port(struct FileSystemVectorPort *vp) FreeDosObject(DOS_FSVECTORPORT, vp); } -void odfs_os4_emulate_packet(struct FileSystemVectorPort *vp, - struct DosPacket *pkt) -{ - if (!pkt) - return; - - if (vp && vp->FSV.DOSEmulatePacket) { - vp->FSV.DOSEmulatePacket(vp, pkt); - return; - } - - pkt->dp_Res1 = DOSFALSE; - pkt->dp_Res2 = ERROR_ACTION_NOT_KNOWN; -} - void odfs_os4_invalidate_vector_port(struct FileSystemVectorPort *vp) { if (vp) diff --git a/platform/amiga/os4/vector_port.h b/platform/amiga/os4/vector_port.h index 33fb66f..edf27d2 100644 --- a/platform/amiga/os4/vector_port.h +++ b/platform/amiga/os4/vector_port.h @@ -12,18 +12,9 @@ struct DosPacket; -const struct FileSystemVectors *odfs_os4_vector_template(void); struct FileSystemVectorPort *odfs_os4_alloc_vector_port(APTR fs_private); void odfs_os4_free_vector_port(struct FileSystemVectorPort *vp); -/* - * Route a direct legacy DosPacket through the DOS packet emulator that - * AllocDosObject() installed in the vector port. Results are placed in - * the packet; the caller still replies it. - */ -void odfs_os4_emulate_packet(struct FileSystemVectorPort *vp, - struct DosPacket *pkt); - /* * Stop dos.library from vectoring new callers (sets the vector version * to zero). Must be called before DOS-visible shutdown teardown. From 90df62d4d3238166fe8965518a40ee91d027a7ae Mon Sep 17 00:00:00 2001 From: Stefan Date: Mon, 15 Jun 2026 18:03:01 -0700 Subject: [PATCH 28/36] amiga: inline trivial compat field helpers Two sys_compat helpers only wrapped field assignments that are either OS3-only or no-ops on OS4. Keeping them in the compatibility layer added extra exported surface without hiding any real platform difference. Assign fib_EntryType and dn_Lock directly at the call sites under the OS3 guard, then drop the OS3 and OS4 helper implementations. --- platform/amiga/common/sys_compat.h | 5 ----- platform/amiga/handler_main.c | 8 ++++++-- platform/amiga/os3/sys_compat.c | 11 ----------- platform/amiga/os4/sys_compat.c | 13 ------------- 4 files changed, 6 insertions(+), 31 deletions(-) diff --git a/platform/amiga/common/sys_compat.h b/platform/amiga/common/sys_compat.h index b7efcd9..3c485b5 100644 --- a/platform/amiga/common/sys_compat.h +++ b/platform/amiga/common/sys_compat.h @@ -21,8 +21,6 @@ #include struct Hook; -struct FileInfoBlock; -struct DeviceNode; typedef LONG (*odfs_amiga_interrupt_fn)(APTR data); @@ -62,9 +60,6 @@ void odfs_amiga_init_interrupt(struct Interrupt *intr, APTR data, odfs_amiga_interrupt_fn code); -void odfs_amiga_set_fib_entry_type(struct FileInfoBlock *fib, LONG type); -void odfs_amiga_copy_device_lock(struct DeviceNode *dst, - const struct DeviceNode *src); 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_main.c b/platform/amiga/handler_main.c index 82a6072..925dda1 100644 --- a/platform/amiga/handler_main.c +++ b/platform/amiga/handler_main.c @@ -1712,7 +1712,9 @@ static void fill_fib(handler_global_t *g, struct FileInfoBlock *fib, memcpy(&fib->fib_FileName[1], info.name, name_len); fib->fib_DirEntryType = info.fib_type; - odfs_amiga_set_fib_entry_type(fib, fib->fib_DirEntryType); +#if !ODFS_AMIGA_OS4 + fib->fib_EntryType = fib->fib_DirEntryType; +#endif fib->fib_Size = (LONG)info.size; fib->fib_NumBlocks = (info.size + 511) / 512; fib->fib_Protection = info.protection; @@ -3441,7 +3443,9 @@ static struct DeviceNode *create_device_node(handler_global_t *g) if (!devnode) return NULL; - odfs_amiga_copy_device_lock(devnode, g->devnode); +#if !ODFS_AMIGA_OS4 + devnode->dn_Lock = g->devnode ? g->devnode->dn_Lock : 0; +#endif sync_device_node(g, devnode); return devnode; diff --git a/platform/amiga/os3/sys_compat.c b/platform/amiga/os3/sys_compat.c index f85ccff..8c42b40 100644 --- a/platform/amiga/os3/sys_compat.c +++ b/platform/amiga/os3/sys_compat.c @@ -156,17 +156,6 @@ void odfs_amiga_init_interrupt(struct Interrupt *intr, intr->is_Code = (void (*)(void))(APTR)odfs_amiga_interrupt_entry; } -void odfs_amiga_set_fib_entry_type(struct FileInfoBlock *fib, LONG type) -{ - fib->fib_EntryType = type; -} - -void odfs_amiga_copy_device_lock(struct DeviceNode *dst, - const struct DeviceNode *src) -{ - dst->dn_Lock = src ? src->dn_Lock : 0; -} - ULONG odfs_amiga_call_hook_pkt(struct Hook *hook, APTR object, APTR message) { if (!UtilityBase) diff --git a/platform/amiga/os4/sys_compat.c b/platform/amiga/os4/sys_compat.c index 68a2aa6..d0d79af 100644 --- a/platform/amiga/os4/sys_compat.c +++ b/platform/amiga/os4/sys_compat.c @@ -223,19 +223,6 @@ void odfs_amiga_init_interrupt(struct Interrupt *intr, intr->is_Code = (void (*)(void))(APTR)odfs_amiga_interrupt_entry; } -void odfs_amiga_set_fib_entry_type(struct FileInfoBlock *fib, LONG type) -{ - (void)fib; - (void)type; -} - -void odfs_amiga_copy_device_lock(struct DeviceNode *dst, - const struct DeviceNode *src) -{ - (void)dst; - (void)src; -} - ULONG odfs_amiga_call_hook_pkt(struct Hook *hook, APTR object, APTR message) { if (!utility_iface) From 9bbc15189b998fa597e338266b219103037eeefa Mon Sep 17 00:00:00 2001 From: Stefan Date: Mon, 15 Jun 2026 20:17:08 -0700 Subject: [PATCH 29/36] core: batch contiguous file reads Each backend copied file data one sector at a time through the block cache, even for long aligned reads. That creates many media calls and duplicates the same edge-sector logic across every backend. Add odfs_cache_read_bytes() to handle partial edge sectors through the cache and send aligned multi-sector runs directly to the media layer. Switch ISO, Joliet, UDF, HFS, and HFS+ reads to that helper and cover the batching behavior with cache unit tests. --- backends/hfs/hfs.c | 26 +------ backends/hfsplus/hfsplus.c | 25 +------ backends/iso9660/iso9660.c | 29 +------- backends/joliet/joliet.c | 26 +------ backends/udf/udf.c | 26 +------ core/cache_block.c | 140 +++++++++++++++++++++++++++++++++++++ include/odfs/cache.h | 11 +++ tests/unit/test_cache.c | 82 ++++++++++++++++++++++ 8 files changed, 248 insertions(+), 117 deletions(-) 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..c7d3a63 100644 --- a/core/cache_block.c +++ b/core/cache_block.c @@ -8,6 +8,8 @@ #include "odfs/alloc.h" #include +#define ODFS_CACHE_STREAM_MIN_SECTORS 2u + odfs_err_t odfs_cache_init(odfs_cache_t *cache, odfs_media_t *media, uint32_t capacity) @@ -138,6 +140,144 @@ odfs_err_t odfs_cache_read(odfs_cache_t *cache, 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/include/odfs/cache.h b/include/odfs/cache.h index 5b25f81..8369008 100644 --- a/include/odfs/cache.h +++ b/include/odfs/cache.h @@ -51,6 +51,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/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() From 53116500c2df607e18b70e2c2f66b0f58adb2676 Mon Sep 17 00:00:00 2001 From: Stefan Date: Mon, 15 Jun 2026 20:19:44 -0700 Subject: [PATCH 30/36] amiga: cache OS3 ExNext resume offsets Classic ExNext must leave fib_DiskKey as the visible directory or object key, so the handler was rescanning from the start of the directory each time. Large Workbench drawers then spend most of their time replaying entries already seen. Keep a private ExNext cursor on each OS3 lock, with a root cursor for null-lock scans. The cursor stores the backend resume offset and CDDA injection state while preserving the public fib_DiskKey contract. --- platform/amiga/handler.h | 15 ++++++ platform/amiga/handler_main.c | 98 ++++++++++++++++++++++++++++++++++- 2 files changed, 111 insertions(+), 2 deletions(-) diff --git a/platform/amiga/handler.h b/platform/amiga/handler.h index b893616..da2252b 100644 --- a/platform/amiga/handler.h +++ b/platform/amiga/handler.h @@ -49,6 +49,16 @@ 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 { @@ -123,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 */ @@ -156,6 +170,7 @@ struct odfs_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 */ diff --git a/platform/amiga/handler_main.c b/platform/amiga/handler_main.c index 925dda1..5f81f13 100644 --- a/platform/amiga/handler_main.c +++ b/platform/amiga/handler_main.c @@ -2618,6 +2618,59 @@ static odfs_err_t exnext_cb(const odfs_node_t *entry, void *ctx) 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; @@ -2729,6 +2782,10 @@ static void action_examine_object(handler_global_t *g, struct DosPacket *pkt) 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 + exnext_cursor_invalidate(exnext_cursor_for(g, ol)); if (ol) ol->dos_private[1] = (ULONG)-1; #endif @@ -2751,6 +2808,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); @@ -2772,22 +2833,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; @@ -2813,6 +2893,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", @@ -2827,6 +2912,11 @@ static void action_examine_next(handler_global_t *g, struct DosPacket *pkt) if (g->has_cdda && node_is_mount_root(g, dir) && ec.previous_key != amiga_node_key(&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", @@ -2838,6 +2928,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 } } From 9dc14e8943a54110008b6fc2eafb613c936f91ff Mon Sep 17 00:00:00 2001 From: Stefan Date: Mon, 15 Jun 2026 20:20:48 -0700 Subject: [PATCH 31/36] core: pool namefix scan allocations Duplicate-name fixing remembered every name with one allocation for the list entry and another for the copied string. Directory scans with many entries therefore spent unnecessary time in allocator bookkeeping. Add a small chunk allocator to the namefix state and store each entry with its name buffer in that pool. Destroying the state now releases the chunks in one pass. --- core/namefix.c | 73 ++++++++++++++++++++++++++++++++---------- include/odfs/namefix.h | 9 +++++- 2 files changed, 64 insertions(+), 18 deletions(-) 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/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); From 1324227e74283b5bc4f1d94d93ce8384a58858a3 Mon Sep 17 00:00:00 2001 From: Stefan Date: Mon, 15 Jun 2026 20:22:08 -0700 Subject: [PATCH 32/36] core: hash block cache lookups Cache hits were found with a linear scan over every cache entry, and the high-water mark was recomputed by scanning again after each fill. Larger cache sizes made common lookups scale with the whole cache capacity. Add a simple LBA hash table with per-entry collision links and track the valid-entry count directly. Reads now find hits through the hash table and update the chains when entries are evicted or flushed. --- core/cache_block.c | 160 ++++++++++++++++++++++++++++++++++++------- include/odfs/cache.h | 4 ++ 2 files changed, 138 insertions(+), 26 deletions(-) diff --git a/core/cache_block.c b/core/cache_block.c index c7d3a63..d7d549b 100644 --- a/core/cache_block.c +++ b/core/cache_block.c @@ -10,14 +10,103 @@ #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); @@ -28,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; @@ -46,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; } @@ -57,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)); @@ -69,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, @@ -78,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) @@ -87,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) */ @@ -115,26 +223,26 @@ 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; diff --git a/include/odfs/cache.h b/include/odfs/cache.h index 8369008..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; From 6cb041026eadb36c02f5b0446268f4cb5e18e1fd Mon Sep 17 00:00:00 2001 From: Stefan Date: Mon, 15 Jun 2026 20:28:18 -0700 Subject: [PATCH 33/36] amiga: cache parent ancestors on objects Parent lookup for locks and file handles still had to reconstruct the next ancestor when the caller walked upward more than one level. That kept repeated ParentDir() and path ascent operations tied to directory rescans. Store an optional grandparent node in each shared object entry and carry that ancestor through path resolution, lock duplication, open, and parent queries. Fall back to parent-node resolution only when the cached ancestor is not available. --- platform/amiga/handler.h | 4 + platform/amiga/handler_main.c | 310 ++++++++++++++++++++++------------ 2 files changed, 208 insertions(+), 106 deletions(-) diff --git a/platform/amiga/handler.h b/platform/amiga/handler.h index da2252b..56f0819 100644 --- a/platform/amiga/handler.h +++ b/platform/amiga/handler.h @@ -158,6 +158,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; }; @@ -301,11 +303,13 @@ typedef struct odfs_handler_node_info { 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, diff --git a/platform/amiga/handler_main.c b/platform/amiga/handler_main.c index 5f81f13..b89e253 100644 --- a/platform/amiga/handler_main.c +++ b/platform/amiga/handler_main.c @@ -1009,7 +1009,8 @@ 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; @@ -1023,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; } @@ -1052,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; @@ -1062,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; @@ -1443,6 +1465,7 @@ 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; @@ -1452,7 +1475,7 @@ static odfs_lock_t *alloc_lock(handler_global_t *g, 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; @@ -1599,22 +1622,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 @@ -1633,15 +1675,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++; @@ -1667,6 +1718,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; @@ -1674,6 +1727,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) @@ -1686,6 +1741,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; } @@ -1738,19 +1797,21 @@ 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); } -/* ------------------------------------------------------------------ */ -/* shared frontend operations */ -/* ------------------------------------------------------------------ */ - -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) +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) { odfs_err_t err; const odfs_node_t *start; const odfs_node_t *start_parent; + const odfs_node_t *start_grandparent; + + if (has_grandparent_out) + *has_grandparent_out = 0; if (!g || !path || !node_out || !parent_out) return ERROR_REQUIRED_ARG_MISSING; @@ -1766,28 +1827,48 @@ LONG odfs_handler_resolve_object_node(handler_global_t *g, 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, path, node_out, - parent_out); + 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; + odfs_node_t result, parent_node, grandparent_node; + int has_grandparent; LONG err_dos; odfs_lock_t *ol; @@ -1796,15 +1877,17 @@ LONG odfs_handler_lock_object(handler_global_t *g, if (!g || !path || !out) return ERROR_REQUIRED_ARG_MISSING; - err_dos = odfs_handler_resolve_object_node(g, parent_lock, path, - &result, &parent_node); + 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, access); + ol = alloc_lock(g, &result, &parent_node, + has_grandparent ? &grandparent_node : NULL, access); if (!ol) return ERROR_NO_FREE_STORE; @@ -1837,7 +1920,8 @@ LONG odfs_handler_dup_lock_object(handler_global_t *g, if (!src) { if (!g->mounted) return ERROR_NO_DISK; - ol = alloc_lock(g, &g->mount.root, &g->mount.root, SHARED_LOCK); + ol = alloc_lock(g, &g->mount.root, &g->mount.root, &g->mount.root, + SHARED_LOCK); } else { LONG err_dos; @@ -1871,7 +1955,8 @@ LONG odfs_handler_dup_lock_from_fh(handler_global_t *g, if (!fh) { if (!g->mounted) return ERROR_NO_DISK; - ol = alloc_lock(g, &g->mount.root, &g->mount.root, SHARED_LOCK); + ol = alloc_lock(g, &g->mount.root, &g->mount.root, &g->mount.root, + SHARED_LOCK); } else { LONG err_dos; @@ -1881,7 +1966,8 @@ LONG odfs_handler_dup_lock_from_fh(handler_global_t *g, 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), SHARED_LOCK); + ol = alloc_lock(g, fh_node(fh), fh_parent_node(fh), + fh_grandparent_node(fh), SHARED_LOCK); } if (!ol) @@ -1891,15 +1977,94 @@ LONG odfs_handler_dup_lock_from_fh(handler_global_t *g, return 0; } -LONG odfs_handler_parent_lock_object(handler_global_t *g, - odfs_lock_t *ol, - odfs_lock_t **out) +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_err_t err; + 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) @@ -1920,40 +2085,13 @@ LONG odfs_handler_parent_lock_object(handler_global_t *g, return err_dos; } - if (node_is_mount_root(g, lock_node(ol))) - return 0; - - 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) - return odfs_err_to_dos(err); - } - - parent = alloc_lock(g, parent_node, &new_parent, SHARED_LOCK); - if (!parent) - return ERROR_NO_FREE_STORE; - - *out = parent; - return 0; + 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) { - const odfs_node_t *parent_node; - odfs_node_t new_parent; - odfs_err_t err; - odfs_lock_t *parent; - if (out) *out = NULL; if (!g || !out) @@ -1974,29 +2112,7 @@ LONG odfs_handler_parent_fh_object(handler_global_t *g, return err_dos; } - if (node_is_mount_root(g, fh_node(fh))) - return 0; - - 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) - return odfs_err_to_dos(err); - } - - parent = alloc_lock(g, parent_node, &new_parent, SHARED_LOCK); - if (!parent) - return ERROR_NO_FREE_STORE; - - *out = parent; - return 0; + return parent_entry_object(g, fh->entry, out); } LONG odfs_handler_same_lock_object(handler_global_t *g, @@ -2077,10 +2193,9 @@ LONG odfs_handler_open_object(handler_global_t *g, LONG mode, odfs_fh_t **out) { - odfs_node_t result, parent_node; - odfs_err_t err; - const odfs_node_t *start; - const odfs_node_t *start_parent; + odfs_node_t result, parent_node, grandparent_node; + int has_grandparent; + LONG err_dos; odfs_entry_t *entry; odfs_fh_t *fh; @@ -2092,33 +2207,16 @@ LONG odfs_handler_open_object(handler_global_t *g, if (mode != MODE_OLDFILE) return ERROR_DISK_WRITE_PROTECTED; - if (dirlock) { - LONG err_dos; - - if (!lock_is_active(g, dirlock)) - return ERROR_INVALID_LOCK; - - err_dos = validate_object_volume(g, dirlock->entry->volume); - if (err_dos != 0) - return err_dos; - start = lock_node(dirlock); - start_parent = lock_parent_node(dirlock); - } else { - if (!g->mounted) - return ERROR_NO_DISK; - 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) - return odfs_err_to_dos(err); + 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); + entry = alloc_entry(g->current_volume, &result, &parent_node, + has_grandparent ? &grandparent_node : NULL); if (!entry) return ERROR_NO_FREE_STORE; From e544717ebdc3909f3eaf481efe50818cf1d1878c Mon Sep 17 00:00:00 2001 From: Stefan Date: Mon, 15 Jun 2026 20:30:36 -0700 Subject: [PATCH 34/36] amiga: update volume lock lists incrementally Maintaining dl_LockList by rebuilding it from the global lock list makes every lock allocation and free scan all active locks. That cost grows with open drawer state and duplicates ordering work that can be maintained incrementally. Keep a per-volume lock chain with previous and next links in each lock. Link and unlink locks as they are created, duplicated, freed, or drained, and update the DOS-visible volume lock list head inside the same critical section. --- platform/amiga/handler.h | 3 ++ platform/amiga/handler_main.c | 67 ++++++++++++++++++++++------------- 2 files changed, 45 insertions(+), 25 deletions(-) diff --git a/platform/amiga/handler.h b/platform/amiga/handler.h index 56f0819..eed2ff5 100644 --- a/platform/amiga/handler.h +++ b/platform/amiga/handler.h @@ -148,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; }; @@ -176,6 +177,8 @@ struct odfs_lock { #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 diff --git a/platform/amiga/handler_main.c b/platform/amiga/handler_main.c index b89e253..777e39b 100644 --- a/platform/amiga/handler_main.c +++ b/platform/amiga/handler_main.c @@ -1134,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) - ODFS_LOCK_DOS(prev)->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 (prev) - ODFS_LOCK_DOS(prev)->fl_Link = 0; - volume->volnode->dl_LockList = head; + if (!volume || !ol) + return; + + 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(); } @@ -1192,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); } @@ -1423,6 +1440,7 @@ static void drain_all_objects(handler_global_t *g) 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); @@ -1514,7 +1532,7 @@ static odfs_lock_t *alloc_lock(handler_global_t *g, 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; } @@ -1522,8 +1540,8 @@ 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); @@ -1572,7 +1590,7 @@ static odfs_lock_t *dup_lock(handler_global_t *g, odfs_lock_t *src) #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; } @@ -4132,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) { From 13e5d78198d2b2cf97a7d738a49dfbd5033ec478 Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Mon, 15 Jun 2026 23:41:44 -0700 Subject: [PATCH 35/36] build: raise ROM profile size limit The current ROM-profile handler is about 37 KiB on this branch, so the 32 KiB ceiling fails the release and archive build paths. Raise the default budget to 40 KiB and update the ROM profile docs to match the build setting. --- Makefile | 2 +- docs/rom-profile.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index a483d6b..9cd9c83 100644 --- a/Makefile +++ b/Makefile @@ -92,7 +92,7 @@ AMIGA_SIZE_LIMIT ?= 131072 else AMIGA_SIZE_LIMIT ?= 60000 endif -ROM_SIZE_LIMIT ?= 32768 +ROM_SIZE_LIMIT ?= 40960 SIZE_LIMIT_NAME ?= AMIGA_SIZE_LIMIT SIZE_LIMIT_DESC ?= release Amiga handler 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: From e8e95b55e656ff23a619e5dbc6cfb075c76dfeb5 Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Mon, 15 Jun 2026 23:42:31 -0700 Subject: [PATCH 36/36] ci: publish Aminet release packages Add a release-published workflow that rebuilds the OS3 and OS4 LHA packages from v-tagged releases and uploads both to Aminet. The workflow skips prereleases, prepares the matching Aminet readme for each archive, and lets the Aminet action inject the release version. --- .github/workflows/aminet-release.yml | 133 +++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 .github/workflows/aminet-release.yml 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