filesys: mount host directories as AmigaDOS volumes#159
Conversation
A Copperline services board (0x1448 product 5, 64K Zorro II) carries a tiny guest handler (guest/filesys/, C, built reloc-free with dockerized m68k-amigaos GCC 16) that mounts each [[filesys]] config entry via the DiagArea and pumps every DosPacket to the emulator through an A-line trap; src/filesys.rs implements the ACTION_* semantics on the host side. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ACTION_COPY_DIR/PARENT/SET_PROTECT plus read-only file I/O (FINDINPUT/READ/SEEK/END), enough for list, type, and copy-from. A single trailing slash means the directory itself, not its parent (verified against FFS: "Prefs/" lists Prefs, "Prefs//" its parent). The NDK dos.h/dosextens.h constants and structures move to amigaos::dos as repr(C) structs of big-endian fields (zerocopy), so the definition is the guest layout and one write_bytes serializes it. All hand-assembled bytes leave board_image(): the DiagArea now lives inside the handler ROM (entry.s), like real autoboot board ROMs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
I ran out of Fable credits while testing the .uaem metadata parser. My next goals are:
|
Read the UAE .uaem metadata sidecars (same grammar as the Amiberry fsdb: hsparwed presence flags with the rwed group flipped to the FIB deny convention, centisecond timestamp, optional comment) for protection bits, exact datestamps, and file comments; hide the sidecars from listings. Without a sidecar, a read-only host file denies w, and e stays allowed, both matching the UAE fsdb. Sidecar timestamps convert civil-date-direct, with no timezone round trip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Flush all per-boot handler state (ports, volumes, locks, open files, lock pool) at expansion init, so a warm reboot starts clean instead of misrouting packets to stale MsgPort addresses. Point dn_Startup at a per-unit FileSysStartupMsg (plus shared DosEnvec and device BSTR) written into the board window, so Early Startup shows 'CLFS hostfs-N' instead of dereferencing a raw integer as a BPTR. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
[[filesys]] entries take bootpri (-128..127, default -128 = never a boot candidate), carried in a per-unit DosEnvec whose de_BootPri the guest handler passes to AddBootNode. The DiagArea's BootPoint is now the standard autoboot code (FindResident dos.library, jsr rt_Init), and DiagPoint returns success so Kickstart keeps the diag copy strap boots through. Booting a real Workbench off a host directory flushed out packet-level gaps, all fixed: names with an "Assign:" prefix must resolve relative to the supplied lock, not the root (broke SetPatch's LIBS: opens); ACTION_FH_FROM_LOCK (broke the ENVARC: copy), ACTION_SAME_LOCK, ACTION_EXAMINE_FH, ACTION_PARENT_FH, and ACTION_FLUSH. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds experimental host-filesystem support by introducing a new Copperline “services” Zorro II board and a guest-side handler ROM that forwards AmigaDOS packets to a host-side Rust HLE implementation, enabling [[filesys]] config entries to mount host directories as HOSTFS<n>: volumes.
Changes:
- Introduces a new services autoconfig board type and plumbing to pre-seed its RAM window with the handler ROM + mount/diag data.
- Adds
src/filesys.rs(host-side HLE) plus an AmigaDOS ABI mirror (src/amigaos/dos.rs) and configuration parsing/validation for[[filesys]]. - Adds guest-side handler sources/build instructions and commits the built ROM artifact for normal
cargo buildworkflows.
Reviewed changes
Copilot reviewed 14 out of 17 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/zorro.rs | Adds a services board product ID/spec and a helper to add RAM-backed boards pre-seeded with an image. |
| src/lib.rs | Exposes the new filesys module. |
| src/filesys.rs | Implements host-side AmigaDOS packet semantics for mounted host directories, plus board-window layout helpers and unit tests. |
| src/emulator.rs | Wires config-provided filesys mounts into the machine at build time. |
| src/cpu.rs | Replaces the no-op HLE handler with FilesysHle and adds a mounts setter. |
| src/config.rs | Adds [[filesys]] parsing, defaulting, validation, and services-board insertion into the Zorro chain. |
| src/amigaos/dos.rs | Adds a zerocopy-backed big-endian ABI mirror for DOS constants and structures. |
| src/amigaos.rs | Exposes the new amigaos::dos module. |
| guest/services/README.md | Documents the guest-side handler ROM purpose and rebuild process. |
| guest/services/Makefile | Adds dockerized m68k build pipeline and checks for relocations/data/bss. |
| guest/services/handler.c | Implements the guest packet-pump handler and expansion-init mounting logic. |
| guest/services/entry.s | Defines the entry table and embedded DiagArea used for expansion init / autoboot. |
| guest/services/copperline_board.h | Shares board layout/trap opcode constants between guest and host. |
| Cargo.toml | Adds zerocopy dependency for ABI structs. |
| Cargo.lock | Locks zerocopy dependency. |
| .gitignore | Ensures the committed services ROM artifact is not ignored; ignores guest build intermediates. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
A guest could Lock("HOSTFS:..") and walk out of the mounted directory
onto the host filesystem: AmigaDOS gives dots no special meaning, but
match_component passed them to the host, which does. Found by Copilot
review on PR LinuxJedi#159.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Deleting a file popped "unknown packet" (209); a write-protected FFS disk answers every write-family action with error 214 instead, so do the same until write support lands. id_DiskState now reports ID_WRITE_PROTECTED, so C:Info shows the volumes as Read Only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A dismount tool sends ACTION_DIE to shut a handler down; stock Assign DISMOUNT only unlinks the DeviceNode silently. Refused with ERROR_OBJECT_IN_USE while locks or files are open (protects the boot volume). On success the host frees the unit state and clears dn_Task, and the guest RemDosEntry-s the volume and exits the process, so the next reference to the device simply restarts the handler and remounts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The packet pump needs well under 200 bytes; 8K per handler process was just wasted guest RAM. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@codex review |
| for m in &self.filesys { | ||
| if !m.path.is_dir() { | ||
| anyhow::bail!("[[filesys]] path {} is not a directory", m.path.display()); | ||
| } | ||
| } |
There was a problem hiding this comment.
Fixed in 4540ff1: the [[filesys]] config loop now validates the resolved volume name with three separate checks and messages -- empty, over 30 bytes, and containing a colon, slash, or NUL -- failing fast instead of silently truncating or building a broken volume node. Added a rejection test.
So multi-mount debug logs say which HOSTFS unit a packet belongs to. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The read length comes straight from the guest packet, so allocating it up front let a bogus multi-GB read force an unbounded host allocation. Read in 64 KiB chunks instead; DOS callers see identical results. Reported by Copilot on PR LinuxJedi#159. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
handle_aline claimed the whole 0xA400-0xA4FF range and returned true for any opcode, swallowing the CPU A-line exception. We only emit 0xA400 and 0xA402; return false for the rest so guest software using its own A-line traps takes the exception as on hardware. Reported by Copilot on PR LinuxJedi#159. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The volume name becomes an AmigaDOS DosList BSTR, so reject up front rather than silently truncating or building a broken volume node. Three separate checks with their own errors: empty, over 30 bytes, and containing ":" "/" or NUL. Adds a rejection test. Reported by Copilot on PR LinuxJedi#159. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
TRAP_DIAG_ENTRY reset the per-boot maps field by field and missed next_file_key, which could (in theory) wrap and alias an open file. Reassign *self from a default, preserving only the configured mounts, so a newly added per-boot field can never be forgotten. Reported by Copilot on PR LinuxJedi#159. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Host-filesystem support:
[[filesys]]config entries export host directories to the guest as AmigaDOS volumes:Design
A new Copperline services board (manufacturer 0x1448, product 5, 64K Zorro II, RAM-backed) carries everything guest-side, with room for future services (RTG, clipboard, ...) on the same board:
guest/services/-- a tiny handler in C plus an asm entry table, built with the dockerized m68k-amigaos GCC 16 (stefanreinauer/amiga-gcc:gcc-v16.1), compiled-mpcrelso the ROM is relocation-free and runs at whatever base autoconfig assigns (the Makefile fails the build if relocations or data/bss sneak in). The built ROM is committed asassets/services/services_rom.bin(~600 bytes), so a plaincargo buildneeds no cross-toolchain. The DiagArea is embedded in the ROM at +0x40, like real autoboot board ROMs.AddBootNode(ADNF_STARTPROC), so DOS mounts and starts the handlers at boot. Each mount's boot priority comes from a per-unitDosEnvecthe emulator writes into the board window; when a HOSTFS node wins the boot vote, the DiagArea's BootPoint runs the standard autoboot sequence (FindResident("dos.library"),jsr rt_Init) and DOS mounts it as SYS:.WaitPort/GetMsg, one reserved A-line trap per DosPacket (D1 = packet, A1 = port),PutMsgthe reply. All ACTION_* semantics live host-side insrc/filesys.rs(FilesysHle, the CPU's high-level-emulation hook), which reads/writes guest memory directly.AddDosEntrys it (a dedicated trap return code -- only guest code may take the DosList semaphore).id_VolumeNode/fl_Volumepoint at it, which is what makes Workbench icons and C:Info work.src/amigaos/dos.rsmirrors the NDK dos.h/dosextens.h constants and structures asrepr(C)structs of big-endian fields (zerocopy), so the Rust definition is the guest layout and one write serializes it -- no magic offsets at call sites. Guest-visible objects (FileLocks, volume nodes) are allocated from board-window pools, so the host never calls guestAllocMem.Path resolution follows FFS semantics, verified against the real FFS handler (a single trailing slash is the directory itself: "Prefs/" lists Prefs, "Prefs//" its parent; names are case-insensitive; an
Assign:prefix in a packet name resolves relative to the supplied lock, like UAE's get_aino).Status
AddBootNodemounts, handler autostartFileSysStartupMsg/DosEnvec, so Early Startup showsCLFS hostfs-Nbootpriconfig option; BootPoint boots DOS off a HOSTFS volumeACTION_DIEdismount: volume off the DosList, handler process exits; refused withERROR_OBJECT_IN_USEwhile locks or files are open (protects the boot volume).dn_Taskis cleared, so the next access to the device restarts the handler and remounts the volumeDISK_INFO/INFOwith real statvfs sizes, UAE-style block scalingIS_FILESYSTEMLOCATE_OBJECT,FREE_LOCK,COPY_DIR(DupLock),PARENT,SAME_LOCKEXAMINE_OBJECT,EXAMINE_NEXT(dir, list),EXAMINE_FHEXAMINE_ALL(DOS falls back toEXAMINE_NEXT)FINDINPUT,READ,SEEK,END,FH_FROM_LOCK,PARENT_FH,FLUSH(type, copy-from)FINDOUTPUT/FINDUPDATE,WRITE,SET_FILE_SIZE(all write actions refused withERROR_DISK_WRITE_PROTECTED, andid_DiskStatereports Read Only).uaemsidecars (+s/+p/+a; sidecars hidden from listings); host read-only maps tow-deniedSET_PROTECT(accepted and ignored; persist to.uaemonce writes land)CREATE_DIR,DELETE,RENAME,SET_DATE,SET_COMMENTRENAME_DISK, Latin-1 filename mappingTesting
info/dir/list/type/copyall behave.bootpri = 5, and survives warm reboots.dirremounts it, and dismounting the boot volume is refused with error 202.🤖 Generated with Claude Code - hand edited afterwards