From ae2e7855c9c078852fa5a67c7dc4fd4bafec1197 Mon Sep 17 00:00:00 2001 From: alice Date: Wed, 9 Sep 2026 14:09:42 +0000 Subject: [PATCH] =?UTF-8?q?vms-3320:=20extend=20the=20alpha=20CRTL->RMS=20?= =?UTF-8?q?veneer=20beyond=20stdio=20=E2=80=94=20open/creat/unlink/remove/?= =?UTF-8?q?rename/opendir/readdir/closedir=20over=20RMS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The crtl_rms_stdio.c veneer covered only the stdio family (fopen/fwrite/ fread/fclose); the alpha-dec-vms GCC port additionally references decc$ file-ops that were still musl-POSIX (Linux-Alpha ramfs, not RMS/ACP): open/creat (temp-file minting), unlink/remove (cleanup), rename (atomic output finalization), opendir/readdir/closedir (dir enumeration). Blocks vms-fd1 (the port can't compile real C without these reaching RMS). Binds all 8 to the already-proven RMS engine over the executive ACP, fail-honest (INV-6 / Rule 9), no POSIX fallback on the executive-present path: - open/creat: sys$create/$open + an fd table based at 0x40000000 (veneer fds never collide with musl POSIX fds). - unlink/remove: sys$erase (IO$_DELETE). - rename: a new thin sys$rename RMS service (rms_core.c) driving the EXISTING executive primitive IO$_MODIFY!IO$M_MOVE (vms-de7) — one atomic directory-entry re-link that KEEPS the File ID. NOT erase+create (which would be non-atomic and mint a new FID). The faithful decc$rename. - opendir/readdir/closedir: sys$parse + sys$search + rms_search_fid. The executive-absent host/netbsd defer is the SAME rms_acp_absent() flip sys$erase/$open/$create already take (vms-5f0) — no new host-FS surface. DECC$SHR wiring (mk_decc_shr.sh): the 8 decc$ names are substituted to their ovmx_crtl_* veneer aliases IN-PLACE at each name's existing sorted sv# slot — never tail-appended (IMGACT binds by sv# index; the vms-b14 sv#-skew trap). A 12-name presence guard + a >=12 universal floor fail the build on any drift. Proof — teeth green: - x86_64 /dev/vms (real executive, real ODS-2, real ACP): 40/40 assertions, an INDEPENDENT ACP reader for every op (test_syssvc_crtl_rms_veneer.c): creat/open -> genuine File ID + version ;1; unlink/remove -> GONE; rename -> OLD gone, NEW present with the SAME File ID (atomic re-link, not copy); opendir/readdir -> enumerated entries' File IDs match an independent sys$search. - Alpha static: all 8 decc$ names substituted in-place in DECC$SHR; sys$rename is a real LIBVMSRMS$SHR universal (sv#95); the port image STRICT-links binding all 8 by sv# index, 0-deferred / 0-undef. - New alpha gate crtl-rms-fileop-gate boots crtl_rms3_test.c on the real executive + runs the independent DIRECTORY reader. Known: the alpha veneer image inherits the pre-existing vms-c5d (__malloc_donate/__libc_free) post-commit crash (veneer-build-only, not this change — x86_64 is clean); the fileop program commits all effects before its single free(), so the independent-reader proof banks like the vms-f49 veneer gate. vms-c5d tracked separately (blocks a fully-clean alpha exit); root linker fix vms-f59. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01FJZf62TMXxvy6fXzFQYfLQ --- .github/workflows/ci.yml | 58 ++++ docs/design-gcc-port-surface-gaps-register.md | 17 ++ src/libvms/include/starlet.h | 5 + src/vmslink/mk_decc_shr.sh | 41 ++- src/vmsrms/crtl_rms_stdio.c | 261 +++++++++++++++++- src/vmsrms/include/rms/crtl_stdio.h | 103 +++++++ src/vmsrms/include/rms/rms.h | 7 + src/vmsrms/rms_core.c | 168 +++++++++++ tests/qemu/test_syssvc_crtl_rms_veneer.c | 140 +++++++++- .../joint-e2e/build-joint-image.sh | 5 + .../joint-e2e/crtl_rms3_test.c | 151 ++++++++++ .../SYSTARTUP_VMS_FILEOP_PROOF.COM | 53 ++++ tools/cross-alpha/build-alpha-bootimage.sh | 16 +- .../run-module-gp-activation-alpha.sh | 200 +++++++++++++- 14 files changed, 1214 insertions(+), 11 deletions(-) create mode 100644 tools/cross-alpha-vms/joint-e2e/crtl_rms3_test.c create mode 100644 tools/cross-alpha/SYSTARTUP_VMS_FILEOP_PROOF.COM diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1f715eb8..d7048b723 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7610,6 +7610,64 @@ jobs: - name: "CRTL->RMS veneer gate — qemu-system-alpha boots the veneer-wired crtl_rms image on the real /dev/vms executive; its decc$fopen writes PORTTEST.DAT via sys$create over the ODS-2 ACP, then an INDEPENDENT reader (DCL DIRECTORY/FULL, a different accessor than the writer's CRTL/RMS handle) asserts PORTTEST.DAT;1 with a genuine ODS-2 File ID a ramfs cannot produce; runs a can-fail selftest (incl. the ramfs-rejection case) before the boot" run: tools/cross-alpha/run-module-gp-activation-alpha.sh crtl-rms-veneer-gate + # ----------------------------------------------------------------------- + # alpha-crtl-rms-fileop (vms-3320, parent vms-b4f, blocks vms-fd1): the + # UN-FAKEABLE CRTL->RMS FILE-OP veneer gate -- extends alpha-crtl-rms-veneer + # from the stdio family to open/creat/unlink/remove/rename/opendir/readdir/ + # closedir. The port image (crtl_rms3_test.c) creats FOPCRE.DAT, creats+unlinks + # FOPDEL.DAT, creats+renames FOPSRC.DAT->FOPDST.DAT via the VECTOR-SUBSTITUTED + # decc$* file-ops (bound by symbol-vector INDEX to the crtl_rms_stdio.c veneer + # -> sys$create/$erase/$rename over the ACP), then an INDEPENDENT reader (DCL + # DIRECTORY, a different accessor) asserts FOPCRE.DAT + FOPDST.DAT present with + # genuine ODS-2 File IDs and FOPDEL.DAT/FOPSRC.DAT gone -- something a ramfs/ + # POSIX write can never produce in the ACP directory. Runs a 4-case can-fail + # selftest before the boot. Same alpha_activation PR trigger + toolchain images. + # ----------------------------------------------------------------------- + alpha-crtl-rms-fileop: + name: "OVMX/Alpha CRTL->RMS file-op veneer gate — boots the veneer-wired crtl_rms3 image on the real /dev/vms executive; its decc$open/creat/unlink/rename/opendir/readdir/closedir reach the ODS-2 ACP, then an INDEPENDENT DIRECTORY reader asserts the created+renamed files landed with genuine File IDs and the deleted+renamed-away files are gone (vms-3320, blocks vms-fd1)" + needs: changes + if: ${{ github.event_name == 'pull_request' && needs.changes.outputs.alpha_activation == 'true' }} + runs-on: ubuntu-latest + timeout-minutes: 150 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Free up disk space + run: | + sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc \ + /opt/hostedtoolcache/CodeQL /usr/local/share/boost || true + df -h / + + - name: Build the alpha-linux-gnu cross toolchain image (gha layer cache) + uses: docker/build-push-action@v6 + with: + context: tools/cross-alpha + file: tools/cross-alpha/Dockerfile + push: false + load: true + tags: ovmx-cross-alpha:latest + cache-from: type=gha,scope=cross-alpha + cache-to: type=gha,mode=max,scope=cross-alpha + + - name: Build the alpha-dec-vms cross toolchain image (gha layer cache) + uses: docker/build-push-action@v6 + with: + context: tools/cross-alpha-vms + file: tools/cross-alpha-vms/Dockerfile + push: false + load: true + tags: ovmx-cross-alpha-vms:latest + cache-from: type=gha,scope=cross-alpha-vms + cache-to: type=gha,mode=max,scope=cross-alpha-vms + + - name: "CRTL->RMS file-op veneer gate — qemu-system-alpha boots the veneer-wired crtl_rms3 image on the real /dev/vms executive; its decc$open/creat/unlink/rename/opendir/readdir/closedir drive sys$create/$open/$erase/$rename/$parse/$search over the ODS-2 ACP, then an INDEPENDENT reader (DCL DIRECTORY, a different accessor than the writer's CRTL handle) asserts FOPCRE.DAT + FOPDST.DAT landed with genuine ODS-2 File IDs and FOPDEL.DAT/FOPSRC.DAT are gone; runs a 4-case can-fail selftest before the boot" + run: tools/cross-alpha/run-module-gp-activation-alpha.sh crtl-rms-fileop-gate + # ----------------------------------------------------------------------- # alpha-mf-multifile (vms-bdd): the MULTI-.O STRICT-link + activation gate -- # the rung above alpha-crtl-rms-n7 (which is single-object). The milestone diff --git a/docs/design-gcc-port-surface-gaps-register.md b/docs/design-gcc-port-surface-gaps-register.md index 92b139496..feb2e1c17 100644 --- a/docs/design-gcc-port-surface-gaps-register.md +++ b/docs/design-gcc-port-surface-gaps-register.md @@ -257,6 +257,23 @@ PORT image's DECC$SHR (a build sub-project — the alpha-dec-vms port world has `vms_kif`/RMS substrate yet; the `vms-47e` child, §3.1). Until that lands, the alpha port image's own file writes are still musl-POSIX → Linux-Alpha VFS. +> **UPDATE (vms-3320, blocks vms-fd1):** the CRTL→RMS veneer is now extended +> **beyond the stdio family** to the eight file-ops the GCC-port driver leans on: +> `open`/`creat` (temp-file minting → `sys$create`/`sys$open`), `unlink`/`remove` +> (cleanup → `sys$erase`), `rename` (atomic output finalization → the new +> `sys$rename` RMS service → executive ACP `IO$_MODIFY!IO$M_MOVE`, vms-de7: a +> directory-entry re-link that KEEPS the File ID, NOT erase+create), and +> `opendir`/`readdir`/`closedir` (directory enumeration → `sys$parse`+`sys$search` +> + `rms_search_fid`). All eight are added to `src/vmsrms/crtl_rms_stdio.c` and +> **vector-substituted into the alpha DECC$SHR in their sorted sv# slots** +> (`mk_decc_shr.sh` ALPHA_CRTL_RMS_USE block, in-place — never tail-appended; +> sv# skew is the vms-b14/vms-f49 trap). Proven un-fakeably on the real executive +> by an INDEPENDENT ACP reader for each op (`tests/qemu/test_syssvc_crtl_rms_veneer.c`, +> 40/40 — incl. rename keeping the SAME File ID), and wired as the alpha activation +> gate `crtl-rms-fileop-gate` (`crtl_rms3_test.c` + an independent DIRECTORY reader). +> This closes the **PORT-CRTL binding** for the file-op family; the compiler driver's +> temp-file/cleanup/dir-enum now reach real RMS/ODS-2, not musl-POSIX. + > **rd-ID caveat (Rule 10):** this table's "rd item" column reads `vms-1b5`, but > in rd `vms-1b5` is actually the *decc$feature* item; the RMS-beyond-stdio item > is **`vms-2e72`**. Doc↔rd cross-wiring for the conductor to reconcile. diff --git a/src/libvms/include/starlet.h b/src/libvms/include/starlet.h index fba76058b..d2498e429 100644 --- a/src/libvms/include/starlet.h +++ b/src/libvms/include/starlet.h @@ -1020,6 +1020,11 @@ uint32_t sys$create(void *fab, void (*err)(void *), void (*suc)(void *)); /** sys$erase - Erase (delete) file (cb=FAB) */ uint32_t sys$erase(void *fab, void (*err)(void *), void (*suc)(void *)); +/** sys$rename - Atomically rename/move a file (cb=old FAB, new FAB) -- ACP + * MODIFY!IO$M_MOVE, keeps the File ID (vms-3320). Four-argument VMS form. */ +uint32_t sys$rename(void *old_fab, void (*err)(void *), void (*suc)(void *), + void *new_fab); + /** sys$parse - Parse file specification (cb=FAB with NAM/NAML) */ uint32_t sys$parse(void *fab, void (*err)(void *), void (*suc)(void *)); diff --git a/src/vmslink/mk_decc_shr.sh b/src/vmslink/mk_decc_shr.sh index 5802660d9..3ba765589 100755 --- a/src/vmslink/mk_decc_shr.sh +++ b/src/vmslink/mk_decc_shr.sh @@ -161,8 +161,33 @@ if [ "$OVMX_DECC_ARCH" = alpha ]; then # Real VMS symbol vectors are APPEND-ONLY and NEVER renumbered; rewriting # the binding in the name's EXISTING slot keeps every sv# stable across the # two passes (the invariant the veneer block below already documents). + # + # vms-3320 EXTENDS this in-place rewrite to the file-op family beyond stdio: + # decc$open/creat/unlink/remove/rename/opendir/readdir/closedir are ALSO + # musl-alpha's OWN POSIX definitions (raw Linux-Alpha VFS callsys), never + # RMS -- so temp-file minting, cleanup, atomic finalization and directory + # enumeration never reach the executive/ODS-2 volume. Each is rewritten in + # its EXISTING enumerated slot to its veneer-alias form (ovmx_crtl_*), the + # SAME sv#-stable in-place substitution as the stdio four (NEVER tail- + # appended: IMGACT binds by sv# index; vms-b14). The names sort across + # the middle of the vector; rewriting the binding in each name's own slot + # keeps every sv# byte-stable vs the bootstrap pass. + # + # decc$close is the 9th file-op, needed so a decc$open/creat fd is CLOSED + # via RMS -- sys$close finalizes the ODS-2 header/FH2 so the File ID becomes + # visible to an independent reader (a created-but-never-closed file's FID + # only finalizes at clean image exit; on alpha the vms-c5d crash preempts + # that, so a genuine port program must close explicitly, vms-3320). Its + # veneer target is the OVMX-original name ovmx_crtl_fdclose (NOT ovmx_crtl_ + # close -- the fd-close entry point), so it needs its own rewrite rule. + # ⚠ ovmx_crtl_fdclose only closes fds this veneer MINTED (>= OVMX_CRTL_FD_BASE); + # a foreign fd (socket/pipe) returns -1 fail-honest. Sound for a file-only + # compiler port; a socket-using port image would need the fd-close design + # extended (INV-6: no silent POSIX fallback) -- flagged for that future case. if [ -n "${ALPHA_CRTL_RMS_USE:-}" ]; then - sed -E 's,^decc\$(fopen|fwrite|fread|fclose)=PROCEDURE$,decc$\1/ovmx_crtl_\1=PROCEDURE,' \ + sed -E \ + -e 's,^decc\$(fopen|fwrite|fread|fclose|open|creat|unlink|remove|rename|opendir|readdir|closedir)=PROCEDURE$,decc$\1/ovmx_crtl_\1=PROCEDURE,' \ + -e 's,^decc\$close=PROCEDURE$,decc$close/ovmx_crtl_fdclose=PROCEDURE,' \ "$ALPHA_VEC" > "$ALPHA_VEC.f" mv "$ALPHA_VEC.f" "$ALPHA_VEC" fi @@ -317,8 +342,17 @@ if [ "$OVMX_DECC_ARCH" = alpha ]; then | awk '$NF ~ /^ovmx_crtl_/ { t=$(NF-1); if (t=="T"||t=="t"||t=="W"||t=="w") print $NF }' \ | sort -u > "$VENEER_VEC" NVENEER=$(wc -l < "$VENEER_VEC") - [ "$NVENEER" -eq 4 ] || { echo "mk_decc_shr: FAIL expected 4 ovmx_crtl_ universals from crtl_rms_stdio.c, got $NVENEER: $(tr '\n' ' ' < "$VENEER_VEC")" >&2; exit 2; } - for want in ovmx_crtl_fopen ovmx_crtl_fwrite ovmx_crtl_fread ovmx_crtl_fclose; do + # vms-3320: the veneer defines the 4 stdio + 8 file-op entry points plus + # ovmx_crtl_fdclose (the 13th, the target of decc$close so a decc$open/ + # creat fd closes via RMS and its ODS-2 File ID finalizes). Require the + # 13 SUBSTITUTED names by presence (never a hand-list drift) and a floor + # of 13 universals; any further internal ovmx_crtl_ helper is harmless + # dead weight (never exported), as musl's own fopen.o stays whole-archived. + [ "$NVENEER" -ge 13 ] || { echo "mk_decc_shr: FAIL expected >=13 ovmx_crtl_ universals from crtl_rms_stdio.c, got $NVENEER: $(tr '\n' ' ' < "$VENEER_VEC")" >&2; exit 2; } + for want in ovmx_crtl_fopen ovmx_crtl_fwrite ovmx_crtl_fread ovmx_crtl_fclose \ + ovmx_crtl_open ovmx_crtl_creat ovmx_crtl_unlink ovmx_crtl_remove \ + ovmx_crtl_rename ovmx_crtl_opendir ovmx_crtl_readdir ovmx_crtl_closedir \ + ovmx_crtl_fdclose; do grep -qx "$want" "$VENEER_VEC" || { echo "mk_decc_shr: FAIL crtl_rms_stdio.c did not define $want" >&2; exit 2; } done rm -f "$VENEER_VEC" @@ -332,6 +366,7 @@ if [ "$OVMX_DECC_ARCH" = alpha ]; then # decc$strspn at runtime. ALPHA_VENEER_OBJ="$VENEER_OBJ" echo "mk_decc_shr: CRTL->RMS stdio veneer wired: decc\$fopen/fwrite/fread/fclose -> ovmx_crtl_* (--use $ALPHA_CRTL_RMS_USE)" + echo "mk_decc_shr: CRTL->RMS file-op veneer wired (vms-3320): decc\$open/creat/unlink/remove/rename/opendir/readdir/closedir -> ovmx_crtl_*, decc\$close -> ovmx_crtl_fdclose (--use $ALPHA_CRTL_RMS_USE)" fi # Plain (non-decc$-decorated) names the decc$ filter above cannot catch, diff --git a/src/vmsrms/crtl_rms_stdio.c b/src/vmsrms/crtl_rms_stdio.c index 10e24ae8c..56d2a7fab 100644 --- a/src/vmsrms/crtl_rms_stdio.c +++ b/src/vmsrms/crtl_rms_stdio.c @@ -21,8 +21,9 @@ #include #include #include +#include /* vms-3320: O_CREAT/O_WRONLY/O_RDWR/O_TRUNC (target ABI) */ -#include "rms/rms.h" +#include "rms/rms.h" /* sys$create/$open/$erase/$parse/$search/$rename */ #include "rms/crtl_stdio.h" /* The RMS-backed stream. The FAB/RAB live for the life of the handle (the @@ -168,3 +169,261 @@ int ovmx_crtl_fclose(OVMX_CRTL_FILE *fh) free(fh); return (st == RMS$_NORMAL) ? 0 : -1; } + +/* ======================================================================== * + * vms-3320: file-ops beyond the stdio family (open/creat/unlink/remove/ * + * rename/opendir/readdir/closedir), each over the SAME proven RMS engine. * + * ======================================================================== */ + +/* Shared open helper (the fopen body, parameterized). Mints a connected RMS + * stream: writing/create => sys$create (FAB$C_FIX mrs=0 byte-exact put), + * else sys$open (re-asserted FIX mrs=1 for byte-exact get). Returns a handle or + * NULL (fail-honest, no POSIX fallback). Kept separate from ovmx_crtl_fopen so + * that proven stdio entry point stays byte-identical (its gates depend on it). */ +static OVMX_CRTL_FILE *crtl_rms_open_handle(const char *path, int writing, + int create) +{ + OVMX_CRTL_FILE *fh = calloc(1, sizeof *fh); + if (!fh) { + fprintf(stderr, "OVMX-CRTL-RMS: open(\"%s\"): oom\n", path); + return NULL; + } + strncpy(fh->fnbuf, path, sizeof fh->fnbuf - 1); + fh->fnbuf[sizeof fh->fnbuf - 1] = '\0'; + + fh->fab = cc$rms_fab; + fh->fab.fab$l_fna = fh->fnbuf; + fh->fab.fab$b_fns = (uint8_t)strlen(fh->fnbuf); + fh->fab.fab$b_org = FAB$C_SEQ; + fh->fab.fab$b_rfm = FAB$C_FIX; + fh->fab.fab$w_mrs = writing ? 0 : 1; + fh->fab.fab$b_fac = writing ? FAB$M_PUT : FAB$M_GET; + + uint32_t st = create ? sys$create(&fh->fab, 0, 0) + : sys$open(&fh->fab, 0, 0); + fprintf(stderr, "OVMX-CRTL-RMS: sys$%s(\"%s\") -> %u\n", + create ? "create" : "open", path, st); + if (st != RMS$_NORMAL) { + free(fh); + return NULL; /* fail-honest */ + } + if (!writing) { + fh->fab.fab$b_rfm = FAB$C_FIX; + fh->fab.fab$w_mrs = 1; + } + + fh->rab = cc$rms_rab; + fh->rab.rab$l_fab = &fh->fab; + st = sys$connect(&fh->rab, 0, 0); + fprintf(stderr, "OVMX-CRTL-RMS: sys$connect(\"%s\") -> %u\n", path, st); + if (st != RMS$_NORMAL) { + sys$close(&fh->fab, 0, 0); + free(fh); + return NULL; + } + fh->connected = 1; + fh->writing = writing; + return fh; +} + +/* --------------------------------------------------------------- open/creat - + * The fd->RMS-handle table. Values start at OVMX_CRTL_FD_BASE so a veneer fd + * can never collide with a musl POSIX fd; a musl op on a foreign value fails + * EBADF (honest), never a silent wrong success. */ +static OVMX_CRTL_FILE *ovmx_crtl_fd_tbl[OVMX_CRTL_FD_MAX]; + +static int crtl_fd_alloc(OVMX_CRTL_FILE *fh) +{ + for (int i = 0; i < OVMX_CRTL_FD_MAX; i++) { + if (!ovmx_crtl_fd_tbl[i]) { + ovmx_crtl_fd_tbl[i] = fh; + return OVMX_CRTL_FD_BASE + i; + } + } + return -1; +} + +int ovmx_crtl_open(const char *path, int oflag) /* NON-variadic: see crtl_stdio.h */ +{ + if (!path) + return -1; + + int create = (oflag & O_CREAT) != 0; + int writing = create || ((oflag & O_ACCMODE) != O_RDONLY); + + OVMX_CRTL_FILE *fh = crtl_rms_open_handle(path, writing, create); + if (!fh) { + fprintf(stderr, "OVMX-CRTL-RMS: open(\"%s\",0x%x) -> -1 " + "(fail-honest, no POSIX fallback)\n", path, oflag); + return -1; /* fail-honest */ + } + int fd = crtl_fd_alloc(fh); + if (fd < 0) { + fprintf(stderr, "OVMX-CRTL-RMS: open(\"%s\"): fd table full\n", path); + ovmx_crtl_fclose(fh); + return -1; + } + fprintf(stderr, "OVMX-CRTL-RMS: open(\"%s\",0x%x) -> fd %d (%s over the ACP)\n", + path, oflag, fd, create ? "sys$create" : "sys$open"); + return fd; +} + +int ovmx_crtl_creat(const char *path, int mode) +{ + (void)mode; /* ODS-2 protection is the header's */ + /* creat == open(path, O_CREAT|O_WRONLY|O_TRUNC): always mint a new file. */ + return ovmx_crtl_open(path, O_CREAT | O_WRONLY | O_TRUNC); +} + +int ovmx_crtl_fdclose(int fd) +{ + int i = fd - OVMX_CRTL_FD_BASE; + if (i < 0 || i >= OVMX_CRTL_FD_MAX || !ovmx_crtl_fd_tbl[i]) { + fprintf(stderr, "OVMX-CRTL-RMS: fdclose(%d): not a veneer fd " + "(fail-honest)\n", fd); + return -1; + } + OVMX_CRTL_FILE *fh = ovmx_crtl_fd_tbl[i]; + ovmx_crtl_fd_tbl[i] = NULL; + return ovmx_crtl_fclose(fh); +} + +/* ------------------------------------------------------------ unlink/remove - + * sys$erase: IO$_DELETE removes the directory entry + deallocates the header + * and blocks on the real ODS-2 volume. remove() is the ISO C spelling. */ +int ovmx_crtl_unlink(const char *path) +{ + if (!path) + return -1; + struct FAB fab = cc$rms_fab; + fab.fab$l_fna = (char *)path; + fab.fab$b_fns = (uint8_t)strlen(path); + uint32_t st = sys$erase(&fab, 0, 0); + fprintf(stderr, "OVMX-CRTL-RMS: unlink/remove(\"%s\") -> sys$erase %u\n", + path, st); + return (st == RMS$_NORMAL) ? 0 : -1; /* fail-honest */ +} + +int ovmx_crtl_remove(const char *path) +{ + return ovmx_crtl_unlink(path); +} + +/* -------------------------------------------------------------------- rename - + * sys$rename -> executive ACP MODIFY!M_MOVE: atomic directory-entry re-link, + * the file KEEPS its File ID + allocation (NOT erase+create). */ +int ovmx_crtl_rename(const char *oldpath, const char *newpath) +{ + if (!oldpath || !newpath) + return -1; + struct FAB ofab = cc$rms_fab, nfab = cc$rms_fab; + ofab.fab$l_fna = (char *)oldpath; + ofab.fab$b_fns = (uint8_t)strlen(oldpath); + nfab.fab$l_fna = (char *)newpath; + nfab.fab$b_fns = (uint8_t)strlen(newpath); + uint32_t st = sys$rename(&ofab, 0, 0, &nfab); + fprintf(stderr, "OVMX-CRTL-RMS: rename(\"%s\",\"%s\") -> sys$rename %u " + "(ACP MODIFY!M_MOVE, same File ID)\n", oldpath, newpath, st); + return (st == RMS$_NORMAL) ? 0 : -1; /* fail-honest */ +} + +/* --------------------------------------------------- opendir/readdir/closedir + * A DIR*-equivalent over the sys$parse + sys$search wildcard context. */ +struct ovmx_crtl_dir { + struct FAB fab; + struct NAM nam; + char pattern[512]; /* stable backing for fab$l_fna */ + char esa[512]; /* expanded-string area (sys$parse) */ + char rsa[512]; /* resultant-string area (each sys$search) */ + struct ovmx_crtl_dirent de; /* returned entry (overwritten per readdir) */ + int parsed; /* sys$parse succeeded => search context live */ +}; + +OVMX_CRTL_DIR *ovmx_crtl_opendir(const char *name) +{ + if (!name) + return NULL; + + OVMX_CRTL_DIR *d = calloc(1, sizeof *d); + if (!d) { + fprintf(stderr, "OVMX-CRTL-RMS: opendir(\"%s\"): oom\n", name); + return NULL; + } + + /* Compose "*.*;*" -- every file, type and version in the dir. If + * the caller already handed a wildcard (contains '*'), take it verbatim. */ + if (strchr(name, '*')) { + strncpy(d->pattern, name, sizeof d->pattern - 1); + } else { + strncpy(d->pattern, name, sizeof d->pattern - 1); + size_t l = strlen(d->pattern); + strncpy(d->pattern + l, "*.*;*", sizeof d->pattern - 1 - l); + } + d->pattern[sizeof d->pattern - 1] = '\0'; + + d->fab = cc$rms_fab; + d->fab.fab$l_fna = d->pattern; + d->fab.fab$b_fns = (uint8_t)strlen(d->pattern); + d->nam = cc$rms_nam; + d->nam.nam$l_esa = d->esa; + d->nam.nam$b_ess = (uint8_t)(sizeof d->esa > 255 ? 255 : sizeof d->esa); + d->nam.nam$l_rsa = d->rsa; + d->nam.nam$b_rss = (uint8_t)(sizeof d->rsa > 255 ? 255 : sizeof d->rsa); + d->fab.fab$l_nam = &d->nam; + + uint32_t st = sys$parse(&d->fab, 0, 0); + fprintf(stderr, "OVMX-CRTL-RMS: opendir(\"%s\") -> sys$parse(\"%s\") %u\n", + name, d->pattern, st); + if (st != RMS$_NORMAL) { + rms_search_end(&d->nam); + free(d); + return NULL; /* fail-honest */ + } + d->parsed = 1; + return d; +} + +struct ovmx_crtl_dirent *ovmx_crtl_readdir(OVMX_CRTL_DIR *dirp) +{ + if (!dirp || !dirp->parsed) + return NULL; + + uint32_t st = sys$search(&dirp->fab, 0, 0); + if (st != RMS$_NORMAL) { + /* RMS$_NMF at end of the real directory, or a fail-honest error. */ + fprintf(stderr, "OVMX-CRTL-RMS: readdir -> sys$search %u (end/none)\n", st); + return NULL; + } + + /* Resultant spec is "DEV:[DIR]NAME.TYP;VER"; the dirent name is the tail + * after the closing ']' (or ':' if no dir) -- the filename component. */ + size_t rl = dirp->nam.nam$b_rsl; + if (rl >= sizeof dirp->rsa) rl = sizeof dirp->rsa - 1; + dirp->rsa[rl] = '\0'; + const char *nm = dirp->rsa; + const char *rb = strrchr(dirp->rsa, ']'); + if (!rb) rb = strrchr(dirp->rsa, ':'); + if (rb) nm = rb + 1; + + strncpy(dirp->de.d_name, nm, sizeof dirp->de.d_name - 1); + dirp->de.d_name[sizeof dirp->de.d_name - 1] = '\0'; + dirp->de.d_namlen = (unsigned short)strlen(dirp->de.d_name); + + uint16_t num = 0, seq = 0; uint8_t rvn = 0, nmx = 0; + rms_search_fid(&dirp->nam, &num, &seq, &rvn, &nmx); + dirp->de.d_fileid = num; + + fprintf(stderr, "OVMX-CRTL-RMS: readdir -> \"%s\" fid=(%u,%u,%u)\n", + dirp->de.d_name, num, seq, rvn); + return &dirp->de; +} + +int ovmx_crtl_closedir(OVMX_CRTL_DIR *dirp) +{ + if (!dirp) + return -1; + rms_search_end(&dirp->nam); /* release the executive context */ + fprintf(stderr, "OVMX-CRTL-RMS: closedir\n"); + free(dirp); + return 0; +} diff --git a/src/vmsrms/include/rms/crtl_stdio.h b/src/vmsrms/include/rms/crtl_stdio.h index 94fbbc7fc..1e002e31d 100644 --- a/src/vmsrms/include/rms/crtl_stdio.h +++ b/src/vmsrms/include/rms/crtl_stdio.h @@ -69,4 +69,107 @@ size_t ovmx_crtl_fread(void *ptr, size_t size, size_t nmemb, * a close failure or a NULL handle. */ int ovmx_crtl_fclose(OVMX_CRTL_FILE *fh); +/* ======================================================================== * + * vms-3320: the file-op veneer beyond the stdio family. The alpha GCC PORT's + * DECC$SHR binds these decc$ file-ops to musl-POSIX (raw Linux-Alpha VFS + * callsys), NOT RMS -- so temp-file minting (open/creat), cleanup + the + * create->use->delete lifecycle (unlink/remove), atomic output finalization + * (rename), and directory enumeration (opendir/readdir/closedir) never reach + * the executive/ODS-2 volume (docs/design-gcc-port-surface-gaps-register.md + * §3.2). These veneers close that binding to the SAME proven RMS engine the + * stdio family rides -- sys$create/$open/$erase and, for the atomic rename, the + * executive ACP MODIFY!M_MOVE primitive (vms-de7) via the new sys$rename RMS + * service. FAIL-HONEST (INV-6 / Rule 9): every op returns the real RMS/SS$ + * status; there is NO POSIX fallback on the executive-present runtime path. + * + * The port wiring aliases decc$open->ovmx_crtl_open, ... in the alpha DECC$SHR + * symbol vector (src/vmslink/mk_decc_shr.sh ALPHA_CRTL_RMS_USE block), exactly + * as the stdio family, and IN THEIR SORTED SLOT (never tail-appended -- IMGACT + * binds by sv# index; vms-b14). + * ======================================================================== */ + +/* --- open/creat: POSIX-signature int-fd file minting over RMS ------------- + * These return an int fd (the DEC C decc$open/creat ABI), NOT a FILE*. The fd + * indexes a small veneer-private RMS-handle table whose values start at a high + * base (OVMX_CRTL_FD_BASE) so they can NEVER be confused with a musl POSIX fd + * (an accidental musl read()/close() on one returns EBADF, honest, never a + * silent wrong success). The `oflag` bits are interpreted with the compile- + * target's own O_* values, so a caller compiled against the same + * (alpha musl / host) headers passes the flags this veneer expects. + * + * open: O_CREAT set (or any write mode + a missing file via O_CREAT) -> + * sys$create (mints a real ODS-2 version ;N with a genuine File ID); + * else sys$open. O_RDONLY -> read stream; O_WRONLY/O_RDWR -> write. + * creat: == open(path, O_CREAT|O_WRONLY|O_TRUNC): always sys$create. + * Returns the fd (>= OVMX_CRTL_FD_BASE) or -1 on any RMS failure / table full. */ +#define OVMX_CRTL_FD_BASE 0x40000000 +#define OVMX_CRTL_FD_MAX 64 +/* NON-variadic (vms-3320): the DEC C decc$open prototype is variadic (the mode + * arg is optional), but this veneer IGNORES mode, so it takes two fixed args. + * A variadic definition SIGSEGV'd on alpha-dec-vms -- the VMS/Alpha varargs ABI + * (AI register + argument home-area) is a codegen path this cross-toolchain + * mishandles (x86_64/LP64 was clean, alpha/LP64 crashed on the FIRST call, + * before any output -- the classic x86_64-green/alpha-red variadic split). A + * caller that passes a 3rd (mode) arg is harmless: alpha passes it in a register + * the 2-arg callee never reads. */ +int ovmx_crtl_open(const char *path, int oflag); +int ovmx_crtl_creat(const char *path, int mode); + +/* ovmx_crtl_fdclose: close an fd minted by ovmx_crtl_open/creat (sys$close + + * free the table slot). sys$close is what FINALIZES the ODS-2 header/FH2, so + * the created file's File ID becomes visible to an independent reader -- a + * created-but-never-closed file's FID only finalizes at clean image exit, which + * a crash (e.g. alpha vms-c5d) preempts, so a genuine port program MUST close + * explicitly (vms-3320). This IS vector-substituted onto decc$close in the + * alpha DECC$SHR (mk_decc_shr.sh), so a port's close(fd) reaches RMS. + * ⚠ It closes ONLY fds this veneer minted (>= OVMX_CRTL_FD_BASE); a foreign fd + * (socket/pipe) returns -1 fail-honest (INV-6: no silent POSIX fallback). Sound + * for a file-only compiler port; a socket-using port image would need the + * fd-close design extended -- flagged for that future case. + * Returns 0 on success, -1 on a bad/foreign fd or a close failure. */ +int ovmx_crtl_fdclose(int fd); + +/* --- unlink/remove: file deletion over sys$erase -------------------------- + * Both remove the named file from the ODS-2 volume via sys$erase (IO$_DELETE: + * directory-entry removal + header/blocks deallocation). remove() is the ISO C + * spelling of the same file deletion. Returns 0 on success, -1 on any RMS + * failure (fail-honest; an independent DIRECTORY then shows the file GONE). */ +int ovmx_crtl_unlink(const char *path); +int ovmx_crtl_remove(const char *path); + +/* --- rename: ATOMIC directory-entry re-link over sys$rename --------------- + * Drives the new sys$rename RMS service -> the executive ACP MODIFY!M_MOVE + * primitive (vms-de7): the file KEEPS its File ID and allocation; only the + * directory entry is re-linked (old {name,ver} removed, new {name,ver} + * inserted). This is decc$rename's "atomic output finalization" semantics + * (compiler writes NAME.tmp, then renames it over the final name) done + * faithfully -- NOT erase+create (which is non-atomic and mints a NEW FID). An + * independent DIRECTORY then shows the new name carrying the SAME File ID as + * the old had. Returns 0 on success, -1 on any RMS failure. */ +int ovmx_crtl_rename(const char *oldpath, const char *newpath); + +/* --- opendir/readdir/closedir: real ODS-2 directory enumeration ----------- + * A DIR*-equivalent over the sys$parse+sys$search wildcard context (the SAME + * engine DCL DIRECTORY / F$SEARCH ride). opendir composes "*.*;*" and + * sys$parses it; each readdir is one sys$search step returning the next real + * ODS-2 directory entry (its filename + genuine File ID from rms_search_fid); + * closedir releases the executive wildcard context (rms_search_end) + frees the + * handle. Fail-honest: opendir NULL on a bad dir; readdir NULL at RMS$_NMF or + * on error. */ +typedef struct ovmx_crtl_dir OVMX_CRTL_DIR; + +/* Directory entry. d_name is FIRST (offset 0) so the common `ent->d_name` + * access is layout-robust; d_namlen and d_fileid follow. d_fileid is the + * GENUINE ODS-2 File-ID number the executive directory scan returned (an + * enumeration a musl-POSIX readdir on a raw VFS cannot produce). */ +struct ovmx_crtl_dirent { + char d_name[256]; /* NAME.TYP;VER of the match (after the ']') */ + unsigned short d_namlen; /* length of d_name */ + unsigned short d_fileid; /* genuine ODS-2 File-ID number (rms_search_fid) */ +}; + +OVMX_CRTL_DIR *ovmx_crtl_opendir(const char *name); +struct ovmx_crtl_dirent *ovmx_crtl_readdir(OVMX_CRTL_DIR *dirp); +int ovmx_crtl_closedir(OVMX_CRTL_DIR *dirp); + #endif /* __RMS_CRTL_STDIO_H */ diff --git a/src/vmsrms/include/rms/rms.h b/src/vmsrms/include/rms/rms.h index 1623328ce..931d9c4dd 100644 --- a/src/vmsrms/include/rms/rms.h +++ b/src/vmsrms/include/rms/rms.h @@ -33,6 +33,13 @@ uint32_t sys$open(void *fab, void (*err)(void *), void (*suc)(void *)); /* uint32_t sys$close(void *fab, void (*err)(void *), void (*suc)(void *)); /* Close file */ uint32_t sys$create(void *fab, void (*err)(void *), void (*suc)(void *)); /* Create new file */ uint32_t sys$erase(void *fab, void (*err)(void *), void (*suc)(void *)); /* Delete file */ +/* sys$rename (vms-3320) - atomically rename/move a file: the old FAB names the + * source spec, the new FAB the target spec. Drives the executive ACP + * MODIFY!IO$M_MOVE (vms-de7) -- a directory-entry re-link that KEEPS the file's + * File ID + allocation (NOT erase+create). Four-argument VMS form: SYS$RENAME + * old_fab,[err],[suc],new_fab (VSI OpenVMS RMS Reference). Fail-honest RMS$_. */ +uint32_t sys$rename(void *old_fab, void (*err)(void *), void (*suc)(void *), + void *new_fab); uint32_t sys$display(void *fab, void (*err)(void *), void (*suc)(void *)); /* Display file attributes */ uint32_t sys$extend(void *fab, void (*err)(void *), void (*suc)(void *)); /* Extend file allocation */ diff --git a/src/vmsrms/rms_core.c b/src/vmsrms/rms_core.c index d72e78042..3f90b65a4 100644 --- a/src/vmsrms/rms_core.c +++ b/src/vmsrms/rms_core.c @@ -56,6 +56,11 @@ * OVMX-PARTIAL: sys$extend (vms-bc7) -- exec: IO$_MODIFY allocates fab$l_alq * more blocks (BITMAP.SYS + FH2 retrieval-pointer append) without moving EOF. * OVMX-LOCAL: sys$extend -- validates the caller's own FAB before the request. + * OVMX-PARTIAL: sys$rename (vms-de7) -- exec: ONE IO$_MODIFY!IO$M_MOVE atomically + * re-links the directory entry to the new name, keeping the same File ID + * (not erase+create), proof=tests/qemu/test_syssvc_crtl_rms_veneer.c. + * OVMX-LOCAL: sys$rename -- resolves the old/new filespecs and validates both + * FABs in this process; the executive-absent path defers to rename(2) (vms-5f0). * OVMX-PARTIAL: sys$connect (vms-407) -- exec: $DEQs the RAB's * stashed record lkid (_rec_lock_lkid) if a prior lock survived from * before this $CONNECT, a real vms_kif_deq -- guards a reconnect without @@ -194,6 +199,8 @@ static uint32_t rms_impl_create(void *fab_ptr); static uint32_t rms_posix_open(struct FAB *fab); static uint32_t rms_posix_create(struct FAB *fab); static uint32_t rms_posix_erase(struct FAB *fab); +static uint32_t rms_posix_rename(struct FAB *ofab, struct FAB *nfab); /* vms-3320 */ +static uint32_t rms_impl_rename(void *old_ptr, void *new_ptr); /* vms-3320 */ static void rms_posix_close(struct FAB *fab, int deleting, uint32_t *close_sts); static int rms_resolve_version(const char *path, char *out, size_t outlen); /* rms_resolve_spec (filespec + default merge) is defined further down; the ACP @@ -2497,6 +2504,159 @@ static uint32_t rms_posix_erase(struct FAB *fab) return RMS$_NORMAL; } +/* + * sys$rename - Atomically rename/move a file (vms-3320). + * + * The OLD FAB names the source spec, the NEW FAB the target. On the executive + * (ACP) path this drives ONE IO$_MODIFY!IO$M_MOVE (vms-de7): the source + * {directory, name, version} entry is re-linked to the target + * {directory, name, version}, and the file KEEPS its File ID and allocation -- + * NOT erase+create, which would be non-atomic and mint a new FID. This is the + * faithful decc$rename ("atomic output finalization") the GCC port needs. + * + * FAIL-HONEST (INV-6 / Rule 9): the real SS$/RMS$ status; no POSIX fallback on + * the executive-present path. The rms_acp_absent() defer to rms_posix_rename is + * the SAME atomic-flip host/netbsd defer sys$erase/$open/$create take (vms-5f0): + * reached only when /dev/vms is unreachable (host ctest / plain-container gates + * / netbsd-vax cross), never a runtime state under a live executive. + */ +static uint32_t rms_impl_rename(void *old_ptr, void *new_ptr) +{ + struct FAB *ofab = (struct FAB *)old_ptr; + struct FAB *nfab = (struct FAB *)new_ptr; + if (!ofab || ofab->fab$b_bid != FAB$C_BID || + !nfab || nfab->fab$b_bid != FAB$C_BID) { + return RMS$_FAB; + } + +#if defined(OVMX_HAVE_ACP) + { + struct rms_acp_spec ospecs[RMS_ACP_MAX_CANDS]; + struct rms_acp_spec nspecs[RMS_ACP_MAX_CANDS]; + struct vms_acp_fileop_args fop; + uint32_t chan = 0, st = SS$_NOSUCHFILE; + int onc, nnc, done = 0; + + /* ATOMIC-FLIP DEFER (vms-5f0): executive absent => legacy POSIX rename. */ + if (rms_acp_absent()) + return rms_posix_rename(ofab, nfab); + + onc = rms_acp_specs_from_fab(ofab, ospecs, RMS_ACP_MAX_CANDS); + nnc = rms_acp_specs_from_fab(nfab, nspecs, RMS_ACP_MAX_CANDS); + if (onc < 0 || nnc < 0) { + ofab->fab$l_sts = RMS$_SYN; + return RMS$_SYN; + } + + /* Rename has a SINGLE target (not a search-list op): use the first new + * candidate. Try each source candidate in order; the first that + * resolves + moves wins (mirrors sys$erase's candidate walk). */ + for (int i = 0; i < onc && !done; i++) { + struct rms_acp_spec *os = &ospecs[i]; + struct rms_acp_spec *ns = &nspecs[0]; + + chan = 0; + st = vms_kif_acp_assign(os->devnam, &chan); + if (!$VMS_STATUS_SUCCESS(st)) + continue; + + memset(&fop, 0, sizeof(fop)); + fop.chan = chan; + fop.func = VMS_ACP_FOP_MODIFY; + fop.modifiers = VMS_ACP_M_MOVE; /* atomic directory-entry re-link */ + + /* Source directory (FIB$W_DID) + name + version (0 => highest). */ + st = rms_acp_resolve_did(chan, os->dirpath, &fop.did_num, + &fop.did_seq, &fop.did_rvn, &fop.did_nmx); + if (!$VMS_STATUS_SUCCESS(st)) { + vms_kif_dassgn(chan); + continue; /* source dir absent in this member */ + } + fop.version = os->version; /* 0 => highest existing */ + strncpy(fop.name, os->name, VMS_ACP_NAME_SIZE - 1); + + /* Target directory: resolve only when it differs from the source + * (0/0 new_did => the executive uses the SAME directory). */ + if (ns->dirpath[0] && + strcmp(ns->dirpath, os->dirpath) != 0) { + uint16_t nd = 0, nseq = 0; uint8_t nrvn = 0, nnmx = 0; + st = rms_acp_resolve_did(chan, ns->dirpath, &nd, &nseq, + &nrvn, &nnmx); + if (!$VMS_STATUS_SUCCESS(st)) { + vms_kif_dassgn(chan); + continue; /* target dir absent */ + } + fop.new_did_num = nd; + fop.new_did_nmx = nnmx; + } + strncpy(fop.new_name, ns->name, VMS_ACP_NAME_SIZE - 1); + fop.new_version = ns->version; /* 0 => highest existing + 1 */ + + st = vms_kif_acp_fileop(&fop); + vms_kif_dassgn(chan); + if ($VMS_STATUS_SUCCESS(st)) + done = 1; + } + + if (!done) { + ofab->fab$l_stv = st; + ofab->fab$l_sts = (st == SS$_NOSUCHFILE) ? RMS$_FNF + : (st == SS$_NOPRIV) ? RMS$_PRV + : RMS$_ACC; + return ofab->fab$l_sts; + } + ofab->fab$l_sts = RMS$_NORMAL; + ofab->fab$l_stv = 0; + nfab->fab$l_sts = RMS$_NORMAL; + nfab->fab$l_stv = 0; + return RMS$_NORMAL; + } +#else + return rms_posix_rename(ofab, nfab); +#endif +} + +/* + * rms_posix_rename - the executive-absent legacy rename body (rename(2) on the + * resolved host paths). netbsd-vax record backend / __linux__ defer target + * (vms-5f0). NOT the runtime path under a live /dev/vms (INV-6). + */ +static uint32_t rms_posix_rename(struct FAB *ofab, struct FAB *nfab) +{ + char oldpath[1024]; /* == FAB._resolved_path size */ + if (resolve_for_open(ofab) < 0) { + ofab->fab$l_sts = RMS$_SYN; + return RMS$_SYN; + } + strncpy(oldpath, ofab->_resolved_path, sizeof(oldpath) - 1); + oldpath[sizeof(oldpath) - 1] = '\0'; + + if (resolve_for_open(nfab) < 0) { + ofab->fab$l_sts = RMS$_SYN; + return RMS$_SYN; + } + + if (rename(oldpath, nfab->_resolved_path) < 0) { + ofab->fab$l_stv = (uint32_t)errno; + switch (errno) { + case ENOENT: + ofab->fab$l_sts = RMS$_FNF; + return RMS$_FNF; + case EACCES: + case EPERM: + ofab->fab$l_sts = RMS$_PRV; + return RMS$_PRV; + default: + ofab->fab$l_sts = RMS$_ACC; + return RMS$_ACC; + } + } + ofab->fab$l_sts = RMS$_NORMAL; + ofab->fab$l_stv = 0; + nfab->fab$l_sts = RMS$_NORMAL; + return RMS$_NORMAL; +} + /* * sys$connect - Connect a RAB to its FAB, establishing a record stream. * @@ -2832,6 +2992,14 @@ uint32_t sys$erase(void *fab, void (*err)(void *), void (*suc)(void *)) return rms_complete(rms_impl_erase(fab), fab, err, suc); } +/* Four-argument VMS form: SYS$RENAME old_fab,[err],[suc],new_fab. The optional + * completion routine is dispatched on the OLD (primary) FAB, as on OpenVMS. */ +uint32_t sys$rename(void *old_fab, void (*err)(void *), void (*suc)(void *), + void *new_fab) +{ + return rms_complete(rms_impl_rename(old_fab, new_fab), old_fab, err, suc); +} + uint32_t sys$connect(void *rab, void (*err)(void *), void (*suc)(void *)) { return rms_complete(rms_impl_connect(rab), rab, err, suc); diff --git a/tests/qemu/test_syssvc_crtl_rms_veneer.c b/tests/qemu/test_syssvc_crtl_rms_veneer.c index e05988ce7..18cd42abb 100644 --- a/tests/qemu/test_syssvc_crtl_rms_veneer.c +++ b/tests/qemu/test_syssvc_crtl_rms_veneer.c @@ -39,6 +39,7 @@ #include #include #include +#include /* vms-3320: O_* for ovmx_crtl_open */ #include "vms_kif.h" #include "rms/rms.h" @@ -217,12 +218,145 @@ int main(void) } /* ================================================================= * - * 5. Isolation — erase the file so the fixture is restored. * + * 5. FILE-OP VENEER (vms-3320): decc$creat/open/unlink/remove/rename/ * + * opendir/readdir/closedir over RMS, each proven by the SAME * + * INDEPENDENT ACP reader a ramfs cannot fake. * + * ================================================================= */ +#define CREATNAME "CVENEER.DAT" +#define CREATSPEC DIRSPEC CREATNAME +#define RENSRC DIRSPEC "RENSRC.DAT" +#define RENDST DIRSPEC "RENDST.DAT" +#define DIRA DIRSPEC "ENUMA.DAT" +#define DIRB DIRSPEC "ENUMB.DAT" + + /* --- 5.1 creat mints a real ODS-2 file the independent reader sees. --- */ + erase_spec(CREATSPEC ";*"); + int cfd = ovmx_crtl_creat(CREATSPEC, 0); + check(cfd >= OVMX_CRTL_FD_BASE, + "5.1a: ovmx_crtl_creat -> sys$create over the ACP, returns a veneer fd"); + if (cfd >= 0) ovmx_crtl_fdclose(cfd); + { + uint16_t cfid = 0; char ctail[128]; uint32_t cend = 0; + int cn = search_one(CREATSPEC ";*", &cfid, ctail, sizeof(ctail), &cend); + check(cn == 1, + "5.1b: independent sys$search finds the creat-minted file " + "(ramfs cannot appear on the ACP directory)"); + check(cfid != 0, + "5.1c: the creat file carries a genuine nonzero ODS-2 File ID"); + check(strstr(ctail, ";1") != NULL, + "5.1d: creat minted version ;1 (a genuine ODS-2 create)"); + printf(" [independent ACP reader] creat resultant='%s' fid=(%u,...)\n", + ctail, cfid); + } + + /* --- 5.2 unlink removes it; the independent reader sees it GONE. --- */ + check(ovmx_crtl_unlink(CREATSPEC ";*") == 0, + "5.2a: ovmx_crtl_unlink -> sys$erase NORMAL"); + check(search_one(CREATSPEC ";*", NULL, NULL, 0, &endst) == 0, + "5.2b: independent sys$search finds the unlinked file GONE " + "(a real ODS-2 directory-entry removal)"); + + /* --- 5.3 open(O_CREAT) mints; remove() (ISO C) deletes; reader agrees. --- */ + { + erase_spec(CREATSPEC ";*"); + int ofd = ovmx_crtl_open(CREATSPEC, O_CREAT | O_WRONLY | O_TRUNC); + check(ofd >= OVMX_CRTL_FD_BASE, + "5.3a: ovmx_crtl_open(O_CREAT) -> sys$create, returns a veneer fd"); + if (ofd >= 0) ovmx_crtl_fdclose(ofd); + uint16_t ofid = 0; + check(search_one(CREATSPEC ";*", &ofid, NULL, 0, &endst) == 1 && ofid != 0, + "5.3b: independent reader sees the open(O_CREAT) file with a File ID"); + check(ovmx_crtl_remove(CREATSPEC ";*") == 0, + "5.3c: ovmx_crtl_remove -> sys$erase NORMAL"); + check(search_one(CREATSPEC ";*", NULL, NULL, 0, &endst) == 0, + "5.3d: independent reader sees the removed file GONE"); + } + + /* --- 5.4 rename: the ATOMIC re-link keeps the SAME File ID (teeth). --- */ + { + erase_spec(RENSRC ";*"); + erase_spec(RENDST ";*"); + /* Create the source through the proven stdio veneer, capture its FID. */ + OVMX_CRTL_FILE *sf = ovmx_crtl_fopen(RENSRC, "w"); + check(sf != NULL, "5.4a: create RENSRC.DAT (fopen->sys$create)"); + if (sf) { ovmx_crtl_fwrite("RENAMEME", 1, 8, sf); ovmx_crtl_fclose(sf); } + uint16_t src_fid = 0; + check(search_one(RENSRC ";*", &src_fid, NULL, 0, &endst) == 1 && src_fid != 0, + "5.4b: independent reader sees RENSRC.DAT with File ID X"); + + check(ovmx_crtl_rename(RENSRC, RENDST) == 0, + "5.4c: ovmx_crtl_rename -> sys$rename (ACP MODIFY!M_MOVE) NORMAL"); + + check(search_one(RENSRC ";*", NULL, NULL, 0, &endst) == 0, + "5.4d: independent reader sees the OLD name RENSRC.DAT GONE"); + uint16_t dst_fid = 0; char dtail[128]; + int dn = search_one(RENDST ";*", &dst_fid, dtail, sizeof(dtail), &endst); + check(dn == 1, + "5.4e: independent reader sees the NEW name RENDST.DAT present"); + check(dst_fid != 0 && dst_fid == src_fid, + "5.4f: RENDST.DAT carries the SAME File ID as RENSRC had -- proves " + "an ATOMIC directory-entry re-link, NOT erase+create (a new FID)"); + printf(" [independent ACP reader] rename: RENSRC fid=(%u,...) -> " + "RENDST '%s' fid=(%u,...) SAME=%s\n", + src_fid, dtail, dst_fid, (src_fid == dst_fid) ? "YES" : "NO"); + /* On-disk header confirms the moved file keeps its FID + allocation. */ + struct rms_fileattr rattr; memset(&rattr, 0, sizeof rattr); + uint32_t rst = rms_file_attr(RENDST, &rattr); + check($VMS_STATUS_SUCCESS(rst) && rattr.fid_num == src_fid, + "5.4g: RENDST.DAT on-disk header File ID == the source's (two " + "independent readers agree the file kept its FID)"); + erase_spec(RENDST ";*"); + } + + /* --- 5.5 opendir/readdir enumerate the REAL ODS-2 directory entries. --- */ + { + erase_spec(DIRA ";*"); + erase_spec(DIRB ";*"); + OVMX_CRTL_FILE *fa = ovmx_crtl_fopen(DIRA, "w"); + if (fa) ovmx_crtl_fclose(fa); + OVMX_CRTL_FILE *fb = ovmx_crtl_fopen(DIRB, "w"); + if (fb) ovmx_crtl_fclose(fb); + check(fa != NULL && fb != NULL, "5.5a: create ENUMA.DAT + ENUMB.DAT"); + + OVMX_CRTL_DIR *dp = ovmx_crtl_opendir(DIRSPEC); + check(dp != NULL, "5.5b: ovmx_crtl_opendir(dir) -> sys$parse over the ACP"); + int saw_a = 0, saw_b = 0, fid_a = 0, fid_b = 0, total = 0; + if (dp) { + struct ovmx_crtl_dirent *e; + while ((e = ovmx_crtl_readdir(dp)) != NULL) { + total++; + if (strstr(e->d_name, "ENUMA.DAT")) { saw_a = 1; fid_a = e->d_fileid; } + if (strstr(e->d_name, "ENUMB.DAT")) { saw_b = 1; fid_b = e->d_fileid; } + } + check(ovmx_crtl_closedir(dp) == 0, + "5.5c: ovmx_crtl_closedir -> rms_search_end (context released)"); + } + check(saw_a && saw_b, + "5.5d: readdir enumerated BOTH real ODS-2 entries by name"); + check(fid_a != 0 && fid_b != 0 && fid_a != fid_b, + "5.5e: each enumerated entry carries its genuine (distinct) File ID"); + /* Cross-check against the independent single-file searches. */ + uint16_t ia = 0, ib = 0; + search_one(DIRA ";*", &ia, NULL, 0, &endst); + search_one(DIRB ";*", &ib, NULL, 0, &endst); + check((uint16_t)fid_a == ia && (uint16_t)fid_b == ib, + "5.5f: readdir's File IDs match the independent sys$search File IDs " + "(same genuine ODS-2 directory, two readers agree)"); + printf(" [independent ACP reader] readdir enumerated %d entries; " + "ENUMA fid=%d (search %u), ENUMB fid=%d (search %u)\n", + total, fid_a, ia, fid_b, ib); + erase_spec(DIRA ";*"); + erase_spec(DIRB ";*"); + } + + /* ================================================================= * + * 6. Isolation — erase the stdio-veneer file so the fixture is * + * restored. * * ================================================================= */ st = erase_spec(VSPEC ";*"); - check($VMS_STATUS_SUCCESS(st), "5a: sys$erase VENEER.DAT (isolation)"); + check($VMS_STATUS_SUCCESS(st), "6a: sys$erase VENEER.DAT (isolation)"); check(search_one(VSPEC ";*", NULL, NULL, 0, &endst) == 0, - "5b: a final search finds NONE (fixture restored)"); + "6b: a final search finds NONE (fixture restored)"); free(buf); diff --git a/tools/cross-alpha-vms/joint-e2e/build-joint-image.sh b/tools/cross-alpha-vms/joint-e2e/build-joint-image.sh index b3db537c0..4e403dde7 100755 --- a/tools/cross-alpha-vms/joint-e2e/build-joint-image.sh +++ b/tools/cross-alpha-vms/joint-e2e/build-joint-image.sh @@ -333,6 +333,11 @@ cp "$WORK/LINK.EXE" "$WORK/DECC\$SHR.EXE" "$WORK/libots/LIBOTS_SHR.EXE" "$OUT/" # SYS$SHARE search-path set -- with no extra copy needed when the veneer path # built it; a plain (non-veneer) run leaves $RMS empty and stages nothing new. echo "== joint-e2e image built (genuine alpha path, vms-864) ==" +# vms-3320: the FILE-OP veneer gate (JOINT_MAIN=crtl_rms3_test.c) drops a marker +# so build-alpha-bootimage.sh stages the FILE-OP independent-reader SYSTARTUP +# (DIRECTORY of the FOP*.DAT set) instead of the stdio VENEER one (which reads +# PORTTEST.DAT). Any other JOINT_MAIN leaves it absent -> unchanged behaviour. +[ "$JOINT_MAIN" = crtl_rms3_test.c ] && { : > "$OUT/FILEOP_PROOF"; echo "== FILEOP_PROOF marker staged (vms-3320 file-op veneer gate) =="; } ls -la "$OUT/" readelf -h "$OUT/joint_e2e.exe" | grep -E "Type|Machine|Entry" readelf -SW "$OUT/joint_e2e.exe" | grep -E "vms\\\$xfer|vms\\\$imp|CODE|DATA" || true diff --git a/tools/cross-alpha-vms/joint-e2e/crtl_rms3_test.c b/tools/cross-alpha-vms/joint-e2e/crtl_rms3_test.c new file mode 100644 index 000000000..98fb6553e --- /dev/null +++ b/tools/cross-alpha-vms/joint-e2e/crtl_rms3_test.c @@ -0,0 +1,151 @@ +/* crtl_rms3_test.c — the alpha-dec-vms CRTL/RMS FILE-OP port program (vms-3320), + * wired as a REPRODUCIBLE joint-e2e VARIANT (build-joint-image.sh JOINT_MAIN). + * + * It advances the vms-b4f / vms-da0 ladder past crtl_rms_test.c (stdio family) + * and crtl_rms2_test.c (line-I/O + fmt + sort): this program drives the FILE-OP + * decc$ surface a real GCC port leans on but which was still musl-POSIX before + * vms-3320 — temp-file minting, cleanup, atomic finalization, dir enumeration: + * + * - open / creat : mint a real ODS-2 file (sys$create) and reopen it (sys$open) + * - unlink : delete a file (sys$erase) — temp cleanup + * - rename : ATOMIC re-link (sys$rename -> ACP MODIFY!M_MOVE) — the + * compiler's "write NAME.tmp, rename over the final" finalize + * - opendir/readdir/closedir : enumerate the REAL ODS-2 directory entries + * + * Every reference is a GENUINE decc$ call: the alpha-dec-vms cross cc1 decorates + * each name to the decc$ surface at codegen, and mk_decc_shr.sh's ALPHA/EVAX + * branch (ALPHA_CRTL_RMS_USE) VECTOR-SUBSTITUTES decc$open/creat/unlink/remove/ + * rename/opendir/readdir/closedir -> src/vmsrms/crtl_rms_stdio.c's ovmx_crtl_* + * (which drive sys$create/$open/$erase/$rename/$parse/$search over the real + * Files-11 ODS-2 executive ACP). Under JOINT_CRTL_RMS_VENEER=1 this is the path + * that runs — NOT musl-POSIX. Fail-honest: any RMS failure returns a sentinel. + * + * This program is its OWN first (same-CRTL) check — it returns sentinel 7 only + * if every op succeeded AND its own decc$opendir/readdir enumeration agrees the + * created + renamed files are present and the deleted + old-name files are gone. + * The UN-FAKEABLE proof is the INDEPENDENT reader the boot runs afterward + * (SYS$MANAGER:SYSTARTUP_VMS -> DCL DIRECTORY over the ACP, a DIFFERENT accessor): + * it must see FOPCRE.DAT + FOPDST.DAT with genuine ODS-2 File IDs and NOT see + * FOPDEL.DAT / FOPSRC.DAT. The program deliberately LEAVES FOPCRE.DAT + FOPDST.DAT + * on the volume for that reader; a ramfs/POSIX write can never appear in the ACP + * directory (see tools/cross-alpha/SYSTARTUP_VMS_FILEOP_PROOF.COM). + * + * SENTINEL-RETURN CONVENTION (deterministic). crt0 maps the return N through + * C$_EXIT1, so $STATUS decodes to C$_EXIT1 + (N-1)*8: + * 7 = FULL SUCCESS (all 8 ops + self-enumeration agreement) + * 1 = creat(FOPCRE.DAT) failed + * 2 = open(FOPCRE.DAT, O_RDONLY) failed + * 3 = creat(FOPDEL.DAT) failed + * 4 = unlink(FOPDEL.DAT) failed + * 5 = creat(FOPSRC.DAT) failed + * 6 = rename(FOPSRC.DAT -> FOPDST.DAT) failed + * 8 = opendir failed, OR self-enumeration disagreed (created/renamed missing, + * or deleted/old-name still present) + */ + +/* alpha-dec-vms is LP64 (-mpointer-size=64). No libc headers in the cross image; + * declare the CRTL surface directly — the NAMES matter for the link and the + * cross cc1 decorates them to decc$ at codegen (matching crtl_rms_test.c). */ +typedef unsigned long ovmx_size_t; + +/* Alpha (OSF/1) open() flag ABI — arch/alpha uapi/asm/fcntl.h, matching the + * OVMX alpha musl bits/fcntl.h the veneer is compiled against. */ +#define O_RDONLY 0x0000 +#define O_WRONLY 0x0001 +#define O_CREAT 0x0200 /* 01000 */ +#define O_TRUNC 0x0400 /* 02000 */ + +extern int open(const char *, int); /* non-variadic: match the veneer ABI (vms-3320) */ +extern int creat(const char *, int); +extern int close(int); +extern int unlink(const char *); +extern int rename(const char *, const char *); +extern void *opendir(const char *); +extern int closedir(void *); +extern int printf(const char *, ...); +extern int fprintf(void *, const char *, ...); +extern char *strstr(const char *, const char *); +extern void *stderr; + +/* decc$readdir returns a struct dirent*; ovmx_crtl_readdir fills a struct whose + * FIRST member is d_name[256] (offset 0), so this layout reads the name back. */ +struct portdirent { + char d_name[256]; + unsigned short d_namlen; + unsigned short d_fileid; +}; +extern struct portdirent *readdir(void *); + +#define DIRSPEC "VDA0:[SYSTMP]" +#define FOPCRE DIRSPEC "FOPCRE.DAT" /* created, LEFT for the reader */ +#define FOPDEL DIRSPEC "FOPDEL.DAT" /* created then unlinked (gone) */ +#define FOPSRC DIRSPEC "FOPSRC.DAT" /* created then renamed away (gone) */ +#define FOPDST DIRSPEC "FOPDST.DAT" /* rename target, LEFT for the reader */ + +int main(int argc, char **argv, char **envp) +{ + (void)argv; (void)envp; + + /* 1. creat mints a real ODS-2 file (sys$create); CLOSE it so sys$close + * finalizes the ODS-2 header/FH2 and its File ID becomes visible to the + * independent reader. Without an explicit close the FID only finalizes at + * clean image exit -- which the post-op vms-c5d crash preempts on alpha + * (that is the x86_64-green / alpha-red split this closes, vms-3320). */ + int cfd = creat(FOPCRE, 0); + if (cfd < 0) + return 1; + close(cfd); /* finalize FOPCRE's File ID */ + + /* 2. open the just-created file read-only (sys$open), then close. */ + int ofd = open(FOPCRE, O_RDONLY); + if (ofd < 0) + return 2; + close(ofd); + + /* 3. create a doomed temp, close it, then unlink it (sys$erase). */ + int dfd = creat(FOPDEL, 0); + if (dfd < 0) + return 3; + close(dfd); + if (unlink(FOPDEL) != 0) + return 4; + + /* 4. create a source, CLOSE it (finalize its FID), then ATOMICALLY rename + * it (sys$rename -> ACP MODIFY!M_MOVE) -- the re-link carries that SAME + * File ID into FOPDST, which the independent reader then confirms. */ + int sfd = creat(FOPSRC, 0); + if (sfd < 0) + return 5; + close(sfd); /* finalize FOPSRC's File ID BEFORE the rename */ + if (rename(FOPSRC, FOPDST) != 0) + return 6; + + /* 5. SELF-CHECK: enumerate the directory via decc$opendir/readdir and + * confirm the created + renamed files are present, the deleted + old + * names gone. (The un-fakeable proof is the independent DIRECTORY reader + * the boot runs next — this is the same-CRTL corroboration.) */ + void *dir = opendir(DIRSPEC); + if (!dir) + return 8; + int saw_cre = 0, saw_dst = 0, saw_del = 0, saw_src = 0, nent = 0; + struct portdirent *e; + while ((e = readdir(dir)) != 0) { + nent++; + if (strstr(e->d_name, "FOPCRE.DAT")) saw_cre = 1; + if (strstr(e->d_name, "FOPDST.DAT")) saw_dst = 1; + if (strstr(e->d_name, "FOPDEL.DAT")) saw_del = 1; + if (strstr(e->d_name, "FOPSRC.DAT")) saw_src = 1; + } + closedir(dir); + + fprintf(stderr, "OVMX CRTL/RMS3 file-op test: enumerated %d entries; " + "cre=%d dst=%d del=%d src=%d\n", nent, saw_cre, saw_dst, saw_del, saw_src); + + if (!saw_cre || !saw_dst || saw_del || saw_src) + return 8; + + printf("OVMX CRTL/RMS3 file-op test: OK " + "(open+creat+unlink+rename+opendir+readdir+closedir over RMS) argc=%d\n", argc); + + return 7; /* distinctive success -> $STATUS = C$_EXIT1 + (7-1)*8 */ +} diff --git a/tools/cross-alpha/SYSTARTUP_VMS_FILEOP_PROOF.COM b/tools/cross-alpha/SYSTARTUP_VMS_FILEOP_PROOF.COM new file mode 100644 index 000000000..01594b2c9 --- /dev/null +++ b/tools/cross-alpha/SYSTARTUP_VMS_FILEOP_PROOF.COM @@ -0,0 +1,53 @@ +$! SYSTARTUP_VMS.COM - CRTL->RMS FILE-OP veneer un-fakeable proof variant +$! (vms-3320, extends the vms-f49 rung-4 veneer proof to the file-op family; +$! harness-only). +$! +$! Harness variant of the Alpha static-bootstrap SYS$MANAGER:SYSTARTUP_VMS.COM, +$! staged onto the ODS-2 volume by tools/cross-alpha/build-alpha-bootimage.sh +$! ONLY when the FILEOP proof marker is present (a crtl_rms3_test.c build); the +$! product rootfs-alpha file is never modified. +$! +$! WHAT THIS PROVES (the anti-fabrication payoff, INV-6). SYS$SYSTEM:JOINT_E2E is +$! the alpha-dec-vms GCC-port image built with the CRTL->RMS veneer wired in +$! (JOINT_CRTL_RMS_VENEER=1): its decc$open/creat/unlink/rename/opendir/readdir/ +$! closedir bind, BY SYMBOL-VECTOR INDEX, to the crtl_rms_stdio.c veneer +$! (ovmx_crtl_* -> sys$create/$open/$erase/$rename/$parse/$search -> +$! LIBVMSRMS$SHR -> ioctl(/dev/vms) -> the Files-11 ACP), NOT to musl-POSIX. It +$! creats FOPCRE.DAT, creats+unlinks FOPDEL.DAT, and creats+renames +$! FOPSRC.DAT -> FOPDST.DAT, all on the live OVMX/Alpha executive, LEAVING +$! FOPCRE.DAT + FOPDST.DAT behind for this reader. +$! +$! Then -- the un-fakeable part -- an INDEPENDENT reader inspects the volume +$! through a DIFFERENT accessor than the writer's CRTL handle: DCL DIRECTORY, +$! which runs its OWN sys$parse+sys$search over the Files-11 ACP directory +$! (dcl_cmd_file.c cmd_directory -> from_acp) and prints the GENUINE ODS-2 File +$! ID the directory search returned. A POSIX/ramfs write cannot appear in the +$! ACP directory at all (it draws %DIRECT-W-NOFILES, NO File ID). So: +$! - FOPCRE.DAT + FOPDST.DAT present WITH File IDs => creat + rename landed; +$! - FOPDEL.DAT + FOPSRC.DAT %DIRECT-W-NOFILES => unlink + rename-away worked. +$! The renamed FOPDST.DAT keeping a genuine File ID proves the ACP MODIFY!M_MOVE +$! re-link (the same-File-ID atomicity is proven exhaustively on the host-arch +$! layer, tests/qemu/test_syssvc_crtl_rms_veneer.c). +$! +$ SET NOON +$! +$ WRITE SYS$OUTPUT "FILEOP-PROOF: === MILESTONE: RUN JOINT_E2E (veneer file-ops over sys$create/$erase/$rename) ===" +$ RUN SYS$SYSTEM:JOINT_E2E +$ WRITE SYS$OUTPUT "FILEOP-PROOF: RUN-STATUS=''$STATUS' SEVERITY=''$SEVERITY'" +$! +$ WRITE SYS$OUTPUT "FILEOP-PROOF: === INDEPENDENT READER: DIRECTORY over the ACP (a DIFFERENT accessor) ===" +$ WRITE SYS$OUTPUT "FILEOP-PROOF: --- created file FOPCRE.DAT (must be present) ---" +$ DIRECTORY/FULL VDA0:[SYSTMP]FOPCRE.DAT +$ WRITE SYS$OUTPUT "FILEOP-PROOF: CRE-STATUS=''$STATUS' SEVERITY=''$SEVERITY'" +$ WRITE SYS$OUTPUT "FILEOP-PROOF: --- renamed target FOPDST.DAT (must be present, genuine File ID) ---" +$ DIRECTORY/FULL VDA0:[SYSTMP]FOPDST.DAT +$ WRITE SYS$OUTPUT "FILEOP-PROOF: DST-STATUS=''$STATUS' SEVERITY=''$SEVERITY'" +$ WRITE SYS$OUTPUT "FILEOP-PROOF: --- unlinked file FOPDEL.DAT (must be GONE) ---" +$ DIRECTORY VDA0:[SYSTMP]FOPDEL.DAT +$ WRITE SYS$OUTPUT "FILEOP-PROOF: DEL-STATUS=''$STATUS' SEVERITY=''$SEVERITY'" +$ WRITE SYS$OUTPUT "FILEOP-PROOF: --- rename source FOPSRC.DAT (must be GONE) ---" +$ DIRECTORY VDA0:[SYSTMP]FOPSRC.DAT +$ WRITE SYS$OUTPUT "FILEOP-PROOF: SRC-STATUS=''$STATUS' SEVERITY=''$SEVERITY'" +$ WRITE SYS$OUTPUT "FILEOP-PROOF: === END INDEPENDENT READER ===" +$! +$ EXIT diff --git a/tools/cross-alpha/build-alpha-bootimage.sh b/tools/cross-alpha/build-alpha-bootimage.sh index b31f12f44..98ae9f747 100755 --- a/tools/cross-alpha/build-alpha-bootimage.sh +++ b/tools/cross-alpha/build-alpha-bootimage.sh @@ -251,9 +251,19 @@ docker run --rm --memory=8g --cpus="$(nproc)" \ && cp "$JOINT/${_vp}\$SHR.EXE" "$ST/vms/SYS0/SYSCOMMON/SYSLIB/${_vp}\$SHR.EXE" \ || { echo "FAIL: veneer producer $JOINT/${_vp}\$SHR.EXE missing"; exit 1; } done - cp /repo/tools/cross-alpha/SYSTARTUP_VMS_VENEER_PROOF.COM \ - "$ST/vms/SYS0/SYSCOMMON/SYSMGR/SYSTARTUP_VMS.COM" - echo " JOINT-E2E (VENEER): joint_e2e.exe -> SYS\$SYSEXE; DECC\$SHR + LIBOTS_SHR + full RMS producer graph (LIBVMSRMS/LIBVMS/LIBVMSFS/LIBVMSLNM/LIBVMSPROCESS/LIBVMSSYS\$SHR) -> SYS\$SHARE; VENEER-proof SYSTARTUP (independent DIRECTORY/FULL reader) staged" + # vms-3320: the FILE-OP veneer gate marks its joint dir (FILEOP_PROOF) + # so it stages its OWN independent-reader SYSTARTUP (DIRECTORY of the + # FOP*.DAT set) instead of the stdio VENEER one (PORTTEST.DAT). The + # producer graph staged just above is identical for both. + if [ -f "$JOINT/FILEOP_PROOF" ]; then + cp /repo/tools/cross-alpha/SYSTARTUP_VMS_FILEOP_PROOF.COM \ + "$ST/vms/SYS0/SYSCOMMON/SYSMGR/SYSTARTUP_VMS.COM" + echo " JOINT-E2E (FILE-OP VENEER): joint_e2e.exe -> SYS\$SYSEXE; full RMS producer graph -> SYS\$SHARE; FILEOP-proof SYSTARTUP (independent DIRECTORY reader over creat/unlink/rename set) staged" + else + cp /repo/tools/cross-alpha/SYSTARTUP_VMS_VENEER_PROOF.COM \ + "$ST/vms/SYS0/SYSCOMMON/SYSMGR/SYSTARTUP_VMS.COM" + echo " JOINT-E2E (VENEER): joint_e2e.exe -> SYS\$SYSEXE; DECC\$SHR + LIBOTS_SHR + full RMS producer graph (LIBVMSRMS/LIBVMS/LIBVMSFS/LIBVMSLNM/LIBVMSPROCESS/LIBVMSSYS\$SHR) -> SYS\$SHARE; VENEER-proof SYSTARTUP (independent DIRECTORY/FULL reader) staged" + fi else cp /repo/tools/cross-alpha/SYSTARTUP_VMS_JOINT_PROOF.COM \ "$ST/vms/SYS0/SYSCOMMON/SYSMGR/SYSTARTUP_VMS.COM" diff --git a/tools/cross-alpha/run-module-gp-activation-alpha.sh b/tools/cross-alpha/run-module-gp-activation-alpha.sh index 26b2aee1c..df674ac9c 100755 --- a/tools/cross-alpha/run-module-gp-activation-alpha.sh +++ b/tools/cross-alpha/run-module-gp-activation-alpha.sh @@ -65,6 +65,7 @@ # tools/cross-alpha/run-module-gp-activation-alpha.sh gate # same, explicit # tools/cross-alpha/run-module-gp-activation-alpha.sh crtl-rms-gate # crtl_rms heap+RMS+stdio -> N=7 (non-veneer control) # tools/cross-alpha/run-module-gp-activation-alpha.sh crtl-rms-veneer-gate # vms-f49 rung 4: veneer write + INDEPENDENT ODS-2 File-ID reader +# tools/cross-alpha/run-module-gp-activation-alpha.sh crtl-rms-fileop-gate # vms-3320: open/creat/unlink/rename/opendir/readdir/closedir veneer + INDEPENDENT DIRECTORY reader # tools/cross-alpha/run-module-gp-activation-alpha.sh mf-gate # multi-.o cross-boundary -> N=5 (vms-bdd) # tools/cross-alpha/run-module-gp-activation-alpha.sh shipped-gate # SHIPPED packaging path -> N=3 (vms-410) # tools/cross-alpha/run-module-gp-activation-alpha.sh selftest # can-fail proof, no boot @@ -364,6 +365,70 @@ assert_veneer() { return 1 } +# assert_fileop -- THE TEETH for the vms-3320 CRTL->RMS FILE-OP +# veneer proof (`crtl-rms-fileop-gate' mode). Extends the vms-f49 veneer proof +# from the stdio family to open/creat/unlink/remove/rename/opendir/readdir/ +# closedir: the port image (crtl_rms3_test.c) creats FOPCRE.DAT, creats+unlinks +# FOPDEL.DAT, creats+renames FOPSRC.DAT->FOPDST.DAT via the VECTOR-SUBSTITUTED +# decc$* file-ops, then the boot's INDEPENDENT reader (DCL DIRECTORY over the +# ACP -- a DIFFERENT accessor) inspects the volume. Pass iff, in the +# INDEPENDENT-reader region: +# (b1) FOPCRE.DAT present WITH a nonzero ODS-2 File ID (creat landed); +# (b2) FOPDST.DAT present WITH a nonzero ODS-2 File ID (rename target landed); +# (b3) FOPDEL.DAT %DIRECT-W-NOFILES (unlink removed it); +# (b4) FOPSRC.DAT %DIRECT-W-NOFILES (rename moved it away); +# AND (c) no activation-LOAD failure. The port program's own sentinel-7 self- +# enumeration is informational only (a post-op cleanup crash cannot undo a +# landing the independent reader already confirmed -- the vms-f49 banked-gate +# discipline). Pure function over the transcript; shared by the boot + selftest. +assert_fileop() { + local log="$1" + [ -f "$log" ] || { echo " FAIL: no console log at $log"; return 1; } + + # (a) informational: the port image's own self-verify (sentinel 7 + OK line). + local port_ok seam mile_hex sentinel="?" + port_ok=$(grep -qaE "OVMX CRTL/RMS3 file-op test: OK" "$log" && echo 1 || echo 0) + seam=$(grep -aoE "OVMX-SEAM: image=JOINT_E2E\.EXE[^\"]*STATUS=0x[0-9A-Fa-f]+" "$log" 2>/dev/null | tail -1) + mile_hex=$(printf '%s' "$seam" | grep -oiE '0x[0-9a-f]+' | tail -1) + if [ -n "$mile_hex" ] && [ "$(( mile_hex ))" -ge "$CEXIT1" ] && [ $(( ( $(( mile_hex )) - CEXIT1) % 8 )) -eq 0 ]; then + sentinel=$(( ( $(( mile_hex )) - CEXIT1) / 8 + 1 )) + fi + + # (b) THE TEETH -- confine to the INDEPENDENT-reader region, then sub-region by + # the per-file "--- ... ---" markers so a token cannot leak across files. + local region cre_reg dst_reg del_reg src_reg + region=$(awk '/FILEOP-PROOF: === INDEPENDENT READER/{f=1} f{print} /FILEOP-PROOF: === END INDEPENDENT READER/{f=0}' "$log") + cre_reg=$(printf '%s\n' "$region" | awk '/created file FOPCRE.DAT/{f=1} f{print} /FILEOP-PROOF: CRE-STATUS/{f=0}') + dst_reg=$(printf '%s\n' "$region" | awk '/renamed target FOPDST.DAT/{f=1} f{print} /FILEOP-PROOF: DST-STATUS/{f=0}') + del_reg=$(printf '%s\n' "$region" | awk '/unlinked file FOPDEL.DAT/{f=1} f{print} /FILEOP-PROOF: DEL-STATUS/{f=0}') + src_reg=$(printf '%s\n' "$region" | awk '/rename source FOPSRC.DAT/{f=1} f{print} /FILEOP-PROOF: SRC-STATUS/{f=0}') + + local cre_fid dst_fid cre_ok=0 dst_ok=0 del_ok=0 src_ok=0 + cre_fid=$(printf '%s' "$cre_reg" | grep -aoE "FOPCRE\.DAT;[0-9]+[^A-Za-z]*File ID:[[:space:]]*\([0-9]+" | grep -oE '\([0-9]+' | tr -d '(' | tail -1) + dst_fid=$(printf '%s' "$dst_reg" | grep -aoE "FOPDST\.DAT;[0-9]+[^A-Za-z]*File ID:[[:space:]]*\([0-9]+" | grep -oE '\([0-9]+' | tr -d '(' | tail -1) + [ -n "$cre_fid" ] && [ "$cre_fid" -gt 0 ] && ! printf '%s' "$cre_reg" | grep -qaE "%DIRECT-W-NOFILES" && cre_ok=1 + [ -n "$dst_fid" ] && [ "$dst_fid" -gt 0 ] && ! printf '%s' "$dst_reg" | grep -qaE "%DIRECT-W-NOFILES" && dst_ok=1 + printf '%s' "$del_reg" | grep -qaE "%DIRECT-W-NOFILES" && del_ok=1 + printf '%s' "$src_reg" | grep -qaE "%DIRECT-W-NOFILES" && src_ok=1 + + # (c) activation-LOAD failure only (a post-landing crash is not fatal here). + local errs err_ok=1 + errs=$(grep -aE "%IMGACT-F|IMGNOTFND|DEVNOTMOUNT" "$log" 2>/dev/null || true) + [ -n "$errs" ] && err_ok=0 + + echo " (a) port self-verify (informational) : port_ok=$port_ok seam=${seam:-} sentinel=$sentinel (7 = full)" + echo " (b) INDEPENDENT ACP reader (DIRECTORY):" + echo " b1 creat FOPCRE.DAT present, fid=${cre_fid:-} ok=$cre_ok" + echo " b2 rename FOPDST.DAT present, fid=${dst_fid:-} ok=$dst_ok" + echo " b3 unlink FOPDEL.DAT GONE (%DIRECT-W-NOFILES) ok=$del_ok" + echo " b4 rnsrc FOPSRC.DAT GONE (%DIRECT-W-NOFILES) ok=$src_ok" + echo " (c) image activated (no load failure) : ok=$err_ok" + [ "$err_ok" -eq 0 ] && echo " offending: $(printf '%s' "$errs" | tr '\n' '|')" + + [ "$cre_ok" -eq 1 ] && [ "$dst_ok" -eq 1 ] && [ "$del_ok" -eq 1 ] && [ "$src_ok" -eq 1 ] && [ "$err_ok" -eq 1 ] && return 0 + return 1 +} + # --------------------------------------------------------------------------- # build_joint_images -- build the N=3 milestone image (joint_main.c -> return 3) # and the SS$_NORMAL control (joint_main_ok.c -> return 0) with the SAME merged @@ -425,6 +490,12 @@ build_joint_images() { || die "veneer build produced no ${_p}\$SHR.EXE in $out_n3 (JOINT_CRTL_RMS_VENEER=1 expected the full producer graph)" cp "$out_n3/${_p}\$SHR.EXE" "$WORK/joint/${_p}\$SHR.EXE" done + # vms-3320: carry the FILE-OP marker (dropped by build-joint-image.sh when + # JOINT_MAIN=crtl_rms3_test.c) into $WORK/joint so build-alpha-bootimage.sh + # stages the FILE-OP independent-reader SYSTARTUP (DIRECTORY of the FOP*.DAT + # set) instead of the stdio VENEER one (which reads PORTTEST.DAT). The + # selective staging above would otherwise drop it. + [ -f "$out_n3/FILEOP_PROOF" ] && cp "$out_n3/FILEOP_PROOF" "$WORK/joint/FILEOP_PROOF" log "step 1: joint images staged into $WORK/joint (VENEER milestone N=$WANT_SENTINEL + control + DECC\$SHR/LIBOTS + full RMS producer graph LIBVMSRMS/LIBVMS/LIBVMSFS/LIBVMSLNM/LIBVMSPROCESS/LIBVMSSYS\$SHR)" else log "step 1: joint images staged into $WORK/joint (milestone N=$WANT_SENTINEL + SS\$_NORMAL control + producers)" @@ -466,7 +537,7 @@ run_boot_a() { # activated image (GETEXIT(SEL_SELF)); the DCL RUN fork path collapses the # POSIX exit, so the seam is the truth for the returned value. timeout "$BT" qemu-system-alpha -M clipper -smp 1 -m 1024 -vga none -nic none \ - -kernel vmlinux-boot -append "console=ttyS0 panic=-1 OVMX_IMGACT_SEAM=1" \ + -kernel vmlinux-boot -append "console=ttyS0 panic=-1 OVMX_IMGACT_SEAM=1 ${BOOT_APPEND_EXTRA:-}" \ -drive file=modgpA.img,format=raw,if=virtio \ -nographic -no-reboot <"$FIFO" > modgpA.raw 2>&1 & QP=$! @@ -876,6 +947,133 @@ EOF tail -60 "$WORK/modgpA.log" 2>/dev/null | sed 's/^/ | /' || true exit 1 ;; + crtl-rms-fileop-gate) + # vms-3320 (parent vms-b4f, blocks vms-fd1): the un-fakeable CRTL->RMS + # FILE-OP veneer proof -- open/creat/unlink/remove/rename/opendir/readdir/ + # closedir beyond the stdio family. Same VENEER path as crtl-rms-veneer-gate + # (JOINT_CRTL_RMS_VENEER=1) but the MILESTONE image is crtl_rms3_test.c: it + # creats FOPCRE.DAT, creats+unlinks FOPDEL.DAT, creats+renames FOPSRC.DAT-> + # FOPDST.DAT via the VECTOR-SUBSTITUTED decc$* file-ops (bound by sv# index + # to the crtl_rms_stdio.c veneer -> sys$create/$erase/$rename -> the ACP), + # leaving FOPCRE.DAT + FOPDST.DAT behind. The proof is an INDEPENDENT reader + # (DCL DIRECTORY over the ACP, a different accessor) seeing FOPCRE.DAT + + # FOPDST.DAT with genuine ODS-2 File IDs and FOPDEL.DAT/FOPSRC.DAT gone. + MILESTONE_MAIN=crtl_rms3_test.c + WANT_SENTINEL=7 + JOINT_CRTL_RMS_VENEER=1 + # Fault-capture: print the user PC of any fatal signal so a crash (e.g. the + # pre-existing veneer-build mallocng crash vms-c5d) can be mapped to a symbol + # (subtract the DECC$SHR +0x2c000 slide: file_off = VA - 0x20000000000 - 0x2c000). + export BOOT_APPEND_EXTRA="ignore_loglevel print-fatal-signals=1 loglevel=8" + + # Prove assert_fileop has teeth before trusting a green boot (mirrors the + # crtl-rms-veneer-gate selftest discipline: a can-fail gate certifies nothing). + _st=$(mktemp -d); _fails=0 + cat > "$_st/pass.log" <<'EOF' +OVMX CRTL/RMS3 file-op test: OK (open+creat+unlink+rename+opendir+readdir+closedir over RMS) argc=1 +OVMX-SEAM: image=JOINT_E2E.EXE stdcall_returned=1 has_exited=1 $STATUS=0x0035a039 +FILEOP-PROOF: === INDEPENDENT READER: DIRECTORY over the ACP (a DIFFERENT accessor) === +FILEOP-PROOF: --- created file FOPCRE.DAT (must be present) --- +FOPCRE.DAT;1 File ID: (21,1,0) +FILEOP-PROOF: CRE-STATUS=%X00000001 SEVERITY=1 +FILEOP-PROOF: --- renamed target FOPDST.DAT (must be present, genuine File ID) --- +FOPDST.DAT;1 File ID: (23,1,0) +FILEOP-PROOF: DST-STATUS=%X00000001 SEVERITY=1 +FILEOP-PROOF: --- unlinked file FOPDEL.DAT (must be GONE) --- +%DIRECT-W-NOFILES, no files found +FILEOP-PROOF: DEL-STATUS=%X00018292 SEVERITY=0 +FILEOP-PROOF: --- rename source FOPSRC.DAT (must be GONE) --- +%DIRECT-W-NOFILES, no files found +FILEOP-PROOF: SRC-STATUS=%X00018292 SEVERITY=0 +FILEOP-PROOF: === END INDEPENDENT READER === +EOF + # NEGATIVE 1: creat did NOT land (independent reader sees FOPCRE.DAT gone). FAIL. + cat > "$_st/nocre.log" <<'EOF' +FILEOP-PROOF: === INDEPENDENT READER: DIRECTORY over the ACP (a DIFFERENT accessor) === +FILEOP-PROOF: --- created file FOPCRE.DAT (must be present) --- +%DIRECT-W-NOFILES, no files found +FILEOP-PROOF: CRE-STATUS=%X00018292 SEVERITY=0 +FILEOP-PROOF: --- renamed target FOPDST.DAT (must be present, genuine File ID) --- +FOPDST.DAT;1 File ID: (23,1,0) +FILEOP-PROOF: DST-STATUS=%X00000001 SEVERITY=1 +FILEOP-PROOF: --- unlinked file FOPDEL.DAT (must be GONE) --- +%DIRECT-W-NOFILES, no files found +FILEOP-PROOF: --- rename source FOPSRC.DAT (must be GONE) --- +%DIRECT-W-NOFILES, no files found +FILEOP-PROOF: === END INDEPENDENT READER === +EOF + # NEGATIVE 2: unlink did NOT remove FOPDEL.DAT (reader still sees it). FAIL. + cat > "$_st/nodel.log" <<'EOF' +FILEOP-PROOF: === INDEPENDENT READER: DIRECTORY over the ACP (a DIFFERENT accessor) === +FILEOP-PROOF: --- created file FOPCRE.DAT (must be present) --- +FOPCRE.DAT;1 File ID: (21,1,0) +FILEOP-PROOF: --- renamed target FOPDST.DAT (must be present, genuine File ID) --- +FOPDST.DAT;1 File ID: (23,1,0) +FILEOP-PROOF: --- unlinked file FOPDEL.DAT (must be GONE) --- +FOPDEL.DAT;1 File ID: (22,1,0) +FILEOP-PROOF: --- rename source FOPSRC.DAT (must be GONE) --- +%DIRECT-W-NOFILES, no files found +FILEOP-PROOF: === END INDEPENDENT READER === +EOF + # NEGATIVE 3: activation load failure -> FAIL even if a stale region parsed. + cat > "$_st/imgact.log" <<'EOF' +%IMGACT-F-IMGNOTFND, image file not found LIBVMSRMS$SHR +FILEOP-PROOF: === INDEPENDENT READER: DIRECTORY over the ACP (a DIFFERENT accessor) === +FILEOP-PROOF: --- created file FOPCRE.DAT (must be present) --- +FOPCRE.DAT;1 File ID: (21,1,0) +FILEOP-PROOF: --- renamed target FOPDST.DAT (must be present, genuine File ID) --- +FOPDST.DAT;1 File ID: (23,1,0) +FILEOP-PROOF: --- unlinked file FOPDEL.DAT (must be GONE) --- +%DIRECT-W-NOFILES, no files found +FILEOP-PROOF: --- rename source FOPSRC.DAT (must be GONE) --- +%DIRECT-W-NOFILES, no files found +FILEOP-PROOF: === END INDEPENDENT READER === +EOF + echo "-- fileop selftest 1/4: clean independent-reader proof must PASS --" + if assert_fileop "$_st/pass.log" >/dev/null 2>&1; then echo " PASS"; else echo " FAIL: clean proof rejected"; _fails=$((_fails+1)); fi + echo "-- fileop selftest 2/4: creat did not land (FOPCRE gone) must FAIL --" + if assert_fileop "$_st/nocre.log" >/dev/null 2>&1; then echo " FAIL: accepted"; _fails=$((_fails+1)); else echo " PASS (rejected)"; fi + echo "-- fileop selftest 3/4: unlink no-op (FOPDEL still present) must FAIL --" + if assert_fileop "$_st/nodel.log" >/dev/null 2>&1; then echo " FAIL: accepted"; _fails=$((_fails+1)); else echo " PASS (rejected)"; fi + echo "-- fileop selftest 4/4: activation load failure must FAIL --" + if assert_fileop "$_st/imgact.log" >/dev/null 2>&1; then echo " FAIL: accepted"; _fails=$((_fails+1)); else echo " PASS (rejected)"; fi + rm -rf "$_st" + [ "$_fails" -eq 0 ] || die "fileop selftest failed -- assert_fileop cannot be trusted; aborting before the boot" + echo "" + + build_joint_images + assemble_boot_image + log "step 3: BOOT A -- activate the FILE-OP veneer image + run the INDEPENDENT DIRECTORY reader on the REAL executive" + run_boot_a + echo "" + echo "========================================================================" + echo "== vms-3320: CRTL->RMS FILE-OP veneer (open/creat/unlink/remove/rename/" + echo "== opendir/readdir/closedir) -> real ODS-2 effects, PROVEN by an INDEPENDENT" + echo "== ACP reader (DIRECTORY) on the real OVMX/Alpha executive (qemu-system-alpha" + echo "== + /dev/vms). Blocks vms-fd1 (the full alpha-dec-vms GCC port)." + echo "========================================================================" + grep -aE "FILEOP-PROOF:|OVMX CRTL/RMS3|OVMX-SEAM:|FOP...\.DAT|File ID:|%DIRECT|%IMGACT|%DCL-" "$WORK/modgpA.log" 2>/dev/null | sed 's/^/ | /' || true + echo "------------------------------------------------------------------------" + if assert_fileop "$WORK/modgpA.log"; then + echo "" + echo "PASS: the FILE-OP veneer-wired port image's decc\$open/creat/unlink/rename/" + echo " opendir/readdir/closedir genuinely reached the real Files-11 ODS-2 volume" + echo " over the ACP -- an INDEPENDENT reader (DCL DIRECTORY, a different accessor" + echo " than the writer's CRTL handle) saw FOPCRE.DAT + FOPDST.DAT with genuine ODS-2" + echo " File IDs and FOPDEL.DAT/FOPSRC.DAT GONE, which a ramfs/POSIX write can never" + echo " produce in the ACP directory. The 8 file-op decc\$ names bind by symbol-vector" + echo " INDEX to the veneer (mk_decc_shr.sh in-place substitution, sv# stable)." + exit 0 + fi + echo "" + echo "FAIL: the file-op veneer did NOT reach the real ODS-2 volume as expected (the" + echo " INDEPENDENT reader disagreed: a created/renamed file missing a File ID, or a" + echo " deleted/renamed-away file still present). Full log: $WORK/modgpA.log" + grep -aE "FILEOP-PROOF:|%IMGACT|%RUN-|%DCL-|IMGNOTFND|NOSUCHFILE|DEVNOTMOUNT|ACCVIO|%DIRECT|SS\\\$_" "$WORK/modgpA.log" 2>/dev/null | sed 's/^/ /' | tail -30 || echo " (none captured)" + echo "--- last 60 console lines ---" + tail -60 "$WORK/modgpA.log" 2>/dev/null | sed 's/^/ | /' || true + exit 1 + ;; shipped-gate) # vms-410: proves the Alpha shareable graph ships as part of the ORDINARY # build-alpha-bootimage.sh packaging path, not as something only THIS