#!/bin/sh set -eu : "${PRE15_DUT:?PRE15_DUT is required}" : "${PRE15_ROOT:?PRE15_ROOT is required}" : "${PRE15_CASE_TMP:?PRE15_CASE_TMP is required}" : "${PRE15_RUN_DIR:?PRE15_RUN_DIR is required}" : "${PRE15_LIB_DIR:?PRE15_LIB_DIR is required}" . "$PRE15_LIB_DIR/runner.sh" case_script=$PRE15_DUT/tests/pre15/cases/B29-context-pool.sh kld_builder=$PRE15_DUT/tests/pre15/fixtures/B28-build-kld.sh old_gate=$PRE15_DUT/tests/pre15/gates/P15-076.sh old_input=$PRE15_DUT/tests/pre15/gates/P15-076-input.json old_evidence=$PRE15_ROOT/planning/pre15/evidence/20260815T171051Z-G05-P15-076 checkpoint=$PRE15_ROOT/planning/pre15/evidence/20260817T153426Z-B29/checkpoint.md artifacts=$PRE15_RUN_DIR/artifacts fixtures=$PRE15_CASE_TMP/fixtures action=${1:-runtime} case "$action" in runtime|k2) ;; *) pre15_fail_usage "unknown B29 action: $action" ;; esac for tool in awk cc cmp file fsck.erofs git mkfs.erofs nm python3 sha256sum \ tar timeout; do command -v "$tool" >/dev/null 2>&1 || \ pre15_infra_blocked "missing B29 tool: $tool" done for fixture in "$case_script" "$kld_builder" "$old_gate" "$old_input" \ "$old_evidence/gate-output/benchmark-results.json" \ "$old_evidence/gate-output/state-model-result.json" \ "$old_evidence/VERDICT.md" "$checkpoint"; do pre15_record_fixture "b29-$(basename "$fixture")" "$fixture" done for source in internal.h super.c decompressor_lzma.c decompressor_deflate.c \ decompressor_zstd.c; do pre15_record_fixture "b29-$source" "$PRE15_DUT/src/$source" done for source in decompressor_lzma.c decompressor_deflate.c \ decompressor_zstd.c; do pre15_record_fixture "b29-linux-$source" "$PRE15_ROOT/src-linux/$source" done mkdir -p "$artifacts" if test "$action" = k2; then for config in 0 1; do module=$PRE15_CASE_TMP/B29-zstdio$config.ko if ! timeout -k 10 600 /bin/sh "$kld_builder" "$PRE15_DUT" \ "$PRE15_FREEBSD_SRC" "$module" \ "$PRE15_CASE_TMP/kld-work-$config" "$config" \ >"$artifacts/B29-build-zstdio$config.stdout" \ 2>"$artifacts/B29-build-zstdio$config.stderr"; then pre15_dut_fail "B29 zstdio$config exact-ABI KLD build failed" fi file "$module" >"$artifacts/B29-zstdio$config-file.txt" sha256sum "$module" >"$artifacts/B29-zstdio$config-sha256.txt" nm -g "$module" | LC_ALL=C sort \ >"$artifacts/B29-zstdio$config-nm-global.txt" nm -u "$module" | LC_ALL=C sort \ >"$artifacts/B29-zstdio$config-nm-u.txt" done awk '$1 != "U" { print $NF }' "$artifacts/B29-zstdio0-nm-global.txt" \ >"$PRE15_CASE_TMP/B29-zstdio0-global-names.txt" awk '$1 != "U" { print $NF }' "$artifacts/B29-zstdio1-nm-global.txt" \ >"$PRE15_CASE_TMP/B29-zstdio1-global-names.txt" if ! diff -u "$PRE15_CASE_TMP/B29-zstdio0-global-names.txt" \ "$PRE15_CASE_TMP/B29-zstdio1-global-names.txt" \ >"$artifacts/B29-config-nm-global.diff"; then pre15_dut_fail 'B29 defined globals differ by ZSTDIO configuration' fi awk '$NF ~ /^ZSTD_/ { print $NF }' "$artifacts/B29-zstdio1-nm-u.txt" \ >"$PRE15_CASE_TMP/B29-zstd-symbols.txt" printf '%s\n' ZSTD_DCtx_setParameter ZSTD_createDCtx_advanced \ ZSTD_decompressStream ZSTD_freeDCtx ZSTD_isError \ >"$PRE15_CASE_TMP/B29-zstd-expected.txt" if ! cmp "$PRE15_CASE_TMP/B29-zstd-expected.txt" \ "$PRE15_CASE_TMP/B29-zstd-symbols.txt"; then pre15_dut_fail 'B29 changed the accepted five-symbol Zstd provider ABI' fi pre15_target_reached printf '%s\n' \ 'B29 FreeBSD 15 exact-ABI K2 PASS' \ 'zstdio0/zstdio1 compile, link, global-symbol parity, and five-symbol optional Zstd ABI PASS' exit 0 fi if ! python3 -B - "$PRE15_ROOT" "$PRE15_DUT" "$PRE15_FREEBSD_SRC" \ "$old_evidence" "$artifacts/B29-source-audit.json" <<'PY' from __future__ import annotations import json from pathlib import Path import re import subprocess import sys root = Path(sys.argv[1]) dut = Path(sys.argv[2]) freebsd = Path(sys.argv[3]) evidence = Path(sys.argv[4]) report_path = Path(sys.argv[5]) def fail(message: str) -> None: raise SystemExit(message) current = { name: (dut / "src" / name).read_text(encoding="utf-8") for name in ( "internal.h", "super.c", "decompressor_lzma.c", "decompressor_deflate.c", "decompressor_zstd.c", ) } linux = { name: (root / "src-linux" / name).read_text(encoding="utf-8") for name in ( "decompressor_lzma.c", "decompressor_deflate.c", "decompressor_zstd.c", ) } benchmark = json.loads( (evidence / "gate-output/benchmark-results.json").read_text(encoding="ascii") ) old_state = json.loads( (evidence / "gate-output/state-model-result.json").read_text(encoding="ascii") ) old_verdict = (evidence / "VERDICT.md").read_text(encoding="utf-8") assertions = 0 def require(condition: bool, message: str) -> None: global assertions assertions += 1 if not condition: fail(message) for codec, record in benchmark.items(): require(record["baseline_context_allocator_share_percent"] >= 10.0, f"{codec} old allocator-share benefit regressed") require(record["allocator_event_reduction_percent"] >= 25.0, f"{codec} old lifecycle benefit regressed") require(old_state["pool_exhaustion_fallback"] == "fresh baseline allocation", "historical unbounded fallback record changed") require("global-exhaustion scenario" in old_verdict, "historical global-exhaustion rejection is no longer explicit") require("maximum window log 16" in old_verdict and "8 MiB" in old_verdict, "historical maximum-parameter gap is no longer explicit") super_source = current["super.c"] hard_caps = { "EROFS_STREAM_MOUNT_HARD_BUDGET": "32UL * 1024 * 1024", "EROFS_STREAM_GLOBAL_HARD_BUDGET": "128UL * 1024 * 1024", "EROFS_STREAM_ALLOCATION_HARD_MAX": "4UL * 1024 * 1024", "EROFS_STREAM_MOUNT_HARD_CONTEXTS": "16U", "EROFS_STREAM_GLOBAL_HARD_CONTEXTS": "64U", "EROFS_STREAM_MOUNT_CACHED_PER_CODEC": "2U", "EROFS_STREAM_GLOBAL_CACHED_PER_CODEC": "16U", } for name, value in hard_caps.items(): require(f"#define {name} ({value})" in super_source, f"missing production hard cap {name}") for token in ( 'TUNABLE_ULONG("vfs.erofs.stream_pool.mount_budget"', 'TUNABLE_ULONG("vfs.erofs.stream_pool.global_budget"', 'TUNABLE_INT("vfs.erofs.stream_pool.global_contexts"', "MIN(erofs_stream_mount_budget,", "MIN(erofs_stream_global_budget,", "uma_zone_set_max(erofs_stream_zone,", "erofs_stream_reserve_locked(sbi, EROFS_STREAM_CTX_WRAPPER_SIZE)", "++erofs_stream_fallbacks", "++erofs_stream_exhaustions", "EVENTHANDLER_REGISTER(vm_lowmem", "while (!erofs_stream_pool_empty_locked(&sbi->stream_pool))", "KASSERT(erofs_stream_global_contexts == 0", ): require(token in super_source, f"missing bounded pool token: {token}") require(super_source.find("erofs_stream_reserve_locked(sbi, EROFS_STREAM_CTX_WRAPPER_SIZE)") < super_source.find("uma_zalloc(erofs_stream_zone"), "wrapper allocation occurs before hard-budget reservation") require("fresh baseline allocation" not in super_source, "production retained the historical unbounded fallback") internal = current["internal.h"] for field in ("resident_bytes", "contexts", "cached", "borrowed", "idle_count", "closing", "stream_pool_initialized"): require(re.search(rf"\b{field}\b", internal) is not None, f"pool state omits {field}") lzma = current["decompressor_lzma.c"] deflate = current["decompressor_deflate.c"] zstd = current["decompressor_zstd.c"] case_source = (dut / "tests/pre15/cases/B29-context-pool.sh").read_text( encoding="utf-8" ) require("xz_dec_microlzma_alloc(XZ_SINGLE" in lzma, "FreeBSD LZMA no longer uses the bounded XZ_SINGLE mode") require("ctx->state_bytes = sizeof(*ctx->state);" in lzma, "LZMA fixed state is not charged exactly") require("dict_size > Z_EROFS_LZMA_MAX_DICT_SIZE" in lzma, "LZMA 8 MiB format hard limit is absent") require("M_WAITOK" not in lzma, "LZMA backend still has an unbounded waiting allocation") require("z_erofs_stream_ctx_alloc(pool, bytes)" in deflate, "Deflate backend allocations bypass hard accounting") require("inflateReset2(strm" in deflate, "Deflate reusable context is not reset") require("z_erofs_stream_ctx_alloc(opaque, size)" in zstd, "Zstd backend allocations bypass hard accounting") require("zstd->windowlog > 10" in zstd and "rq->sbi->zstd_windowlog + 10 > 20" in zstd, "Zstd maximum window parameter is not enforced") require("ZSTD_DCtx_reset" not in zstd, "B29 added a new Zstd provider ABI symbol") require("error == 0 && !rq->partial_decoding" in zstd, "partial Zstd state is incorrectly cached without reset") require('"$B29_BAD_MOUNT" 45' in case_source, "B29 runtime uses the Linux EOPNOTSUPP value instead of FreeBSD 45") require("run_exhaustion_phase lzma byte" in case_source and "run_exhaustion_phase zstd byte" in case_source and "run_exhaustion_phase lzma count" in case_source, "B29 runtime omits a production LZMA/Zstd byte or global count gate") for source in (deflate, zstd): require("M_WAITOK" not in source, "codec backend retained an unbounded waiting allocation") xz_source = (freebsd / "sys/contrib/xz-embedded/linux/lib/xz/xz_dec_lzma2.c").read_text( encoding="utf-8" ) xz_private = (freebsd / "sys/contrib/xz-embedded/linux/lib/xz/xz_private.h").read_text( encoding="utf-8" ) require("if (DEC_IS_MULTI(mode))" in xz_source and "s->s.dict.buf = vmalloc(dict_size);" in xz_source, "FreeBSD MicroLZMA allocation branch changed") require("#\tdefine DEC_IS_MULTI(mode) ((mode) != XZ_SINGLE)" in xz_private, "FreeBSD XZ_SINGLE dictionary-allocation proof changed") for codec, tokens in { "lzma": ("get an available lzma context", "push back LZMA stream context"), "deflate": ("get an available DEFLATE context", "push back DEFLATE stream context"), "zstd": ("get an available ZSTD context", "push back ZSTD stream context"), }.items(): for token in tokens: require(token in linux[f"decompressor_{codec}.c"], f"Linux reusable-stream intent changed for {codec}") status = subprocess.run( ["git", "-C", str(root), "status", "--porcelain"], check=True, text=True, stdout=subprocess.PIPE, ).stdout.splitlines() allowed_implementation = { "repo-pre-15/src/decompressor_lzma.c", "repo-pre-15/src/decompressor_deflate.c", "repo-pre-15/src/decompressor_zstd.c", "repo-pre-15/src/internal.h", "repo-pre-15/src/super.c", "repo-pre-15/tests/pre15/cases/B29-context-pool.sh", } for line in status: path = line[3:] require(path in allowed_implementation or path.startswith("planning/pre15/evidence/20260817T153426Z-B29/") or path in { "planning/pre15/execution-status.md", "planning/pre15/final-review/closure.tsv", "planning/pre15/final-review/completion-audit.md", "planning/pre15/final-review/completion-audit.tsv", }, f"B29 dirty path escapes allowed scope: {path}") report = { "assertions": assertions, "batch": "B29", "benefit_gate_retained": True, "freebsd_difference": { "allocator": "UMA wrapper plus exact mount/global byte charging", "lzma": "XZ_SINGLE fixed state; no dictionary buffer allocation", "locking": "one FreeBSD mutex, no Linux waitqueue/per-CPU API copy", }, "global_exhaustion_required": True, "hard_caps": hard_caps, "linux_reusable_stream_intent": True, "status": "PASS", } report_path.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="ascii") print(json.dumps(report, indent=2, sort_keys=True)) PY then pre15_dut_fail 'B29 benefit/source/hard-cap audit failed' fi cat >"$PRE15_CASE_TMP/B29-pool-oracle.c" <<'EOF' #include #include #include #include #include #include #include #include #define WRAPPER_BYTES 256U #define MOUNT_CACHED 2U #define GLOBAL_CACHED 16U struct budget { pthread_mutex_t lock; pthread_cond_t cv; size_t bytes; size_t byte_limit; unsigned contexts; unsigned context_limit; unsigned cached; unsigned borrowed; unsigned exhaustions; }; struct mount_pool { struct budget *global; size_t bytes; size_t byte_limit; unsigned contexts; unsigned context_limit; unsigned cached; unsigned borrowed; unsigned idle; bool closing; }; struct context { struct mount_pool *mount; bool cached; }; static unsigned assertions; static void check(bool condition, const char *message) { ++assertions; if (!condition) { fprintf(stderr, "B29 oracle failure: %s\n", message); exit(1); } } static void budget_init(struct budget *global, size_t bytes, unsigned contexts) { memset(global, 0, sizeof(*global)); pthread_mutex_init(&global->lock, NULL); pthread_cond_init(&global->cv, NULL); global->byte_limit = bytes; global->context_limit = contexts; } static void budget_fini(struct budget *global) { pthread_cond_destroy(&global->cv); pthread_mutex_destroy(&global->lock); } static void mount_init(struct mount_pool *mount, struct budget *global, size_t bytes, unsigned contexts) { memset(mount, 0, sizeof(*mount)); mount->global = global; mount->byte_limit = bytes; mount->context_limit = contexts; } static int acquire(struct mount_pool *mount, bool inject_failure, struct context **out) { struct budget *global = mount->global; struct context *ctx; bool cached; *out = NULL; pthread_mutex_lock(&global->lock); if (mount->closing) { pthread_mutex_unlock(&global->lock); return ENXIO; } if (mount->idle != 0) { --mount->idle; ++mount->borrowed; ++global->borrowed; pthread_mutex_unlock(&global->lock); ctx = calloc(1, sizeof(*ctx)); check(ctx != NULL, "idle wrapper allocation"); ctx->mount = mount; ctx->cached = true; *out = ctx; return 0; } cached = mount->cached < MOUNT_CACHED && global->cached < GLOBAL_CACHED; if (mount->contexts >= mount->context_limit || global->contexts >= global->context_limit || mount->bytes > mount->byte_limit - WRAPPER_BYTES || global->bytes > global->byte_limit - WRAPPER_BYTES) { ++global->exhaustions; pthread_mutex_unlock(&global->lock); return ENOMEM; } mount->bytes += WRAPPER_BYTES; global->bytes += WRAPPER_BYTES; ++mount->contexts; ++global->contexts; ++mount->borrowed; ++global->borrowed; if (cached) { ++mount->cached; ++global->cached; } pthread_mutex_unlock(&global->lock); if (inject_failure) { pthread_mutex_lock(&global->lock); mount->bytes -= WRAPPER_BYTES; global->bytes -= WRAPPER_BYTES; --mount->contexts; --global->contexts; --mount->borrowed; --global->borrowed; if (cached) { --mount->cached; --global->cached; } pthread_cond_broadcast(&global->cv); pthread_mutex_unlock(&global->lock); return ENOMEM; } ctx = calloc(1, sizeof(*ctx)); check(ctx != NULL, "new wrapper allocation"); ctx->mount = mount; ctx->cached = cached; *out = ctx; return 0; } static void release(struct context *ctx, bool reusable) { struct mount_pool *mount = ctx->mount; struct budget *global = mount->global; pthread_mutex_lock(&global->lock); if (reusable && ctx->cached && !mount->closing) { --mount->borrowed; --global->borrowed; ++mount->idle; pthread_cond_broadcast(&global->cv); pthread_mutex_unlock(&global->lock); free(ctx); return; } --mount->borrowed; --global->borrowed; --mount->contexts; --global->contexts; mount->bytes -= WRAPPER_BYTES; global->bytes -= WRAPPER_BYTES; if (ctx->cached) { --mount->cached; --global->cached; } pthread_cond_broadcast(&global->cv); pthread_mutex_unlock(&global->lock); free(ctx); } static void drain(struct mount_pool *mount) { struct budget *global = mount->global; pthread_mutex_lock(&global->lock); mount->closing = true; while (mount->idle != 0) { --mount->idle; --mount->contexts; --global->contexts; --mount->cached; --global->cached; mount->bytes -= WRAPPER_BYTES; global->bytes -= WRAPPER_BYTES; } while (mount->contexts != 0) pthread_cond_wait(&global->cv, &global->lock); pthread_mutex_unlock(&global->lock); } struct acquire_result { struct mount_pool *mount; int error; }; static void * acquire_worker(void *opaque) { struct acquire_result *result = opaque; struct context *ctx; result->error = acquire(result->mount, false, &ctx); if (result->error == 0) release(ctx, false); return NULL; } struct delayed_release { struct context *ctx; }; static void * release_worker(void *opaque) { struct delayed_release *delayed = opaque; struct timespec delay = { .tv_nsec = 50000000 }; nanosleep(&delay, NULL); release(delayed->ctx, true); return NULL; } int main(void) { struct acquire_result results[2]; struct delayed_release delayed; struct mount_pool a, b, c; struct budget global; struct context *one, *two, *three; pthread_t threads[2]; unsigned before; budget_init(&global, 4096, 8); mount_init(&a, &global, 4096, 8); check(acquire(&a, false, &one) == 0, "first cached acquire"); release(one, true); check(a.idle == 1 && a.cached == 1 && global.contexts == 1, "first release did not cache"); check(acquire(&a, false, &one) == 0, "idle reuse acquire"); check(a.idle == 0 && a.borrowed == 1, "idle reuse counters"); release(one, true); drain(&a); check(global.contexts == 0 && global.bytes == 0, "reuse drain leaked"); budget_fini(&global); budget_init(&global, 4096, 8); mount_init(&a, &global, 4096, 8); check(acquire(&a, false, &one) == 0, "fallback first acquire"); check(acquire(&a, false, &two) == 0, "fallback second acquire"); check(acquire(&a, false, &three) == 0, "bounded fallback acquire"); check(one->cached && two->cached && !three->cached, "cache saturation did not select bounded temporary context"); release(one, true); release(two, true); release(three, true); check(a.contexts == 2 && a.idle == 2 && global.contexts == 2, "temporary fallback was retained or cached contexts were lost"); drain(&a); check(global.contexts == 0 && global.bytes == 0, "fallback drain leaked"); budget_fini(&global); budget_init(&global, 4096, 1); mount_init(&a, &global, 4096, 8); mount_init(&b, &global, 4096, 8); mount_init(&c, &global, 4096, 8); check(acquire(&a, false, &one) == 0, "global owner acquire"); release(one, true); before = global.exhaustions; results[0].mount = &b; results[1].mount = &c; check(pthread_create(&threads[0], NULL, acquire_worker, &results[0]) == 0, "first exhaustion worker create"); check(pthread_create(&threads[1], NULL, acquire_worker, &results[1]) == 0, "second exhaustion worker create"); check(pthread_join(threads[0], NULL) == 0, "first exhaustion worker join"); check(pthread_join(threads[1], NULL) == 0, "second exhaustion worker join"); check(results[0].error == ENOMEM && results[1].error == ENOMEM, "concurrent global exhaustion errno is unstable"); check(global.exhaustions == before + 2 && global.contexts == 1 && global.bytes == WRAPPER_BYTES, "global exhaustion changed retained ownership"); drain(&a); check(acquire(&b, false, &one) == 0, "recovery acquire failed"); release(one, true); drain(&b); drain(&c); check(global.contexts == 0 && global.bytes == 0 && global.borrowed == 0, "global recovery leaked resources"); budget_fini(&global); budget_init(&global, 511, 8); mount_init(&a, &global, 511, 8); mount_init(&b, &global, 511, 8); check(acquire(&a, false, &one) == 0, "byte owner acquire"); check(acquire(&b, false, &two) == ENOMEM, "global byte exhaustion did not return ENOMEM"); release(one, false); check(acquire(&b, false, &two) == 0, "byte recovery acquire failed"); release(two, false); drain(&a); drain(&b); check(global.bytes == 0 && global.contexts == 0, "byte recovery leaked"); budget_fini(&global); budget_init(&global, 4096, 8); mount_init(&a, &global, 4096, 8); check(acquire(&a, true, &one) == ENOMEM, "construction failure did not return ENOMEM"); check(global.bytes == 0 && global.contexts == 0 && global.borrowed == 0, "construction failure did not roll back reservations"); drain(&a); budget_fini(&global); budget_init(&global, 4096, 8); mount_init(&a, &global, 4096, 8); check(acquire(&a, false, &one) == 0, "drain borrower acquire"); delayed.ctx = one; check(pthread_create(&threads[0], NULL, release_worker, &delayed) == 0, "drain release worker create"); drain(&a); check(pthread_join(threads[0], NULL) == 0, "drain release worker join"); check(global.bytes == 0 && global.contexts == 0 && global.borrowed == 0, "unmount drain did not wait for borrower cleanup"); budget_fini(&global); printf("B29 pool oracle PASS assertions=%u global-count global-bytes failure rollback recovery drain\n", assertions); return 0; } EOF if ! cc -std=c11 -Wall -Wextra -Werror -pthread \ "$PRE15_CASE_TMP/B29-pool-oracle.c" -o "$PRE15_CASE_TMP/B29-pool-oracle" \ >"$artifacts/B29-pool-oracle-build.stdout" \ 2>"$artifacts/B29-pool-oracle-build.stderr" || \ ! "$PRE15_CASE_TMP/B29-pool-oracle" \ >"$artifacts/B29-pool-oracle.stdout" \ 2>"$artifacts/B29-pool-oracle.stderr"; then pre15_dut_fail 'B29 concurrent pool accounting oracle failed' fi if ! python3 -B - "$fixtures" "$artifacts/B29-fixture-index.json" <<'PY' from __future__ import annotations import hashlib import json from pathlib import Path import shutil import struct import subprocess import sys output = Path(sys.argv[1]) index_path = Path(sys.argv[2]) source = output / "source" images = output / "images" output.mkdir(parents=True) source.mkdir() images.mkdir() def run(argv: list[str]) -> None: completed = subprocess.run( argv, check=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, timeout=120, ) if completed.returncode != 0: raise SystemExit(f"command failed ({completed.returncode}): {' '.join(argv)}\n{completed.stdout}") block = bytes((index * 29 + 17) & 0xFF for index in range(4096)) for index in range(3): payload = (block[index * 257 :] + block[: index * 257]) * 64 (source / f"payload-{index}.bin").write_bytes(payload) specs = { "lzma": "-zlzma,level=6,dictsize=65536", "lzma-max": "-zlzma,level=6,dictsize=8388608", "deflate": "-zdeflate,level=1,dictsize=32768", "zstd": "-zzstd,level=3,dictsize=65536", "zstd-max": "-zzstd,level=3,dictsize=1048576", } records = [] for name, option in specs.items(): image = images / f"{name}.erofs" run([ "mkfs.erofs", "-T0", "--all-time", "--force-uid=0", "--force-gid=0", "-C65536", "--max-extent-bytes=1048576", option, str(image), str(source), ]) run(["fsck.erofs", "--extract=" + str(output / (name + "-extract")), str(image)]) for index in range(3): actual = output / (name + "-extract") / f"payload-{index}.bin" if actual.read_bytes() != (source / f"payload-{index}.bin").read_bytes(): raise SystemExit(f"{name} extraction mismatch for payload-{index}") records.append({"name": name, "sha256": hashlib.sha256(image.read_bytes()).hexdigest()}) def crc32c(data: bytes | bytearray, seed: int = 0xFFFFFFFF) -> int: value = seed polynomial = 0x82F63B78 for byte in data: value ^= byte for _ in range(8): value = (value >> 1) ^ (polynomial if value & 1 else 0) return value & 0xFFFFFFFF def mutate(source_image: Path, target: int, mutate) -> Path: data = bytearray(source_image.read_bytes()) super_offset = 1024 block_size = 1 << data[super_offset + 12] sb_size = 128 + data[super_offset + 13] * 16 algorithms = struct.unpack_from(" super_offset: checksum_size -= super_offset checksum = crc32c( data[super_offset + 8 : super_offset + checksum_size], seed=0x5045B54A, ) struct.pack_into(""$artifacts/SHA256SUMS" if test "${PRE15_QEMU_TARGET_ONLY:-0}" = 1; then pre15_target_reached elif test "${PRE15_MODE:-host}" != qemu; then pre15_target_reached printf '%s\n' \ 'B29 focused host/static gate PASS' \ "$(cat "$artifacts/B29-pool-oracle.stdout")" \ 'Old G05 benefit retained; production hard caps, bounded fallback, true global count/byte exhaustion model, rollback, drain, and recovery PASS' \ 'QEMU runtime NOT_RUN in host mode; full feature suite NOT_RUN' exit 0 fi for tool in scp ssh; do command -v "$tool" >/dev/null 2>&1 || \ pre15_infra_blocked "missing B29 QEMU tool: $tool" done : "${PRE15_QEMU_CONTROL_PATH:?QEMU control path is required}" : "${PRE15_QEMU_SSH_PORT:?QEMU SSH port is required}" : "${PRE15_QEMU_SSH_USER:?QEMU SSH user is required}" module=$PRE15_CASE_TMP/B29-erofs-zstdio1.ko fixture_archive=$PRE15_CASE_TMP/B29-fixtures.tar.gz if ! /bin/sh "$kld_builder" "$PRE15_DUT" "$PRE15_FREEBSD_SRC" "$module" \ "$PRE15_CASE_TMP/kld-work-qemu" 1 >"$artifacts/B29-kld-build.stdout" \ 2>"$artifacts/B29-kld-build.stderr"; then pre15_dut_fail 'B29 exact-ABI zstdio1 QEMU KLD build failed' fi pre15_record_module "$module" file "$module" >"$artifacts/B29-kld-file.txt" sha256sum "$module" >"$artifacts/B29-kld-sha256.txt" nm -u "$module" | LC_ALL=C sort >"$artifacts/B29-kld-nm-u.txt" tar -C "$fixtures" -czf "$fixture_archive" images source cat >"$PRE15_CASE_TMP/B29-read-probe.c" <<'EOF' #include #include #include #include #include #include static int read_all(const char *path, unsigned char **datap, size_t *sizep, int *errorp) { unsigned char *data; size_t capacity, size; ssize_t count; int fd; fd = open(path, O_RDONLY); if (fd < 0) { *errorp = errno; return (-1); } capacity = 1024 * 1024; data = malloc(capacity); if (data == NULL) return (-1); size = 0; while ((count = read(fd, data + size, capacity - size)) > 0) { size += (size_t)count; if (size == capacity) break; } if (count < 0) { *errorp = errno; close(fd); free(data); return (-1); } close(fd); *datap = data; *sizep = size; *errorp = 0; return (0); } int main(int argc, char **argv) { unsigned char *actual, *expected; size_t actual_size, expected_size; int actual_error, expected_error; if (argc == 4 && strcmp(argv[1], "pass") == 0) { if (read_all(argv[2], &actual, &actual_size, &actual_error) != 0 || read_all(argv[3], &expected, &expected_size, &expected_error) != 0) return (1); if (actual_size != expected_size || memcmp(actual, expected, actual_size) != 0) return (1); free(actual); free(expected); return (0); } if (argc == 4 && strcmp(argv[1], "errno") == 0) { expected_error = atoi(argv[3]); if (read_all(argv[2], &actual, &actual_size, &actual_error) == 0) { free(actual); return (1); } return (actual_error == expected_error ? 0 : 1); } fprintf(stderr, "usage: B29-read-probe pass ACTUAL EXPECTED | errno PATH ERRNO\n"); return (2); } EOF cat >"$PRE15_CASE_TMP/B29-mount-probe.c" <<'EOF' #include #include #include #include #include #include static void add_iovec(struct iovec **iov, int *iovlen, const char *name, const char *value) { *iov = realloc(*iov, sizeof(**iov) * (size_t)(*iovlen + 2)); (*iov)[*iovlen].iov_base = __DECONST(char *, name); (*iov)[*iovlen].iov_len = strlen(name) + 1; ++*iovlen; (*iov)[*iovlen].iov_base = __DECONST(char *, value); (*iov)[*iovlen].iov_len = strlen(value) + 1; ++*iovlen; } int main(int argc, char **argv) { struct iovec *iov; int error, expected, iovlen; if (argc != 4) return (2); expected = atoi(argv[3]); iov = NULL; iovlen = 0; add_iovec(&iov, &iovlen, "fstype", "erofs"); add_iovec(&iov, &iovlen, "fspath", argv[2]); add_iovec(&iov, &iovlen, "from", argv[1]); if (nmount(iov, (unsigned)iovlen, MNT_RDONLY) == 0) { unmount(argv[2], 0); free(iov); return (1); } error = errno; free(iov); return (error == expected ? 0 : 1); } EOF pre15_scp() { timeout -k 5 "${PRE15_GUEST_COMMAND_TIMEOUT:-60}" scp -O -q \ -o BatchMode=yes -o StrictHostKeyChecking=no \ -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5 \ -o "ControlPath=$PRE15_QEMU_CONTROL_PATH" \ -P "$PRE15_QEMU_SSH_PORT" \ "$1" "$PRE15_QEMU_SSH_USER@127.0.0.1:$2" } pre15_guest_ssh_bounded() { timeout -k 5 "${PRE15_GUEST_COMMAND_TIMEOUT:-60}" ssh -n -q \ -o BatchMode=yes -o StrictHostKeyChecking=no \ -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5 \ -o "ControlPath=$PRE15_QEMU_CONTROL_PATH" \ -p "$PRE15_QEMU_SSH_PORT" \ "$PRE15_QEMU_SSH_USER@127.0.0.1" "$@" } if ! pre15_scp "$fixture_archive" /root/B29-fixtures.tar.gz || \ ! pre15_scp "$module" /root/B29-erofs-zstdio1.ko || \ ! pre15_scp "$PRE15_CASE_TMP/B29-read-probe.c" /root/B29-read-probe.c || \ ! pre15_scp "$PRE15_CASE_TMP/B29-mount-probe.c" /root/B29-mount-probe.c; then pre15_infra_blocked 'could not transfer B29 module, fixtures, or probes' fi if ! pre15_guest_ssh_bounded \ 'rm -rf /root/B29-fixtures && mkdir /root/B29-fixtures && tar -xzf /root/B29-fixtures.tar.gz -C /root/B29-fixtures && cc -O2 -Wall -Wextra -Werror -o /root/B29-read-probe /root/B29-read-probe.c && cc -O2 -Wall -Wextra -Werror -o /root/B29-mount-probe /root/B29-mount-probe.c'; then pre15_infra_blocked 'could not prepare B29 guest fixtures/probes' fi if pre15_guest_ssh_bounded kldstat -q -m erofs >/dev/null 2>&1; then pre15_infra_blocked 'guest kernel already owns the erofs.1 interface' fi pre15_guest_ssh_bounded \ 'kenv -u vfs.erofs.stream_pool.global_budget >/dev/null 2>&1 || true; kenv -u vfs.erofs.stream_pool.global_contexts >/dev/null 2>&1 || true; kenv -u vfs.erofs.stream_pool.global_cached_per_codec >/dev/null 2>&1 || true' if ! pre15_guest_ssh_bounded kldload /root/B29-erofs-zstdio1.ko \ >"$artifacts/B29-kldload.stdout" 2>"$artifacts/B29-kldload.stderr"; then if grep -Eq 'link_elf|symbol|not defined' "$artifacts/B29-kldload.stderr"; then pre15_infra_blocked 'guest lacks the exact B29 KLD provider ABI' fi pre15_dut_fail 'B29 exact-ABI KLD failed to load' fi pre15_own_guest_kld B29-erofs-zstdio1.ko 'B29 exact-source KLD' attach_mount() { image=$1 label=$2 B29_MD=$(pre15_guest_ssh_bounded mdconfig -a -t vnode \ -f "/root/B29-fixtures/images/$image") || \ pre15_dut_fail "$label md attach failed" case "$B29_MD" in md[0-9]*) ;; *) pre15_runner_fail "unexpected B29 md unit: $B29_MD" ;; esac pre15_own_guest_md "$B29_MD" "$label md" B29_MOUNT=/mnt/pre15-b29-$label pre15_guest_ssh_bounded mkdir -p "$B29_MOUNT" pre15_guest_ssh_bounded mount -t erofs -o ro "/dev/$B29_MD" \ "$B29_MOUNT" || pre15_dut_fail "$label mount failed" pre15_own_guest_mount "$B29_MOUNT" "$label mount" B29_MOUNTS="$B29_MOUNTS $B29_MOUNT" } drop_guest_ownership() { kind=$1 value=$2 temporary=$PRE15_CASE_TMP/B29-ownership.$$ awk -v kind="$kind" -v value="$value" \ 'BEGIN { FS = "\\t" } !($1 == kind && $2 == value)' \ "$PRE15_OWNERSHIP_FILE" >"$temporary" mv "$temporary" "$PRE15_OWNERSHIP_FILE" } unmount_owned() { mountpoint=$1 label=$2 pre15_guest_ssh_bounded umount "$mountpoint" || \ pre15_dut_fail "$label unmount failed" drop_guest_ownership guest-mount "$mountpoint" } unload_owned_kld() { label=$1 if ! pre15_guest_ssh_bounded kldunload B29-erofs-zstdio1.ko; then pre15_dut_fail "$label module unload did not drain cleanly" fi drop_guest_ownership guest-kld B29-erofs-zstdio1.ko } clear_pool_tunables() { pre15_guest_ssh_bounded \ 'kenv -u vfs.erofs.stream_pool.global_budget >/dev/null 2>&1 || true; kenv -u vfs.erofs.stream_pool.global_contexts >/dev/null 2>&1 || true; kenv -u vfs.erofs.stream_pool.global_cached_per_codec >/dev/null 2>&1 || true' } run_exhaustion_phase() { codec=$1 limit_kind=$2 limit_value=$3 phase=$4 clear_pool_tunables case "$limit_kind" in byte) pre15_guest_ssh_bounded kenv \ "vfs.erofs.stream_pool.global_budget=$limit_value" expected_budget=$limit_value expected_contexts=64 ;; count) pre15_guest_ssh_bounded kenv \ "vfs.erofs.stream_pool.global_contexts=$limit_value" expected_budget=134217728 expected_contexts=$limit_value ;; *) pre15_runner_fail "unknown B29 exhaustion limit: $limit_kind" ;; esac pre15_guest_ssh_bounded kenv \ vfs.erofs.stream_pool.global_cached_per_codec=1 if ! pre15_guest_ssh_bounded kldload /root/B29-erofs-zstdio1.ko \ >"$artifacts/B29-kldload-$phase.stdout" \ 2>"$artifacts/B29-kldload-$phase.stderr"; then pre15_dut_fail "B29 $phase constrained-limit KLD reload failed" fi pre15_own_guest_kld B29-erofs-zstdio1.ko "$phase exact-source KLD" pre15_guest_ssh_bounded \ 'sysctl -n vfs.erofs.stream_pool.global_budget; sysctl -n vfs.erofs.stream_pool.global_contexts_limit' \ >"$artifacts/B29-$phase-effective-limits.txt" set -- $(cat "$artifacts/B29-$phase-effective-limits.txt") if test "$1" -ne "$expected_budget" || test "$2" -ne "$expected_contexts"; then pre15_dut_fail "B29 $phase production tunable did not reach its clamped limit" fi runtime_assertions=$((runtime_assertions + 2)) B29_MOUNTS= attach_mount "$codec.erofs" "$phase-owner" B29_OWNER_MOUNT=$B29_MOUNT attach_mount "$codec.erofs" "$phase-b" B29_B_MOUNT=$B29_MOUNT attach_mount "$codec.erofs" "$phase-c" B29_C_MOUNT=$B29_MOUNT pre15_guest_ssh_bounded /root/B29-read-probe pass \ "$B29_OWNER_MOUNT/payload-0.bin" \ /root/B29-fixtures/source/payload-0.bin || \ pre15_dut_fail "B29 $phase global exhaustion owner read failed" runtime_assertions=$((runtime_assertions + 1)) pre15_guest_ssh_bounded \ 'sysctl -n vfs.erofs.stream_pool.contexts; sysctl -n vfs.erofs.stream_pool.idle; sysctl -n vfs.erofs.stream_pool.borrowed; sysctl -n vfs.erofs.stream_pool.resident_bytes; sysctl -n vfs.erofs.stream_pool.exhaustions' \ >"$artifacts/B29-$phase-before-exhaustion.txt" set -- $(cat "$artifacts/B29-$phase-before-exhaustion.txt") before_contexts=$1 before_idle=$2 before_borrowed=$3 before_resident=$4 before_exhaustions=$5 if test "$before_contexts" -ne 1 || test "$before_idle" -ne 1 || \ test "$before_borrowed" -ne 0 || test "$before_resident" -le 256; then pre15_dut_fail "B29 $phase owner did not retain one real idle context" fi if test "$limit_kind" = byte && \ test "$before_resident" -ne "$limit_value"; then pre15_dut_fail "B29 $phase did not consume the exact production byte budget" fi runtime_assertions=$((runtime_assertions + 4)) if ! pre15_guest_ssh_bounded \ "/root/B29-read-probe errno '$B29_B_MOUNT/payload-1.bin' 12 & p1=\$!; /root/B29-read-probe errno '$B29_C_MOUNT/payload-2.bin' 12 & p2=\$!; wait \$p1; r1=\$?; wait \$p2; r2=\$?; test \$r1 -eq 0 -a \$r2 -eq 0"; then pre15_dut_fail "B29 $phase concurrent exhaustion errno was not stable ENOMEM" fi runtime_assertions=$((runtime_assertions + 2)) pre15_guest_ssh_bounded \ 'sysctl -n vfs.erofs.stream_pool.contexts; sysctl -n vfs.erofs.stream_pool.idle; sysctl -n vfs.erofs.stream_pool.borrowed; sysctl -n vfs.erofs.stream_pool.resident_bytes; sysctl -n vfs.erofs.stream_pool.exhaustions' \ >"$artifacts/B29-$phase-after-exhaustion.txt" set -- $(cat "$artifacts/B29-$phase-after-exhaustion.txt") if test "$1" -ne 1 || test "$2" -ne 1 || test "$3" -ne 0 || \ test "$4" -ne "$before_resident" || \ test "$5" -lt $((before_exhaustions + 2)); then pre15_dut_fail "B29 $phase exhaustion changed ownership or missed accounting" fi runtime_assertions=$((runtime_assertions + 5)) unmount_owned "$B29_OWNER_MOUNT" "B29 $phase owner" pre15_guest_ssh_bounded \ 'sysctl -n vfs.erofs.stream_pool.contexts; sysctl -n vfs.erofs.stream_pool.resident_bytes' \ >"$artifacts/B29-$phase-after-owner-unmount.txt" set -- $(cat "$artifacts/B29-$phase-after-owner-unmount.txt") if test "$1" -ne 0 || test "$2" -ne 0; then pre15_dut_fail "B29 $phase owner unmount did not release global resources" fi runtime_assertions=$((runtime_assertions + 2)) pre15_guest_ssh_bounded /root/B29-read-probe pass \ "$B29_B_MOUNT/payload-1.bin" \ /root/B29-fixtures/source/payload-1.bin || \ pre15_dut_fail "B29 $phase read did not recover after resource release" runtime_assertions=$((runtime_assertions + 1)) unmount_owned "$B29_B_MOUNT" "B29 $phase recovery B" unmount_owned "$B29_C_MOUNT" "B29 $phase recovery C" pre15_guest_ssh_bounded \ 'sysctl -n vfs.erofs.stream_pool.contexts; sysctl -n vfs.erofs.stream_pool.resident_bytes; sysctl -n vfs.erofs.stream_pool.borrowed; sysctl -n vfs.erofs.stream_pool.idle' \ >"$artifacts/B29-$phase-final.txt" set -- $(cat "$artifacts/B29-$phase-final.txt") if test "$1" -ne 0 || test "$2" -ne 0 || test "$3" -ne 0 || \ test "$4" -ne 0; then pre15_dut_fail "B29 $phase final pool state is not fully drained" fi runtime_assertions=$((runtime_assertions + 4)) unload_owned_kld "B29 $phase final" runtime_assertions=$((runtime_assertions + 1)) } runtime_assertions=0 B29_LZMA_CONTEXT_BYTES= B29_ZSTD_CONTEXT_BYTES= B29_MOUNTS= for codec in lzma deflate zstd; do attach_mount "$codec.erofs" "$codec" for index in 0 1 2; do pre15_guest_ssh_bounded /root/B29-read-probe pass \ "$B29_MOUNT/payload-$index.bin" \ "/root/B29-fixtures/source/payload-$index.bin" || \ pre15_dut_fail "$codec ordinary payload-$index read failed" runtime_assertions=$((runtime_assertions + 1)) done case "$codec" in lzma|zstd) pre15_guest_ssh_bounded \ 'sysctl -n vfs.erofs.stream_pool.contexts; sysctl -n vfs.erofs.stream_pool.idle; sysctl -n vfs.erofs.stream_pool.resident_bytes' \ >"$artifacts/B29-$codec-context-bytes.txt" set -- $(cat "$artifacts/B29-$codec-context-bytes.txt") if test "$1" -ne 1 || test "$2" -ne 1 || test "$3" -le 256; then pre15_dut_fail "B29 $codec did not retain one charged production context" fi case "$codec" in lzma) B29_LZMA_CONTEXT_BYTES=$3 ;; zstd) B29_ZSTD_CONTEXT_BYTES=$3 ;; esac runtime_assertions=$((runtime_assertions + 3)) ;; esac unmount_owned "$B29_MOUNT" "B29 $codec ordinary" done for codec in lzma-max zstd-max; do attach_mount "$codec.erofs" "$codec" pre15_guest_ssh_bounded /root/B29-read-probe pass \ "$B29_MOUNT/payload-0.bin" \ /root/B29-fixtures/source/payload-0.bin || \ pre15_dut_fail "$codec maximum-parameter workload failed" runtime_assertions=$((runtime_assertions + 1)) unmount_owned "$B29_MOUNT" "B29 $codec maximum-parameter" done for codec in lzma zstd; do B29_BAD_MD=$(pre15_guest_ssh_bounded mdconfig -a -t vnode \ -f "/root/B29-fixtures/images/$codec-max-over.erofs") || \ pre15_dut_fail "$codec over-limit md attach failed" pre15_own_guest_md "$B29_BAD_MD" "$codec over-limit md" B29_BAD_MOUNT=/mnt/pre15-b29-$codec-over pre15_guest_ssh_bounded mkdir -p "$B29_BAD_MOUNT" pre15_guest_ssh_bounded /root/B29-mount-probe "/dev/$B29_BAD_MD" \ "$B29_BAD_MOUNT" 45 || \ pre15_dut_fail "$codec over-limit config did not return EOPNOTSUPP" runtime_assertions=$((runtime_assertions + 1)) done pre15_guest_ssh_bounded \ 'sysctl -n vfs.erofs.stream_pool.creations; sysctl -n vfs.erofs.stream_pool.reuses' \ >"$artifacts/B29-pool-legal-metrics.txt" set -- $(cat "$artifacts/B29-pool-legal-metrics.txt") if test "$1" -lt 5 || test "$2" -lt 6; then pre15_dut_fail 'B29 legal workloads did not create and reuse real contexts' fi runtime_assertions=$((runtime_assertions + 2)) pre15_guest_ssh_bounded \ 'sysctl -n vfs.erofs.stream_pool.contexts; sysctl -n vfs.erofs.stream_pool.resident_bytes' \ >"$artifacts/B29-pool-after-legal-unmount.txt" set -- $(cat "$artifacts/B29-pool-after-legal-unmount.txt") if test "$1" -ne 0 || test "$2" -ne 0; then pre15_dut_fail 'B29 legal workload unmount did not drain all contexts' fi runtime_assertions=$((runtime_assertions + 2)) unload_owned_kld 'B29 first' runtime_assertions=$((runtime_assertions + 1)) test -n "$B29_LZMA_CONTEXT_BYTES" || \ pre15_runner_fail 'B29 LZMA production context size was not recorded' test -n "$B29_ZSTD_CONTEXT_BYTES" || \ pre15_runner_fail 'B29 Zstd production context size was not recorded' run_exhaustion_phase lzma byte "$B29_LZMA_CONTEXT_BYTES" lzma-byte run_exhaustion_phase zstd byte "$B29_ZSTD_CONTEXT_BYTES" zstd-byte run_exhaustion_phase lzma count 1 global-count clear_pool_tunables if test "$runtime_assertions" -ne 90; then pre15_runner_fail "B29 QEMU assertion count mismatch: $runtime_assertions" fi pre15_guest_ssh_bounded dmesg >"$artifacts/B29-dmesg.txt" pre15_target_reached printf '%s\n' \ 'B29 FreeBSD 15 QEMU context-pool PASS' \ 'ordinary=9 charged-codec-state=6 maximum-parameter=2 over-limit=2 lifecycle=5 constrained-phases=66 total=90' \ 'LZMA-byte, Zstd-byte, and global-count cross-mount exhaustion returned ENOMEM=12; each owner drain reached zero and each retry succeeded; full feature suite NOT_RUN'