From c63d70d535be7cf4b8c0a5739a7e623e51d2ebf0 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Thu, 24 Sep 2026 17:50:28 +0100 Subject: Verify paged document app under 4 KiB AArch64 kernel --- .github/workflows/native-linux-4k-page-store.yml | 4 + .github/workflows/native-macos-arm64.yml | 4 + CHECKPOINT.md | 4 + README.md | 9 +- docs/research/071-logical-page-portability.md | 3 + docs/research/072-qemu-4k-page-store.md | 30 +++++ prototypes/connections/README.md | 5 +- prototypes/connections/SOURCE_PREVIEW.md | 3 + .../2026-09-24-logical-page/qemu-4k-repeat.json | 23 ++++ .../results/2026-09-24-logical-page/qemu-4k.json | 23 ++++ .../results/2026-09-24-logical-page/qemu-4k.log.gz | Bin 0 -> 250 bytes .../2026-09-24-qemu-4k/pure-source-preview.tar.gz | Bin 0 -> 338483 bytes .../2026-09-24-qemu-4k/source-package-check.json | 15 +++ prototypes/connections/test-paged-store.c | 4 +- .../007-page-size-compatibility/guest.c | 63 ++++++++++ .../007-page-size-compatibility/run_qemu_4k.py | 133 +++++++++++++++++++++ 16 files changed, 316 insertions(+), 7 deletions(-) create mode 100644 docs/research/072-qemu-4k-page-store.md create mode 100644 prototypes/connections/results/2026-09-24-logical-page/qemu-4k-repeat.json create mode 100644 prototypes/connections/results/2026-09-24-logical-page/qemu-4k.json create mode 100644 prototypes/connections/results/2026-09-24-logical-page/qemu-4k.log.gz create mode 100644 prototypes/connections/results/2026-09-24-qemu-4k/pure-source-preview.tar.gz create mode 100644 prototypes/connections/results/2026-09-24-qemu-4k/source-package-check.json create mode 100644 research/experiments/007-page-size-compatibility/guest.c create mode 100644 research/experiments/007-page-size-compatibility/run_qemu_4k.py diff --git a/.github/workflows/native-linux-4k-page-store.yml b/.github/workflows/native-linux-4k-page-store.yml index c559e46..e5ebe8a 100644 --- a/.github/workflows/native-linux-4k-page-store.yml +++ b/.github/workflows/native-linux-4k-page-store.yml @@ -11,6 +11,10 @@ jobs: timeout-minutes: 20 steps: - uses: actions/checkout@v4 + - uses: actions/setup-node@v7 + with: + node-version: '24' + package-manager-cache: false - name: Require a real 4 KiB OS page run: test "$(getconf PAGESIZE)" = 4096 - name: Exercise the selected 16 KiB logical-page store diff --git a/.github/workflows/native-macos-arm64.yml b/.github/workflows/native-macos-arm64.yml index c3614be..f80491f 100644 --- a/.github/workflows/native-macos-arm64.yml +++ b/.github/workflows/native-macos-arm64.yml @@ -11,6 +11,10 @@ jobs: timeout-minutes: 20 steps: - uses: actions/checkout@v4 + - uses: actions/setup-node@v7 + with: + node-version: '24' + package-manager-cache: false - name: Confirm native Apple Silicon runner run: | test "$(uname -s)" = Darwin diff --git a/CHECKPOINT.md b/CHECKPOINT.md index a5692c9..86991cf 100644 --- a/CHECKPOINT.md +++ b/CHECKPOINT.md @@ -231,6 +231,10 @@ TUI의 Ctrl+S부터 저장 완료 표시까지 확인했다. 1 MiB 마지막 1 B 나누면 초기화를 허용하도록 좁게 바꿨다. 현재 16 KiB Linux의 실제 문서· TUI·형식 내보내기는 통과했다. 4 KiB Linux와 macOS의 CI는 작성만 했고 실행 결과는 아직 없다. + [072](docs/research/072-qemu-4k-page-store.md)에서는 실제 4 KiB AArch64 + Linux 커널을 QEMU에서 부팅해 선택형 저장소 단언과 1 MiB 고정 정의 CLI의 + 생성·수정·충돌을 통과했다. tmpfs 게스트이므로 물리 디스크 내구성·지연이나 + macOS 결과까지 증명하지 않는다. 3. **실제 네이티브 앱과 통신.** 작성·편집·저장·복원·충돌 처리를 연결하고 자체 통신 형식과 검증된 보안 전송을 붙인다. 외부 호환 입력·출력도 유지한다. 4. **규모·실패·사용성 확인과 공개 자료.** 큰 작업물·느린 소비자·혼합 작업에서 구조가 diff --git a/README.md b/README.md index 6813d25..75698e6 100644 --- a/README.md +++ b/README.md @@ -61,10 +61,11 @@ source TUI adds 4,958 B linked `text`; its [CLI+TUI archive](prototypes/connecti is 57,788 B compressed and passed a relocated app check. Large documents use an incompatible on-disk page format, so this remains an opt-in Linux storage research profile. Its 16 KiB logical pages now admit OS page sizes that divide -16 KiB, but only the 16 KiB host has passed the real storage suite so far; -the default core and storage path are unchanged. The -[alignment and verification boundary](docs/research/071-logical-page-portability.md) -records the 32 B selected-code cost and the unrun 4 KiB/macOS checks. +16 KiB. The 16 KiB host passed the full selected suite, and a +[real 4 KiB QEMU guest](docs/research/072-qemu-4k-page-store.md) passed the +page-store assertions plus a 1 MiB native CLI edit and stale-revision check. +Physical-disk durability, Linux x86_64 CI and macOS remain unverified; the +default core and storage path are unchanged. The current execution profile has separate `core`, `fixed-editor`, `editor`, and `server` build targets. `make all` produces the core and the parser-free CLI diff --git a/docs/research/071-logical-page-portability.md b/docs/research/071-logical-page-portability.md index a664432..d57acaa 100644 --- a/docs/research/071-logical-page-portability.md +++ b/docs/research/071-logical-page-portability.md @@ -34,3 +34,6 @@ Linux AArch64에서 선택형 고정 CLI/HTTP의 링크 `text`는 각각 빌드·매핑·실제 문서 수정이 통과하기 전에는 이 변경을 검증된 플랫폼 지원으로 표기하지 않는다. 현재 저장소는 선택형이고, 큰 문서의 온디스크 형식은 기본 저장소와 호환되지 않아 [오프라인 내보내기](062-paged-store-rollback.md)가 필요하다. + +후속 [072](072-qemu-4k-page-store.md)에서 실제 4 KiB AArch64 게스트의 +저장소·1 MiB CLI 작업을 확인했다. 위 미실행 상태는 이 단계 당시의 기록이다. diff --git a/docs/research/072-qemu-4k-page-store.md b/docs/research/072-qemu-4k-page-store.md new file mode 100644 index 0000000..09b5548 --- /dev/null +++ b/docs/research/072-qemu-4k-page-store.md @@ -0,0 +1,30 @@ +# 072 — 실제 4 KiB 커널에서 페이지 저장소와 앱 실행 + +2026-09-24. [071](071-logical-page-portability.md)의 정렬 추론을 실제 +OS 페이지 크기에서 검증했다. 16 KiB AArch64 호스트 위의 QEMU 10.0.13에서 +`CONFIG_ARM64_4K_PAGES=y`인 Linux 6.18.39 AArch64 커널을 부팅했다. +게스트의 `sysconf(_SC_PAGESIZE)`는 **4,096 B**였다. 커널·설정·정적 게스트 +실행물의 SHA-256은 [실행 기록](../../prototypes/connections/results/2026-09-24-logical-page/qemu-4k.json)에 +남겼다. [압축된 원시 콘솔 로그](../../prototypes/connections/results/2026-09-24-logical-page/qemu-4k.log.gz)는 +결과와 정상 전원 종료를 보여준다. + +게스트에서 기존 페이지 저장소 검사는 스냅샷, 범위 매핑, 충돌, 고장 주입, +재구성, 인라인/페이지 전환과 128회 혼합 수정을 통과했다. 이어 실제 +고정 정의 `editor.pure` CLI가 1 MiB 문서를 생성하고 전체 바이트를 +다시 읽어 확인했다. 마지막 1 B 교체 뒤 리비전 2와 전체 본문을 확인했고, +리비전 1의 재요청은 충돌로 거절돼 본문이 유지됐다. +[두 번째 독립 실행](../../prototypes/connections/results/2026-09-24-logical-page/qemu-4k-repeat.json)에서도 +게스트 바이너리와 재현 가능한 initramfs의 SHA-256이 일치했다. +[재현 도구](../../research/experiments/007-page-size-compatibility/run_qemu_4k.py)와 +[게스트 검사 코드](../../research/experiments/007-page-size-compatibility/guest.c)가 +빌드·부팅·마커·해시 검사를 수행한다. + +수정한 저장소 테스트를 포함한 [최신 앱 소스 묶음](../../prototypes/connections/results/2026-09-24-qemu-4k/pure-source-preview.tar.gz)은 +다른 디렉터리에서 빌드하고 기본 릴리스·실제 PTY·선택형 페이지 TUI를 +[다시 검사했다](../../prototypes/connections/results/2026-09-24-qemu-4k/source-package-check.json). +QEMU 재현 코드는 연구 디렉터리에 있으며 이 작은 앱 소스 묶음에는 들어 있지 않다. + +이 게스트의 파일 시스템은 initramfs다. `fsync` 호출과 충돌 계약은 +실행됐지만 **물리 디스크 내구성이나 실제 장치 지연은 검증하지 않았다**. +4 KiB x86_64 CI와 macOS arm64 CI도 아직 미실행이다. 기본 코어·기본 저장 +형식은 바뀌지 않았고, 페이지 저장소는 계속 선택형이다. diff --git a/prototypes/connections/README.md b/prototypes/connections/README.md index 67cccd5..7da7eab 100644 --- a/prototypes/connections/README.md +++ b/prototypes/connections/README.md @@ -146,8 +146,9 @@ python3 prototypes/connections/export_flat_store.py \ 같은 `.pure` 문서 편집기를 선택형 페이지 저장소와 함께 터미널에서 사용하려면 새 저장 디렉터리를 지정한다. 논리 페이지는 16 KiB이며 OS 페이지가 이를 -나눌 때만 초기화한다. 현재 실제 검사는 Linux 16 KiB 환경에서만 통과했고, -4 KiB Linux와 macOS는 CI 실행 결과가 아직 없다. CLI와 TUI를 +나눌 때만 초기화한다. Linux 16 KiB 호스트에서 전체 선택형 검사가 통과했고, +4 KiB QEMU 게스트에서 저장소 단언과 1 MiB 고정 CLI 작업을 통과했다. +Linux x86_64 CI와 macOS 실행 결과는 아직 없다. CLI와 TUI를 함께 담은 [선택형 묶음](results/2026-09-24-native-save/pure-document-hybrid-cli-tui-linux-aarch64.tar.gz)은 다음 빌드로 만들며, 기본 `make all`과 기본 저장 형식은 유지된다. diff --git a/prototypes/connections/SOURCE_PREVIEW.md b/prototypes/connections/SOURCE_PREVIEW.md index a4bbfc3..7e4179e 100644 --- a/prototypes/connections/SOURCE_PREVIEW.md +++ b/prototypes/connections/SOURCE_PREVIEW.md @@ -83,6 +83,9 @@ python3 prototypes/connections/package_native.py \ python3 prototypes/connections/check_package.py /tmp/pure-hybrid-tui.tar.gz ``` +The 4 KiB AArch64 guest has passed the page-store assertions and a 1 MiB +fixed-CLI edit; this archive does not include that QEMU research harness. + Use a new store directory for this profile; large documents use `PUREP001` and need the offline exporter above before the default store can read them. diff --git a/prototypes/connections/results/2026-09-24-logical-page/qemu-4k-repeat.json b/prototypes/connections/results/2026-09-24-logical-page/qemu-4k-repeat.json new file mode 100644 index 0000000..5866d72 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-logical-page/qemu-4k-repeat.json @@ -0,0 +1,23 @@ +{ + "passed": true, + "os_page_bytes": 4096, + "checks": [ + "128 mixed and fault-injected page-store edits", + "real prepared CLI creates and rereads every byte of a 1 MiB document", + "one-byte last-position revision edit and stale-revision rejection" + ], + "guest_shutdown": "power down", + "qemu_exit_code": 0, + "host": { + "system": "Linux", + "machine": "aarch64", + "page_bytes": 16384 + }, + "kernel_sha256": "fc341c15f1df9727f8dff25dc5acbbd872cc58ccf5d035a47ea8f6cecb07c5d8", + "config_sha256": "eb5d806796ea79dc73f73cce9ca3146fbd54318f3a3b0cd5b70a500d488e6114", + "guest_binary_sha256": "b8570d4f48e8b9bc5c4244af76d1d0f8854ef9e39e4487eb0f3ca78d6fc98ae5", + "initramfs_sha256": "e318b16208503a8779c9ab88eaaf5f8b3e3af030d5f8c646f153c5ec1ed1140e", + "log_sha256": "a22ba804cac91ba76a1e5f018f41026be74cca10b17600b1ab301b3d3c8fdd88", + "compressed_log_sha256": "d766f0fbd4069c047d01b7bdab184ccdd7cb748061494bb1d714c60fbf484f68", + "qemu_version": "QEMU emulator version 10.0.13 (Debian 1:10.0.13+ds-0+deb13u1)" +} diff --git a/prototypes/connections/results/2026-09-24-logical-page/qemu-4k.json b/prototypes/connections/results/2026-09-24-logical-page/qemu-4k.json new file mode 100644 index 0000000..ebb1c0b --- /dev/null +++ b/prototypes/connections/results/2026-09-24-logical-page/qemu-4k.json @@ -0,0 +1,23 @@ +{ + "passed": true, + "os_page_bytes": 4096, + "checks": [ + "128 mixed and fault-injected page-store edits", + "real prepared CLI creates and rereads every byte of a 1 MiB document", + "one-byte last-position revision edit and stale-revision rejection" + ], + "guest_shutdown": "power down", + "qemu_exit_code": 0, + "host": { + "system": "Linux", + "machine": "aarch64", + "page_bytes": 16384 + }, + "kernel_sha256": "fc341c15f1df9727f8dff25dc5acbbd872cc58ccf5d035a47ea8f6cecb07c5d8", + "config_sha256": "eb5d806796ea79dc73f73cce9ca3146fbd54318f3a3b0cd5b70a500d488e6114", + "guest_binary_sha256": "b8570d4f48e8b9bc5c4244af76d1d0f8854ef9e39e4487eb0f3ca78d6fc98ae5", + "initramfs_sha256": "e318b16208503a8779c9ab88eaaf5f8b3e3af030d5f8c646f153c5ec1ed1140e", + "log_sha256": "894c401f1e888dfe6be360b8903fc384008ac4751758ca9aa52d5f9ba0efefcf", + "compressed_log_sha256": "128e5d54880b7f5b2322f94282dfad8aa949d7646e5066cb70d86088cdfd1ea4", + "qemu_version": "QEMU emulator version 10.0.13 (Debian 1:10.0.13+ds-0+deb13u1)" +} diff --git a/prototypes/connections/results/2026-09-24-logical-page/qemu-4k.log.gz b/prototypes/connections/results/2026-09-24-logical-page/qemu-4k.log.gz new file mode 100644 index 0000000..b01be34 Binary files /dev/null and b/prototypes/connections/results/2026-09-24-logical-page/qemu-4k.log.gz differ diff --git a/prototypes/connections/results/2026-09-24-qemu-4k/pure-source-preview.tar.gz b/prototypes/connections/results/2026-09-24-qemu-4k/pure-source-preview.tar.gz new file mode 100644 index 0000000..10051cd Binary files /dev/null and b/prototypes/connections/results/2026-09-24-qemu-4k/pure-source-preview.tar.gz differ diff --git a/prototypes/connections/results/2026-09-24-qemu-4k/source-package-check.json b/prototypes/connections/results/2026-09-24-qemu-4k/source-package-check.json new file mode 100644 index 0000000..83b4063 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-qemu-4k/source-package-check.json @@ -0,0 +1,15 @@ +{ + "archive_sha256": "8de4a88acc0ecb6dd28cd569eb2ad1f6a88867898d86754ae2779f9adf771227", + "archive_bytes": 338483, + "source_files": 173, + "checks": [ + "every extracted source file matches the manifest", + "clean default build contains only the core, parser-free CLI and its build-time compiler", + "clean C17, both real terminal PTY scenarios and fixed/source CLI document RSS checks pass", + "source-loaded CLI and parser-free CLI share the real stored document", + "editing .pure changes the native source-loaded reply", + "clean selected page store runs the source-loaded TUI and relocatable CLI+TUI package", + "clean Linux release behavior, code, dependency and RSS gates pass", + "clean source-loaded terminal editor PTY scenarios pass" + ] +} diff --git a/prototypes/connections/test-paged-store.c b/prototypes/connections/test-paged-store.c index aed0539..aa867c6 100644 --- a/prototypes/connections/test-paged-store.c +++ b/prototypes/connections/test-paged-store.c @@ -165,7 +165,8 @@ static void mixed_edits(const char *path) { } int main(void) { - assert(sysconf(_SC_PAGESIZE) == PAGE); + long host_page = sysconf(_SC_PAGESIZE); + assert(host_page > 0 && PAGE % host_page == 0); char base[] = "/tmp/pure-page-store-XXXXXX"; assert(mkdtemp(base)); char path[256]; @@ -267,4 +268,5 @@ int main(void) { } assert(closedir(directory) == 0 && rmdir(path) == 0 && rmdir(base) == 0); puts("{\"hybrid_storage\":\"snapshot-patch-range-compaction-crash-conflict-uncertain-inline-transitions\",\"mixed_edits\":128}"); + return 0; } diff --git a/research/experiments/007-page-size-compatibility/guest.c b/research/experiments/007-page-size-compatibility/guest.c new file mode 100644 index 0000000..0599bc1 --- /dev/null +++ b/research/experiments/007-page-size-compatibility/guest.c @@ -0,0 +1,63 @@ +/* PID 1 in the 4 KiB AArch64 test initramfs. Keep the storage assertions and + * the real prepared document CLI in the same guest with no host file mapping. */ +#define main paged_store_test_main +#include "../../../prototypes/connections/test-paged-store.c" +#undef main +#include + +int pure_edit_main(int argc, char **argv); + +static void check_native_app(void) { + const char *store = "/tmp/pure-4k-app"; + const char *input = "/tmp/pure-4k-input"; + const char *replacement = "/tmp/pure-4k-replacement"; + FILE *file = fopen(input, "wb"); + assert(file); + unsigned char block[4096]; + memset(block, 'A', sizeof(block)); + for (unsigned i = 0; i < 256; ++i) + assert(fwrite(block, 1, sizeof(block), file) == sizeof(block)); + assert(fclose(file) == 0); + + char *first[] = {"pure-edit-fixed", "--store", (char *)store, "edit", + "note", "0", "0", "0", (char *)input}; + assert(pure_edit_main(9, first) == 0); + PureStoredView view; + assert(pure_store_get(store, "note", 1048576, &view) == PURE_OK); + assert(view.revision == 1 && view.body.size == 1048576); + for (size_t i = 0; i < view.body.size; ++i) assert(view.body.data[i] == 'A'); + pure_store_release(&view); + + file = fopen(replacement, "wb"); + assert(file && fputc('Z', file) == 'Z' && fclose(file) == 0); + char *second[] = {"pure-edit-fixed", "--store", (char *)store, "edit", + "note", "1", "1048575", "1", (char *)replacement}; + assert(pure_edit_main(9, second) == 0); + assert(pure_store_get(store, "note", 1048576, &view) == PURE_OK); + assert(view.revision == 2 && view.body.size == 1048576); + for (size_t i = 0; i < view.body.size - 1; ++i) assert(view.body.data[i] == 'A'); + assert(view.body.data[view.body.size - 1] == 'Z'); + pure_store_release(&view); + + assert(pure_edit_main(9, second) == 3); + assert(pure_store_get(store, "note", 1048576, &view) == PURE_OK); + assert(view.revision == 2 && view.body.data[view.body.size - 1] == 'Z'); + pure_store_release(&view); +} + +int main(void) { + long os_page = sysconf(_SC_PAGESIZE); + printf("PURE_GUEST_OS_PAGE=%ld\n", os_page); + fflush(stdout); + if (os_page != 4096) return 2; + int result = paged_store_test_main(); + printf("PURE_4K_STORE_RESULT=%d\n", result); + fflush(stdout); + if (!result) { + check_native_app(); + puts("PURE_4K_APP_RESULT=0"); + fflush(stdout); + reboot(RB_POWER_OFF); + } + return result; +} diff --git a/research/experiments/007-page-size-compatibility/run_qemu_4k.py b/research/experiments/007-page-size-compatibility/run_qemu_4k.py new file mode 100644 index 0000000..2a5b36e --- /dev/null +++ b/research/experiments/007-page-size-compatibility/run_qemu_4k.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +"""Run the selected page store and the real document CLI under a 4 KiB kernel.""" +import argparse +import gzip +import hashlib +import json +import os +from pathlib import Path +import platform +import subprocess +import tempfile + + +ROOT = Path(__file__).resolve().parents[3] +CONNECTIONS = ROOT / 'prototypes/connections' +GUEST = Path(__file__).with_name('guest.c') + + +def sha256(path): + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def run(command, *, cwd=None, data=None, timeout=120): + result = subprocess.run(command, cwd=cwd, input=data, capture_output=True, + timeout=timeout) + if result.returncode: + raise RuntimeError(f'{command[0]} failed ({result.returncode}): ' + f'{result.stdout[-1000:]!r} {result.stderr[-1000:]!r}') + return result + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--kernel', type=Path, required=True, + help='gzip-compressed or raw AArch64 4 KiB Linux kernel') + parser.add_argument('--config', type=Path, required=True, + help='configuration belonging to that kernel') + parser.add_argument('--output', type=Path, required=True) + parser.add_argument('--log', type=Path, required=True) + parser.add_argument('--cc', default='cc') + parser.add_argument('--qemu', default='qemu-system-aarch64') + args = parser.parse_args() + if args.log.suffix != '.gz': + parser.error('--log must be a .gz path') + if platform.system() != 'Linux' or platform.machine() not in ('aarch64', 'arm64'): + parser.error('this runner compiles an AArch64 guest on an AArch64 Linux host') + if 'CONFIG_ARM64_4K_PAGES=y' not in args.config.read_text().splitlines(): + parser.error('the supplied kernel configuration is not a 4 KiB AArch64 build') + if args.output.exists() or args.log.exists(): + parser.error('evidence output already exists') + + with tempfile.TemporaryDirectory(prefix='pure-qemu-4k-') as temporary: + work = Path(temporary) + build = work / 'build' + run(['make', '-C', str(CONNECTIONS), f'BUILD={build}', 'fixed-editor']) + edit = work / 'edit.o' + run([args.cc, '-O2', '-std=c17', '-Wall', '-Wextra', '-Wpedantic', '-Werror', + '-DPURE_EMBEDDED_PROGRAM', '-Dmain=pure_edit_main', f'-I{build}', + '-c', str(CONNECTIONS / 'edit.c'), '-o', str(edit)]) + + rootfs = work / 'rootfs' + rootfs.mkdir() + for name in ('tmp', 'dev'): + (rootfs / name).mkdir() + guest_binary = rootfs / 'init' + run([args.cc, '-O2', '-static', '-std=c17', '-Wall', '-Wextra', + '-Wpedantic', '-Werror', '-DPURE_STORE_TEST', f'-I{CONNECTIONS}', + str(GUEST), str(edit), str(CONNECTIONS / 'store-paged.c'), + str(CONNECTIONS / 'store-port.c'), str(CONNECTIONS / 'evaluate.c'), + str(CONNECTIONS / 'runtime.c'), '-o', str(guest_binary)]) + guest_sha = sha256(guest_binary) + for path in (rootfs, rootfs / 'tmp', rootfs / 'dev', guest_binary): + os.utime(path, (0, 0)) + archive = work / 'initrd.cpio' + entries = b'.\0./tmp\0./dev\0./init\0' + archive.write_bytes(run(['cpio', '--null', '-o', '-H', 'newc', '--owner=0:0', + '--reproducible'], + cwd=rootfs, data=entries).stdout) + kernel_image = work / 'Image' + with args.kernel.open('rb') as source: + compressed_kernel = source.read(2) == b'\x1f\x8b' + if compressed_kernel: + with gzip.open(args.kernel, 'rb') as source, kernel_image.open('wb') as target: + while chunk := source.read(1024 * 1024): + target.write(chunk) + else: + kernel_image.write_bytes(args.kernel.read_bytes()) + + command = [args.qemu, '-machine', 'virt', '-cpu', 'cortex-a72', + '-smp', '1', '-m', '256M', '-nographic', '-no-reboot', + '-nic', 'none', '-kernel', str(kernel_image), '-initrd', str(archive), + '-append', 'console=ttyAMA0 loglevel=4 rdinit=/init panic=-1'] + guest_run = subprocess.run(command, capture_output=True, timeout=90) + log_bytes = guest_run.stdout + guest_run.stderr + args.log.parent.mkdir(parents=True, exist_ok=True) + with args.log.open('xb') as destination: + with gzip.GzipFile(fileobj=destination, mode='wb', filename='', + mtime=0, compresslevel=9) as compressed: + compressed.write(log_bytes) + log = log_bytes.decode(errors='replace').replace('\r\n', '\n') + required = ('PURE_GUEST_OS_PAGE=4096', 'PURE_4K_STORE_RESULT=0', + 'PURE_4K_APP_RESULT=0', '"mixed_edits":128', + '{"revision":"1","reply_bytes":1048576}', + '{"revision":"2","reply_bytes":1}', + '{"error":"conflict","revision":"2"}', + 'reboot: Power down') + missing = [marker for marker in required if marker not in log] + if guest_run.returncode or missing or 'Kernel panic' in log: + raise AssertionError(f'guest exit {guest_run.returncode}, missing {missing}; log: {args.log}') + + evidence = { + 'passed': True, 'os_page_bytes': 4096, + 'checks': ['128 mixed and fault-injected page-store edits', + 'real prepared CLI creates and rereads every byte of a 1 MiB document', + 'one-byte last-position revision edit and stale-revision rejection'], + 'guest_shutdown': 'power down', 'qemu_exit_code': guest_run.returncode, + 'host': {'system': platform.system(), 'machine': platform.machine(), + 'page_bytes': os.sysconf('SC_PAGESIZE')}, + 'kernel_sha256': sha256(args.kernel), 'config_sha256': sha256(args.config), + 'guest_binary_sha256': guest_sha, 'initramfs_sha256': sha256(archive), + 'log_sha256': hashlib.sha256(log_bytes).hexdigest(), + 'compressed_log_sha256': sha256(args.log), + 'qemu_version': run([args.qemu, '--version']).stdout.decode().splitlines()[0], + } + args.output.parent.mkdir(parents=True, exist_ok=True) + with args.output.open('x') as destination: + json.dump(evidence, destination, indent=2) + destination.write('\n') + print(json.dumps(evidence, indent=2)) + + +if __name__ == '__main__': + main() -- cgit v1.2.3