vms-707: DCL RUN surfaces the image's executive-recorded $STATUS (not 0x1) - #853
Merged
Merged
Conversation
…exit
The fork()+execve() fallback in dcl_activate_image collapsed the POSIX child
exit to SS$_NORMAL/SS$_ABORT, discarding the image's real VMS condition value:
an image whose main returned 3 reported $STATUS %X00000001 instead of the
faithful DEC C encoding the executive recorded at SYS$EXIT.
Wire RUN's fork path to read the image's executive-recorded completion $STATUS,
mirroring the in-process path. Because the forked child shares DCL's VMS PID
(REGISTER_CONTINUE), a by-VMS-PID read is ambiguous; add a by-Linux-pid GETEXIT
selector (VMS_JPI_SEL_LINUX_PID) and read the child's row before waitpid reaps
it (WNOWAIT peek). Fall back to the POSIX-derived verdict only when no image
recorded a status (no /dev/vms, or a foreign tool that never $EXITs).
- src/kernel/vms_ioctl.h, src/kernel-netbsd/vms_proctab_nb.h: SEL_LINUX_PID (no
struct/ioctl-number change; reuses the args' vms_pid field for the Linux pid).
- src/kernel-core/vms_proctab.c: find_by_linux_pid + SEL_LINUX_PID case (shared
by the Linux vms.ko and the NetBSD module).
- src/libvmssys/vms_kif.{c,h}: vms_kif_getexit_linux wrapper.
- src/vmslink/libvmssys_shr.vec: append vms_kif_getexit_linux (append-only).
- src/vmsdcl/dcl_cmd_process.c: RUN fork path reads the recorded $STATUS.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend test_kmod_exit (kernel-executive QEMU suite, real /dev/vms) with Part 5: a REGISTER_CONTINUE child records COND_RUN3 (0x0035A019 -- the faithful DEC C encoding of main returning 3), the parent confirms the child SHARES its VMS PID (so a by-VMS-PID read is ambiguous), then reads the child's $STATUS by its Linux pid in the zombie window (WNOWAIT peek, before reap) -- the exact sequence dcl_activate_image now runs -- and asserts the EXACT condition value round-trips, not a POSIX-collapsed 0x1. Includes a negctl: an unbacked Linux pid reads SS$_NONEXPR, never a fabricated status. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… $STATUS Root cause the SEL_LINUX_PID readback exposed: the executive PCB is keyed on the thread group, not the /dev/vms channel, and vms_dev_release() frees the PCB when the LAST channel closes at the owning task's exit. DCL's RUN fork child opens a channel via REGISTER_CONTINUE and then execve()s the image; because /dev/vms was opened WITHOUT O_CLOEXEC, that channel LEAKED into the image (no userspace handle referenced it), stayed open across the whole run, and its implicit close at the image's do_exit() freed the PCB -- destroying the image's recorded completion $STATUS before DCL could read it back. So RUN would always fall back to the POSIX-collapsed status even with the readback wired. Open /dev/vms O_CLOEXEC. The channel now closes at execve() (not a free -- the task is not exiting), the image re-binds its own channel (kif_bind) and closes it before SYS$EXIT, so at do_exit() no channel is open and the PCB survives to lazy reap -- long enough for RUN to read the recorded $STATUS in the waitid(WNOWAIT) window. Nothing relies on inheriting a /dev/vms channel across execve: the executive keys on the thread group and every task re-binds on its first kif call. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…S peek DCL's RUN fork path uses waitid(WNOWAIT) to peek the exited image without reaping it, so the executive PCB survives long enough to read the recorded $STATUS. The VMS-native LINK.EXE build resolves every external against a --use'd shareable's symbol vector, so DCL.EXE referencing waitid failed with %LINK-F-ERROR until waitid was a DECC$SHR universal. waitid is a POSIX.1 C RTL entry point; add it to the DECC$SHR CRTL surface map (musl-backed, alongside wait3/wait4/waitpid). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…or placement) The prior commit added waitid to decc_crtl_map.txt, but that map produces the DECORATED decc$waitid alias (for alpha-dec-vms port objects), while DCL is x86_64/ aarch64 musl-compiled and references the BARE waitid -- which resolves against the curated bare-universal block in mk_decc_shr.sh, not the decc$ alias map. DCL.EXE still failed %LINK-F-ERROR 'waitid'. Revert the map edit (its mid-list insertion also shifted decc$ alias indices) and instead append bare waitid=PROCEDURE at the very END of the DECC$SHR symbol vector -- append-only, so no prior universal's index moves (GSMATCH LEQUAL-compatible). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nce gate) Add a minimal VMS-native RUN target (src/apps/rctest/rc3.c + mk_rc3.sh, same cc->LINK.EXE toolchain as PARTS.EXE) that prints one line to SYS$OUTPUT and exits cleanly; master it into SYS$SYSTEM: on the bootable disk. tests/qemu/test_run_status_e2e.sh boots the runtime, logs in SYSTEM/MANAGER, RUNs RC3.EXE, and asserts the reworked RUN fork path (waitid(WNOWAIT) peek -> executive $STATUS readback by Linux pid -> reap) still, on the real runtime: (1) routes the activated image's stdout to the console, and (2) reports SS$_NORMAL for the clean exit through the new readback path (never a hang, spurious error, or wrong value). The FAITHFUL-ENCODING half (a bit<0>-set condition C$_EXIT1+(N-1)*8 surviving to $STATUS instead of collapsing to %X00000001) is an Alpha GCC-port property (IMGACT VMS-standard activation, which x86_64 lacks -- imgact.c stub): proven at the executive level by test_kmod_exit.c Part 5 (real /dev/vms) and end-to-end by the Alpha crtl_rms re-run this change unblocks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
baron-3dl
force-pushed
the
work/vms-707
branch
from
August 28, 2026 18:06
e266cfa to
2d880d7
Compare
run_run_status_e2e.sh + CMake add_test mirror dcl_acceptance_e2e: opt-in behind OVMX_QEMU_FULL_E2E=1, SKIP_RETURN_CODE 77, TIMEOUT 1200. The gate boots the real runtime, RUNs RC3.EXE, and asserts the reworked RUN fork path routes the image's stdout to the console and reports its completion status via the new waitid-peek/getexit readback path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts: # tests/qemu/CMakeLists.txt
baron-3dl
added a commit
that referenced
this pull request
Aug 28, 2026
…pped gap) This arch-asymmetric boot regression slipped the per-PR gate TWICE now (#835 tracepoints, #853 O_CLOEXEC/O_DIRECT): a change reds the OVMX/Alpha boot while x86_64 stays green, so the author's x86_64-only proofs pass and it merges. The existing per-PR Alpha guard (vms-5af) is BUILD/modpost ONLY -- it proves vms.ko cross-compiles + links + modpost-passes for Alpha, never that the assembled runtime BOOTS and MOUNTS the system disk. #853 modpost-passed cleanly for Alpha yet halted at $MOUNT. Add job alpha-boot-mount: on a pull_request that touches the runtime-executive change classes (new path filter alpha_boot_runtime: src/kernel/**, src/kernel-core/**, src/libvmssys/**, src/ovmx_init/**, tools/cross-alpha/**), run tools/cross-alpha/run-boot-alpha.sh gate -- boot qemu-system-alpha to a real DCL Username:, which proves the DKA0: $MOUNT (mount is a strict prerequisite of reaching login). Scope-gated so the fast PR wall is preserved for the majority of PRs that don't touch the executive; PR-only, since schedule/workflow_dispatch already run the fuller alpha-boot-login / alpha-dcl-acceptance legs. The boot's dominant cost is the cross-build (identical for a mount-only vs Username assertion), so the stronger post-condition is asserted for the same wall time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
baron-3dl
added a commit
that referenced
this pull request
Aug 28, 2026
… on Linux; Alpha O_CLOEXEC != O_DIRECT) (#867) * vms-707: fix Alpha /dev/vms open -- O_ flag ABI is per-arch on Linux (Alpha O_CLOEXEC != generic) PR #853 (cfcd5b5) added O_CLOEXEC to the /dev/vms open in kif_xport_dev_open() so DCL's RUN fork child's channel closes at execve and the executive PCB survives for the $STATUS readback. That was correct on x86_64/aarch64 but broke the Alpha boot: the system disk would not $MOUNT and PID 1 halted with %OVMX-F-SYSINIT, system disk DKA0: (/dev/vda) would not mount. Root cause -- NOT a PCB-lifecycle problem. The freestanding libvmssys O_* flag constants (vms_types.h, Linux raw-syscall ABI block) were hardcoded to the asm-generic numbering used by x86_64/aarch64. Alpha inherits the OSF/1-derived numbering (arch/alpha/include/uapi/asm/fcntl.h), where the octal codes differ: the GENERIC O_CLOEXEC bit 0x80000 is O_DIRECT on Alpha (verified against the alpha-linux-gnu sysroot header). So on Alpha, open("/dev/vms", O_RDWR|0x80000) set O_DIRECT on a char device whose address_space has no ->direct_IO -- the VFS rejects that open with -EINVAL, the kif fd came back negative, and the ACP $MOUNT ioctl ran on a bad fd and failed. Arch-asymmetric: the exact bit that is O_CLOEXEC on x86_64 is O_DIRECT on Alpha, which is why #853's x86_64-only proofs passed and this slipped the per-PR gate. Fix: split the Linux O_* block by arch. Under __alpha__, define the OSF/1 values (O_CLOEXEC 0x200000, O_DIRECTORY 0x8000, O_CREAT 0x200, O_TRUNC 0x400, O_APPEND 0x8, O_NONBLOCK 0x4, O_EXCL 0x800, O_NOCTTY 0x1000); x86_64/aarch64 keep the generic values unchanged. O_RDONLY/O_WRONLY/O_RDWR (0/1/2) and the AT_* constants are identical on every Linux arch, so only the divergent flags split. A _Static_assert guards the exact hazard: on Alpha VMS_O_CLOEXEC must not be the generic 0x80000 (O_DIRECT). Values verified against the alpha-linux-gnu uapi/asm/fcntl.h; header compile-checked on both alpha-linux-gnu-gcc and gcc. This preserves BOTH: Alpha now opens /dev/vms with a real O_CLOEXEC bit (mount works, boots to Username:) and x86_64 keeps vms-707's RUN $STATUS readback. The latent landmine that all the OTHER O_* flags (O_CREAT/O_TRUNC/... used by vms_stdio.c) were also wrong on Alpha is fixed by the same split. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * vms-707: per-PR Alpha runtime boot-to-mount gate (close the twice-slipped gap) This arch-asymmetric boot regression slipped the per-PR gate TWICE now (#835 tracepoints, #853 O_CLOEXEC/O_DIRECT): a change reds the OVMX/Alpha boot while x86_64 stays green, so the author's x86_64-only proofs pass and it merges. The existing per-PR Alpha guard (vms-5af) is BUILD/modpost ONLY -- it proves vms.ko cross-compiles + links + modpost-passes for Alpha, never that the assembled runtime BOOTS and MOUNTS the system disk. #853 modpost-passed cleanly for Alpha yet halted at $MOUNT. Add job alpha-boot-mount: on a pull_request that touches the runtime-executive change classes (new path filter alpha_boot_runtime: src/kernel/**, src/kernel-core/**, src/libvmssys/**, src/ovmx_init/**, tools/cross-alpha/**), run tools/cross-alpha/run-boot-alpha.sh gate -- boot qemu-system-alpha to a real DCL Username:, which proves the DKA0: $MOUNT (mount is a strict prerequisite of reaching login). Scope-gated so the fast PR wall is preserved for the majority of PRs that don't touch the executive; PR-only, since schedule/workflow_dispatch already run the fuller alpha-boot-login / alpha-dcl-acceptance legs. The boot's dominant cost is the cross-build (identical for a mount-only vs Username assertion), so the stronger post-condition is asserted for the same wall time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
baron-3dl
added a commit
that referenced
this pull request
Aug 28, 2026
…E parity (vms-a5d, vms-431) (#877) Two CI gates whose checks never ran at merge (YAML-broken-CI blind window). Each fix makes the gate assert the CORRECT current product behavior; neither weakens, allowlists-out, or deletes a gate (INV-6). RED 2 (vms-a5d) — tests/dcl/test_lexical_getsyi.sh: F$GETSYI("VERSION") now emits the FAITHFUL fixed 8-char space-padded SYI$_VERSION field ("V9.2-3 "), landed by #866/vms-28a (ovmx_compat_version_field, OVMX_VMS_VERSION_FIELD_LEN=8). The old regex required the closing quote immediately after the version, so the faithful trailing pad failed it. Updated to (1) assert the version VALUE is well-formed, tolerating the pad, and (2) POSITIVELY assert the field is exactly 8 chars wide (regex X = "[^"]{8}") — the padding is the point, so the test asserts it rather than ignoring it. Value assertion + EXPECT_NOT V7.3/%DCL- preserved. Verified: built DCL.EXE emits `X = "V9.2-3 "`; harness reports `PASS: F$GETSYI returns system information`. A mis-padded 7-char field is still rejected (teeth intact). Connects to vms-f5d. RED 3 (vms-431) — tools/parity/image-parity-allowlist.json: RC3.EXE (the x86_64 DCL $STATUS-propagation RUN fixture, #853/vms-707) is a LINK.EXE-built VMS-native image (zero DT_NEEDED/DT_HASH, PT_INTERP=IMGACT.EXE) staged into SYS$SYSTEM: so `RUN SYS$SYSTEM:RC3.EXE` exercises the reworked IMGACT.EXE-over-ACP RUN path. It is legitimately x86_64-only: VAX activates images via NetBSD's own ld.elf_so (Decision A, vms-42d) — the same reason IMGACT.EXE itself is already allowlisted — and no VAX test references RC3 (it appears only under tests/qemu/). Added a Decision-A allowlist entry with that documented reason; did NOT blind-allowlist. Verified: parity gate suite 17/17 pass; x86_64_only == set(); RC3.EXE shows in allowlisted_x86_64_only. Gate teeth proven by test_unallowlisted_gap_fails. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem (vms-707, p1 — critical path)
DCL's
RUNverb forks a subprocess to activate an image and read its exit, but the fork()+execve() fallback indcl_activate_imagecollapsed the POSIX child exit toSS\$_NORMAL/SS\$_ABORT, discarding the image's real VMS condition value. An image whosemainreturned 3 reported\$STATUS%X00000001instead of the faithful DEC C encoding (C\$_EXIT1 + (3-1)*8 = 0x0035A019) the executive recorded atSYS\$EXIT. This blocked reading any fork-activated image's true exit status (e.g. the GCC-lane #846 crtl_rms sentinel).The in-process activation path already read the executive-recorded
\$STATUS(vms_kif_getexit); only the fork fallback collapsed it.Fix — RUN reads the executive-recorded condition value
VMS behavior: the image records its completion condition value and the invoking CLI reads it back as
\$STATUS. OVMX's fork child records its condition into its own executive PCB atSYS\$EXIT. Because that child shares DCL's VMS PID (REGISTER_CONTINUE), a by-VMS-PID read is ambiguous between DCL and the child — so RUN reads by the child's Linux pid, which names its PCB row uniquely, in the zombie window (WNOWAIT peek) beforewaitpidreaps it. Falls back to the POSIX-derived verdict only when no image recorded a status (no/dev/vms, or a foreign tool that never\$EXITs).src/kernel/vms_ioctl.h,src/kernel-netbsd/vms_proctab_nb.h:VMS_JPI_SEL_LINUX_PID(no struct/ioctl-number change; reuses the args'vms_pidfield).src/kernel-core/vms_proctab.c:find_by_linux_pid+ theSEL_LINUX_PIDGETEXIT case (shared by the Linuxvms.koand the NetBSD module).src/libvmssys/vms_kif.{c,h}+src/vmslink/libvmssys_shr.vec:vms_kif_getexit_linuxwrapper (census-satisfied: DCL calls it).src/vmsdcl/dcl_cmd_process.c: RUN fork path reads the recorded\$STATUS.stdout/stderr routing
Investigated:
tests/qemu/test_product_install_e2e.shalready RUNs an installed image via the fork path and asserts real console output, so fork-path stdout/stderr routing to the console already works. The BOOT-A "absent output" was the opt-inOVMX_IMGACT_SEAMline (off by default) plus that image's own path — not a routing regression. No routing change needed; the\$STATUScollapse was the substantive bug.Proof / regression gate
tests/qemu/test_kmod_exit.cPart 5 (kernel-executive QEMU suite, real /dev/vms): aREGISTER_CONTINUEchild records0x0035A019, the parent confirms it shares DCL's VMS PID, then reads the child's\$STATUSby Linux pid in the zombie window — the exactdcl_activate_imagesequence — and asserts the EXACT condition round-trips (not0x1), plus a negctl that an unbacked Linux pid readsSS\$_NONEXPR.The
\$STATUSsymbol chain is verified end-to-end:cmd_run→dcl_activate_imagereturnscond→dcl_set_statusrenders%X0035A019into the\$STATUSsymbol read bySHOW SYMBOL \$STATUS.🤖 Generated with Claude Code