Skip to content

filesys: mount host directories as AmigaDOS volumes#159

Open
codewiz wants to merge 17 commits into
LinuxJedi:mainfrom
codewiz:feat/filesys
Open

filesys: mount host directories as AmigaDOS volumes#159
codewiz wants to merge 17 commits into
LinuxJedi:mainfrom
codewiz:feat/filesys

Conversation

@codewiz

@codewiz codewiz commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Host-filesystem support: [[filesys]] config entries export host directories to the guest as AmigaDOS volumes:

# Define HOSTFS0:
[[filesys]]
path = "/home/bernie/emu/amiga/hd/WB"
volume = "WB"   # Optional, defaults to the directory name
bootpri = 5     # Optional boot priority; default -128 = never boot

# Multiple filesystems supported
[[filesys]]
path = "/home/bernie/emu/amiga/hd/Work"

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 -mpcrel so 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 as assets/services/services_rom.bin (~600 bytes), so a plain cargo build needs no cross-toolchain. The DiagArea is embedded in the ROM at +0x40, like real autoboot board ROMs.
  • Boot: Kickstart copies the DiagArea; its DiagPoint stub jsr's back into the ROM, which traps to the emulator and mounts one DeviceNode per config entry via AddBootNode (ADNF_STARTPROC), so DOS mounts and starts the handlers at boot. Each mount's boot priority comes from a per-unit DosEnvec the 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:.
  • Packets: the handler process is a pure pump -- WaitPort/GetMsg, one reserved A-line trap per DosPacket (D1 = packet, A1 = port), PutMsg the reply. All ACTION_* semantics live host-side in src/filesys.rs (FilesysHle, the CPU's high-level-emulation hook), which reads/writes guest memory directly.
  • Volumes: at handler startup the emulator builds a DLT_VOLUME DosList node in the board window and the guest AddDosEntrys it (a dedicated trap return code -- only guest code may take the DosList semaphore). id_VolumeNode/fl_Volume point at it, which is what makes Workbench icons and C:Info work.
  • DOS ABI layer: src/amigaos/dos.rs mirrors the NDK dos.h/dosextens.h constants and structures as repr(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 guest AllocMem.

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

Area Feature
Mounting Autoconfig board, DiagArea boot, AddBootNode mounts, handler autostart
Mounting Volume DosList node (Workbench icon, C:Info row)
Mounting Guest warm-reboot state reset (stale ports/locks in the emulator)
Mounting Real FileSysStartupMsg/DosEnvec, so Early Startup shows CLFS hostfs-N
Mounting bootpri config option; BootPoint boots DOS off a HOSTFS volume
Mounting ACTION_DIE dismount: volume off the DosList, handler process exits; refused with ERROR_OBJECT_IN_USE while locks or files are open (protects the boot volume). dn_Task is cleared, so the next access to the device restarts the handler and remounts the volume
Info DISK_INFO / INFO with real statvfs sizes, UAE-style block scaling
Info IS_FILESYSTEM
Locks LOCATE_OBJECT, FREE_LOCK, COPY_DIR (DupLock), PARENT, SAME_LOCK
Locks Access-mode enforcement (exclusive vs shared)
Examine EXAMINE_OBJECT, EXAMINE_NEXT (dir, list), EXAMINE_FH
Examine EXAMINE_ALL (DOS falls back to EXAMINE_NEXT)
Read I/O FINDINPUT, READ, SEEK, END, FH_FROM_LOCK, PARENT_FH, FLUSH (type, copy-from)
Write I/O FINDOUTPUT / FINDUPDATE, WRITE, SET_FILE_SIZE (all write actions refused with ERROR_DISK_WRITE_PROTECTED, and id_DiskState reports Read Only)
Metadata Protection bits, comments, and exact datestamps from UAE .uaem sidecars (+s/+p/+a; sidecars hidden from listings); host read-only maps to w-denied
Metadata SET_PROTECT (accepted and ignored; persist to .uaem once writes land)
Metadata CREATE_DIR, DELETE, RENAME, SET_DATE, SET_COMMENT
Misc Notifications, RENAME_DISK, Latin-1 filename mapping

Testing

  • Unit tests lock the board-window layout, the DOS struct sizes/serialization, path resolution (assign-lock prefixes), block-count scaling, and DateStamp conversion.
  • Multiple HostFS volumes mount on emulated config (A1200/040 OS3.x).
  • info / dir / list / type / copy all behave.
  • Workbench can browse volumes and copy files to RAM:.
  • Boots a production Workbench (OS 3.2, SetPatch, ENVARC: copy, WBStartup, DOpus5) directly off a host directory with bootpri = 5, and survives warm reboots.
  • Dismount cycle: ACTION_DIE removes the volume and ends the handler task (watched in Scout), a later dir remounts it, and dismounting the boot volume is refused with error 202.

🤖 Generated with Claude Code - hand edited afterwards

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>
@codewiz codewiz changed the title filesys: mount host directories as HOSTFS volumes filesys: mount host directories as AmigaDOS volumes Jul 11, 2026
@codewiz

codewiz commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

I ran out of Fable credits while testing the .uaem metadata parser.

My next goals are:

  • Complete read-only functionality
  • Get Workbench to boot
  • Performance (ExAll, caching .uaem files...)
  • Finally, write support.

codewiz and others added 3 commits July 11, 2026 20:51
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>
@codewiz codewiz marked this pull request as ready for review July 11, 2026 13:58
@LinuxJedi LinuxJedi requested a review from Copilot July 11, 2026 17:54

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 build workflows.

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.

Comment thread src/filesys.rs
Comment thread src/cpu.rs Outdated
codewiz and others added 7 commits July 12, 2026 02:52
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>
@codewiz

codewiz commented Jul 12, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 19 changed files in this pull request and generated 4 comments.

Comment thread src/filesys.rs
Comment thread src/filesys.rs Outdated
Comment thread src/filesys.rs Outdated
Comment thread src/config.rs
Comment on lines +1001 to +1005
for m in &self.filesys {
if !m.path.is_dir() {
anyhow::bail!("[[filesys]] path {} is not a directory", m.path.display());
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@codewiz

codewiz commented Jul 12, 2026

Copy link
Copy Markdown
Contributor Author

Config GUI proposal (I could push it as a separate branch and open a PR if you want to review it):

image

codewiz and others added 4 commits July 12, 2026 19:17
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants