Skip to content

vms-329: VAX runtime reads SYS over the executive ACP (the real flip) — sysboot+image-activation green, install-menu reached - #706

Merged
baron-3dl merged 15 commits into
mainfrom
merge/vms-c45-w2
Aug 22, 2026
Merged

baron-3dl merged 15 commits into
mainfrom
merge/vms-c45-w2

Conversation

@baron-3dl

Copy link
Copy Markdown
Contributor

Summary

  • The VAX ACP-flip cutover: changes the mount contract, DCL/RMS ACP arms, kernel-netbsd dispatch, and build config across vmsdcl/libvms/vmsrms + mk_dcl.sh/mk_libvms_shr.sh.
  • Stages [USERS]+[SYSTMP] on the VAX volume; reverts the temporary ACP block trace.
  • New tests/lab-vax/drive_install_vax.py driver + updated stage_sysvol.sh/run-boot.sh.

Schema/contract change (has_schema_change: true) — this rides the 3-way convergence gate (x86_64 CI + VAX ILP32 + Alpha LP64). DO NOT MERGE on this PR's CI alone — merge is held for explicit go-ahead once the conductor confirms all three legs.

Test plan

  • CI watched to terminal green/red (this PR)
  • Conductor confirms VAX ILP32 + Alpha LP64 legs converge (external, tracked by conductor)

🤖 Generated with Claude Code

baron-3dl and others added 15 commits August 21, 2026 19:32
…(faithful flip)

Retire the vmsfs.ko + POSIX SYS$DISK bypass on VAX. The VAX runtime now reads and
writes SYS$DISK over the executive ACP (IO$_ACCESS/READVBLK/WRITEVBLK/$MOUNT via
/dev/vms) -- the same executive path Linux uses. Option A (conductor-approved):
rename __linux__ -> OVMX_HAVE_ACP so RMS/boot call vms_kif_acp_* with zero platform
knowledge; the POSIX arm is kept only for the kernel-less unit build. Shared
kernel-core vmsfs_acp.c is BYTE-UNCHANGED -- only added to the module SRCS.

Kernel (NetBSD vms.kmod):
- vms_blockdev_netbsd.c (NEW): the exec_blockdev_read/write_block seam + the
  single-unit device-native disk resolve (vms_devtab_disk_backing). Transliterates
  the proven vmsfs.kmod path -- vn_bdev_openpath-cached devvp + bread / getblk+bwrite,
  vn_close(FREAD|FWRITE). Quarantined TU (avoids the exec_rbtree/sys/rbtree collision).
- vms_netbsd.c: 9 IOCTL_ACP_* dispatch cases + the readvb/writevb bounce seam (the
  ACP's separate user-buffer pointer needs a manual bounce like vms_mbx_bigio; the
  shared handler and exec_copyout contract stay unchanged) + vms_acp_init/cleanup +
  per-proc vms_acp_release_all.
- exec_kbackend_netbsd.h: real exec_blockdev_* under OVMX_ODS2_KERNEL.
- Makefile + build-vms-module-vax.sh + build-devvms-vax.sh: add vmsfs_acp.c +
  ods2_reader.c + ods2_edit.c + vms_blockdev_netbsd.c, -DOVMX_ODS2_KERNEL.

Userspace: rms_core/io/search.c + rms_internal.h __linux__ -> OVMX_HAVE_ACP;
OVMX_HAVE_ACP build define on vmsrms (non-standalone) + ovmx_init; ovmx_boot_netbsd.c
ACP $MOUNT flip (mirrors ovmx_boot_linux.c).

Verified: elf32-vax kernel cross-compile ILP32-clean; loadable vms.kmod links with
all OVMX/codec symbols resolved (modload-ready); userspace ovmx-images cross-build
green with the flip. SIMH sysboot-over-ACP runtime proof (INV-6 hash-diff) in flight.

DKA0:->DUA0: MSCP device-native rename tracked as vms-47d (resolve accepts both).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ot-over-ACP GREEN

run-boot.sh's hand-rolled vmsfs_master compile predates the flip's move of
tools/vmsfs_master.c to the genuine ODS-2 codec (vms-5eb R6-build): it passed only
-I src/kernel/vmsfs and no codec sources, so "vmsfs/ods2.h" was not found and the
sysboot SYSTEM-volume mastering died before the boot ever ran. Baseline dd452f5
fails identically -- this is a flip-era harness regression, NOT an ACP issue.
Mirror tools/CMakeLists.txt's vmsfs_master target: add -I src/vmsfs/include + the
six ods2 codec sources (reader/writer/edit/bdev/path/block_posix).

With this, the SIMH sysboot-over-ACP proof PASSES end-to-end on real NetBSD/vax:
ovmx_init boots as PID 1, mounts SYS$DISK over the EXECUTIVE ACP (the vmsfs mount
is #else, not compiled), PROVISION.EXE demand-pages off the ODS-2 volume and RUNS
(SYSTEM [1,4] identity), stamps UIC file ownership, and reaches Username:. The
INV-6 hash-diff confirms the on-disk bytes REALLY changed -- proof the ACP read
AND write paths (exec_blockdev_read/write_block + the readvb/writevb bounce) ran,
with no false-pass path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…MX$INSTALL.COM)

stage_sysvol.sh gains a --distribution --kit <OVMX-OS-VAX.KIT> mode, the netbsd-vax
mirror of the x86_64 install-media flow (design-vms-faithful-install.md §3.3):
the distribution disk differs from an installed system disk ONLY in its payload —
(a) the OS kit at SYS$UPDATE:OVMX-OS-VAX.KIT, and (b) a distribution SYSTARTUP_VMS.COM
(the VAX Decision-A variant + one $ @sys$MANAGER:OVMX$INSTALL.COM block) so the
mastered volume boots into the install menu. Default (non-distribution) mode is
byte-identical. OVMX$INSTALL.COM reused byte-for-byte. Verified: real OVMX-OS-VAX.KIT
packed + staged + mastered, kit present at SYS$UPDATE:, SYSTARTUP invokes the menu.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…IZE formats the real target)

INITIALIZE.EXE resolves a VMS disk unit to its real backing block device via
vms_kif_disk_resolve; the flip wired the 9 ACP ioctls but not DISK_RESOLVE, so on
VAX INITIALIZE of a blank target could not format it. Backfill (shared kernel-core
BYTE-UNCHANGED): the Linux handler uses the full devtab (not ported to VAX), so a
VAX-side vms_ioctl_disk_resolve reuses the existing single-unit resolve.
- vms_acp_nb.h: struct vms_diskresolve_args twin (48B) + VMS_IOCTL_DISK_RESOLVE
  _IOWR(0x57) with a compile-time assert == 0xC0305657u (ABI-identical to Linux).
- vms_blockdev_netbsd.c: unit map + install target (DKA100:/DUA100: -> ra2c);
  transient vms_devtab_disk_resolve (open->v_rdev->close, no cache, fail-honest
  SS$_NOSUCHDEV); the handler (plain memcpy, quarantined TU).
- vms_netbsd.c: VMS_IOCTL_DISK_RESOLVE dispatch case (ACP _IOWR group).
Verified: both elf32-vax cross-builds green (ILP32 + modload-ready), symbols defined.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… proof in flight

drive_install_vax.py (new): forks do_sysboot's SIMH/KA655 skeleton, reuses the
console helpers verbatim (expect_wake/_console_text/_hard_kill + the HARNESS_ERROR/
PROOF_FAILED contract), attaches the distribution volume (rq1->ra1->DKA0:) + a BLANK
ODS-2 target (rq2->ra2->DKA100:, both writable), boots straight into OVMX$INSTALL.COM
(no login), and drives its real PRESERVE path (1/PRESERVE/DKA100:/WORK/YES ->
%MOUNT-I-MOUNTED -> %PCSI-I-DONE -> SYSTEM password -> %UAF-I-SAVED -> SCSNODE/SCSID
-> SYSGEN -> %DISMOUNT -> "installation is complete"). Mirrors the x86_64 assertions:
%PCSI-I-DONE with no %PCSI-[EF]-, rooted DKA100:[SYS0.SYSCOMMON.SYSEXE]DCL.EXE present,
flat [SYSEXE]DCL.EXE ABSENT (%RMS-E-DNF), and the anti-LARP run-an-installed-image-
FROM-the-target (RUNX :== $DKA100:...DCL.EXE + -c "SHOW TIME", no crash).

run-boot.sh: new `install` mode — cross-builds the REAL utility images
(PRODUCT/AUTHORIZE/INITIALIZE/SYSGEN.EXE via ovmx-images), packs OVMX-OS-VAX.KIT,
masters the distribution volume (stage_sysvol.sh --distribution), formats a blank
ODS-2 target, attaches both, and sha256s the target before/after (INV-6 write-proof).
drive_boot_vax.py: assembly now `MAKEDEV ra1 ra2` so /dev/ra2c (DKA100:) exists.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…f4b4)

Brings the SIMH e2e proof harness onto the branch: tests/lab-vax/run-boot.sh
'install' mode + tests/lab-vax/drive_install_vax.py + the two-disk distribution
staging (stage_sysvol.sh), plus vms-f60's VMS_IOCTL_DISK_RESOLVE and the
DKA100:/DUA100: -> /dev/ra2c unit-map entry the installer needs.

Conflict resolution:
 - src/kernel-netbsd/{vms_blockdev_netbsd.c,vms_internal.h,vms_netbsd.c}: took
   the install branch (strict superset -- main lacked vms-f60's DISK_RESOLVE).
 - src/ovmx_init/ovmx_boot_netbsd.c: took main (keeps piece A's
   ovmx_boot_prepare_stage_dir, which the install branch predates).
Turns the shipped netbsd-vax runtime onto the executive Files-11 ACP, all at
once -- NetBSD's spec_vnops allows exactly ONE open of /dev/ra1c, so the ACP
$MOUNT and the vmsfs.ko VFS mount of SYS$DISK cannot coexist and no
transitional half-state exists (rd vms-049's finding).

 * tools/cross-vax/build-ovmx-init-vax.sh (the SHIPPED PID 1): define
   OVMX_HAVE_ACP + OVMX_BOOT_ACP_BRIDGE, link ovmx_boot_acp_read.c +
   ovmx_boot_sysgen_acp.c + src/imgact/imgact_acp.c, drop _POSIX_C_SOURCE so
   the recipe matches the CMake target and the standing ILP32 audit. New
   weak-seam anchor: STARTUP.EXE must resolve ovmx_sysgen_acp_read STRONG, and
   ovmx_boot_netbsd.o must reference vms_kif_acp_mount (teeth for the macro).
 * src/ovmx_init/CMakeLists.txt: bridge TUs on EVERY substrate; OVMX_HAVE_ACP +
   OVMX_BOOT_ACP_BRIDGE unconditional (reconciles vms-8e8f's flagged split).
 * ovmx_boot_netbsd.c / ovmx_boot_linux.c / ovmx_boot.h: RETIRE
   ovmx_boot_mount_system_disk() -- the VFS mount seam op is gone, not merely
   unused; mount_system_disk_native() is ACP-only with no fallback arm.
 * ovmx_init.c: conversational SYSBOOT path is ACP on every substrate (the
   netbsd VFS #else branch deleted); IMGACT.EXE staging is Linux-only (vax
   activates through ld.elf_so, Decision A -- no IMGACT.EXE on the volume).
 * src/vmsrms/CMakeLists.txt: OVMX_HAVE_ACP for the standalone netbsd-vax cross
   too -- that configuration is not a unit build, it is the libvmsrms.a linked
   into the shipped VAX DCL/LOGINOUT/PROVISION/JOB_CONTROL.
 * ovmx_provision.c: re-key the ACP provisioning arms from __linux__ to
   OVMX_HAVE_ACP (+ define it in the CMake target and the vax recipe, with
   teeth). With /vms gone the POSIX lchown arm would swallow every ENOENT and
   provision nothing while reporting success.

Not yet SIMH-proven -- pushed before the long cross build (host reaper).
… block trace)

First traced cutover boot: the executive ACP opened /dev/ra1c and bread() LBN 1
with NO VFS mount in the picture -- and the bytes came back 53 46 4d 56 ("SFMV"),
the OVMX VMFS superblock. vmsfs_acp.c's acp_validate_ods2() therefore refused
the mount (SS$_DEVNOTMOUNT -> %OVMX-F-SYSINIT), correctly: tests/lab-vax
mastered every volume with vmsfs_master's DEFAULT format, which is the bespoke
VMFS, not Files-11 ODS-2. Fix the media, never the validation:

 * run-boot.sh: --ods2 on the system volume, the distribution volume, the blank
   install target, and their content-gating list passes.
 * new master_sysneg_volume(): the sysboot negative control gets its OWN
   genuine-ODS-2 volume with an EMPTY tree, so it still MOUNTS and then fails
   the installed-system gate on the missing SYS$SYSTEM:DCL.EXE. Reusing the
   VMFS ODS2_IMG would have halted one step earlier and silently gutted the
   control; ODS2_IMG stays VMFS for the sibling vmsfs VFS-module drivers.

TEMPORARY (reverted before this branch is handed over): the OVMXTRACE printf in
vms_blockdev_netbsd.c that produced the evidence above.
…DASSGN

Second traced cutover boot: the ACP $MOUNT of DKA0: SUCCEEDED on the VAX
(%OVMX-I-MOUNTED, with the trace showing real bread()s of the genuine ODS-2 home
block at LBN 1, the INDEXF header and the SCB, with NO VFS mount) -- and then
require_installed_system() said DCL.EXE was absent, with ZERO further block
reads. No reads means imgact_acp_open() failed before it ever touched the disk:
src/kernel-netbsd/vms_netbsd.c answers neither VMS_IOCTL_REGISTER nor
VMS_IOCTL_DASSGN, so acp_register() got ENOTTY -> SS$_NOSUCHDEV and EVERY ACP
file open on this substrate failed. The ops were never missed before because
nothing in-kernel needs them (every NetBSD ioctl path find-or-creates the PCB
via vms_proc_get) and no VAX image had ever opened an ACP file.

 * vms_proctab_nb.h: struct vms_register_args / vms_dassgn_args + the request
   numbers, encodings _Static_assert-pinned to the Linux build's command words
   (0xC0085640 / 0xC0085641 / 0xC0085651).
 * vms_netbsd.c: REGISTER/_CONTINUE returns the PCB vms_proc_get builds anyway
   (the Linux twin's adopt-or-create); DASSGN mirrors the Linux fallback chain
   minus the device table this substrate does not carry -- mailbox, then
   file-class, else SS$_IVCHAN. No fabricated success (INV-6).

Substrate dispatch only; src/kernel-core is untouched.
…nt on a read-only NetBSD root

Third traced cutover boot (after the REGISTER/DASSGN fix): the executive ACP
mounted DKA0:, and the ACP file walk now REALLY RUNS on the VAX -- the trace
shows genuine directory-block bread()s (LBN 108/109/128, "SY"/"SY"/"BU" name
bytes) and require_installed_system() FOUND SYS$SYSTEM:DCL.EXE over IO$_ACCESS.
The boot then halted one step later:

    %OVMX-F-SYSINIT, cannot create the boot-image staging directory
    %OVMX-I-SYSINIT, Read-only file system

ovmx_boot_prepare_stage_dir() mkdir(2)s /run and /run/ovmx-boot before mounting
the staging tmpfs. mount(2) does not write to the underlying filesystem, but
mkdir(2) does, and this boot runs on a READ-ONLY root (the assembly session ends
with `mount -u -r /'), so both mkdirs return EROFS.

 * drive_boot_vax.py: create /run/ovmx-boot alongside /vms /proc /dev/pts
   /dev/shm when the bootable disk is assembled -- the same class of thing (a
   mount point a shipped OVMX/NetBSD root carries), the same read-only-root
   reason.
 * ovmx_boot_netbsd.c: document that contract where the mkdirs live. They stay,
   and EROFS on a root without the directory still halts honestly (INV-6) rather
   than staging into nowhere.
…VE_ACP

Third traced cutover boot got PID 1 all the way through the ACP file walk --
installed-system gate PASSED over IO$_ACCESS, ~20 first-hop/utility/shareable
images STAGED off the ODS-2 volume through the ACP, PROVISION.EXE demand-paged
and RAN (%STDRV-I-STARTUP, 'system identity SYSTEM') -- and then:

    %OVMX-I-STARTUP, handing SYS$MANAGER:STARTUP.COM to DCL for ACP resolution
    %DCL-E-OPENIN, error opening SYS$MANAGER:STARTUP.COM as input

Three ACP arms were still gated on __linux__ from when the netbsd-vax runtime
VFS-mounted SYS$DISK and so still had a /vms POSIX tree to fall back to. The
coupled cutover retired that mount: on the VAX those fallbacks now reach
nothing at all. The code behind each gate is already substrate-neutral -- this
is a definition, not a port. (It also removes three __linux__ source forks,
which build-boot-images-vax.sh's INV-DRIFT note forbids in this image set.)

 * vmsdcl/dcl_script.c        -- @-procedure open over RMS/ACP (the blocker).
 * vmsdcl/dcl_cmd_process.c   -- activatable-image resolution through the ACP;
   without it DCL cannot find ANY image on the VAX post-cutover.
 * libvms/rtl/rms_textfile.c  -- SYSUAF / RIGHTSLIST / $GETUAI / OPERATOR.LOG.
   A SYSUAF that reads as EMPTY is worse than one that fails: it looks like 'no
   such account'.
 * src/vmsdcl/CMakeLists.txt + src/libvms/CMakeLists.txt: define OVMX_HAVE_ACP
   on the target, including the netbsd-vax STANDALONE archive (that archive is
   not a unit build -- it is the libvmsdcl.a/libvms.a that become the shipped
   VAX DCL.EXE/LOGINOUT.EXE/PROVISION.EXE/JOB_CONTROL.EXE), exactly as
   src/vmsrms/CMakeLists.txt already reasons.
 * src/vmslink/mk_dcl.sh + mk_libvms_shr.sh: the NATIVE-LINK recipes compile
   these TUs themselves and satisfied __linux__ for free. Without an explicit
   -DOVMX_HAVE_ACP the x86_64 native-link DCL.EXE/LIBVMS$SHR would SILENTLY
   lose arms the CMake build keeps -- a regression no compile gate can catch.
   Byte-for-byte a no-op on x86_64; the VAX is where the arms are new.

OBSERVED, NOT CHANGED: mk_vmsrms_shr.sh defines no OVMX_HAVE_ACP either, so the
native-link LIBVMSRMS$SHR is built with RMS's ACP arms compiled OUT. That
predates this item and is not on the VAX path (the VAX links the CMake
libvmsrms.a, which does define it); turning it on would change the native-link
symbol vector, so it is left for its own item rather than smuggled in here.
…ry ACP block trace

Fourth traced cutover boot: DCL opened SYS$MANAGER:STARTUP.COM OVER THE ACP and
EXECUTED it -- SYSTARTUP_VMS.COM ran ('The OVMX system is now executing the
site-specific startup commands') and $RUN created a real process
(%RUN-S-PROC_ID 00000067). One failure left, and it is a MEDIA gap the cutover
EXPOSED rather than caused:

    %OVMX-W-OWNER, home directory SYS$SYSDEVICE:[USERS.DEFAULT] did not resolve
                   over the ACP (parent missing?)          (x4: DEFAULT/GUEST/USER1/USER2)

The vax system volume has no [USERS]. distro/Dockerfile.bootable creates
/system-stage/vms/{USERS,SYSTMP} before mastering the x86_64 image and gates on
']USERS.DIR;' appearing in the listing -- git cannot carry an empty directory,
so every mastering step must create them itself, and stage_sysvol.sh never did.
Pre-cutover this was INVISIBLE: PROVISION's home pass lchown()ed a /vms
passthrough path, a missing parent came back ENOENT, and it was silently
swallowed -- four home directories 'provisioned' that never existed. The ACP arm
resolves the parent DID for real and says so. Fixing the media, not the report.

 * stage_sysvol.sh: mkdir [USERS] + [SYSTMP] in the staged tree (the vax twin of
   the Dockerfile.bootable step), so PROVISION CREATEs each account home under
   [USERS] over IO$_CREATE with the account's UIC.
 * run-boot.sh: content-gate the mastered system volume on ']USERS.DIR' -- the
   vax twin of the Dockerfile.bootable gate, so a staging regression fails at
   mastering instead of three minutes into a SIMH boot.

 * vms_blockdev_netbsd.c: REVERT the temporary OVMXTRACE printfs from f03666d.
   They did their job -- they are what proved the executive ACP opens /dev/ra1c
   and bread()s the genuine ODS-2 home block / INDEXF header / SCB / directory
   blocks with NO VFS mount anywhere in the picture, and the four boot logs
   holding that evidence are the receipt. No debug output ships.
…ed script banner

The vms-329 ACP flip retired the ovmx_boot_mount_system_disk() boot op (the
vmsfs.ko VFS mount of SYS$DISK), dropping the NetBSD boot backend from 9 to 8
ovmx_boot.h ops. build-ovmx-images-vax-cmake.sh was updated to remove that
symbol from its per-op nm check and reword its banner to the count-neutral
"OK: every ovmx_boot.h op is defined by the NetBSD backend", but the
vax-cmake-images job's post-build grep in ci.yml still asserted the stale
"OK: all 9 ovmx_boot.h ops defined by the NetBSD backend" banner. The docker
build + 10/10 parity check passed; only the migrated-teeth grep failed
("FAIL: the NetBSD boot-backend ops check did not run"), reddening the whole
job. Align the grep to the script's actual banner. No script/CMake/link change
-- the aggregate build and parity closure were already green.

Co-Authored-By: Claude Opus 4.8 (1M context) <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.

1 participant