605 lines
21 KiB
Bash
Executable File
605 lines
21 KiB
Bash
Executable File
#!/bin/sh
|
|
set -eu
|
|
|
|
: "${PRE15_DUT:?PRE15_DUT is required}"
|
|
: "${PRE15_ROOT:?PRE15_ROOT is required}"
|
|
: "${PRE15_RUN_DIR:?PRE15_RUN_DIR is required}"
|
|
: "${PRE15_LIB_DIR:?PRE15_LIB_DIR is required}"
|
|
. "$PRE15_LIB_DIR/runner.sh"
|
|
|
|
baseline=08a567554881f19bcec933a92a46d18cfb73d22e
|
|
artifacts=$PRE15_RUN_DIR/artifacts
|
|
compact_fixture=$PRE15_DUT/tests/pre15/fixtures/B13-compact-time.json
|
|
extended_fixture=$PRE15_DUT/tests/pre15/fixtures/B13-extended-time.json
|
|
inode=$PRE15_DUT/src/inode.c
|
|
internal=$PRE15_DUT/src/internal.h
|
|
super=$PRE15_DUT/src/super.c
|
|
ondisk=$PRE15_DUT/src/erofs_fs.h
|
|
vnops=$PRE15_DUT/src/erofs_vnops.c
|
|
|
|
for tool in cc git python3 sha256sum; do
|
|
command -v "$tool" >/dev/null 2>&1 || \
|
|
pre15_infra_blocked "missing B13 host tool: $tool"
|
|
done
|
|
|
|
pre15_record_fixture b13-compact "$compact_fixture"
|
|
pre15_record_fixture b13-extended "$extended_fixture"
|
|
pre15_record_fixture b13-inode "$inode"
|
|
pre15_record_fixture b13-internal "$internal"
|
|
pre15_record_fixture b13-super "$super"
|
|
pre15_record_fixture b13-ondisk "$ondisk"
|
|
pre15_record_fixture b13-vnops "$vnops"
|
|
pre15_record_fixture b13-case "$PRE15_DUT/tests/pre15/cases/B13-time.sh"
|
|
|
|
mkdir -p "$artifacts"
|
|
pre15_target_reached
|
|
|
|
if python3 - "$PRE15_ROOT" "$PRE15_DUT" "$baseline" "$compact_fixture" \
|
|
"$extended_fixture" "$artifacts" <<'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])
|
|
baseline = sys.argv[3]
|
|
compact_path = Path(sys.argv[4])
|
|
extended_path = Path(sys.argv[5])
|
|
artifacts = Path(sys.argv[6])
|
|
src = dut / "src"
|
|
eintegrity = 97
|
|
int64_min = -(1 << 63)
|
|
int64_max = (1 << 63) - 1
|
|
int32_min = -(1 << 31)
|
|
int32_max = (1 << 31) - 1
|
|
|
|
|
|
def committed(path: str) -> str:
|
|
completed = subprocess.run(
|
|
["git", "-C", str(root), "show", f"{baseline}:repo-pre-15/src/{path}"],
|
|
check=False,
|
|
text=True,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
)
|
|
if completed.returncode != 0:
|
|
raise SystemExit(f"cannot read B13 baseline {path}: {completed.stderr}")
|
|
return completed.stdout
|
|
|
|
|
|
def replace_once(source: str, old: str, new: str, label: str) -> str:
|
|
count = source.count(old)
|
|
if count != 1:
|
|
raise SystemExit(f"{label}: expected one transform source, found {count}")
|
|
return source.replace(old, new, 1)
|
|
|
|
|
|
def extract_function(source: str, name: str) -> str:
|
|
match = re.search(r"^" + re.escape(name) + r"\s*\(", source, re.MULTILINE)
|
|
if not match:
|
|
raise SystemExit(f"missing function: {name}")
|
|
name_line = source.rfind("\n", 0, match.start()) + 1
|
|
start = source.rfind("\n", 0, name_line - 1) + 1
|
|
brace = source.find("{", match.end())
|
|
if brace < 0:
|
|
raise SystemExit(f"missing function body: {name}")
|
|
depth = 0
|
|
state = "code"
|
|
index = brace
|
|
while index < len(source):
|
|
char = source[index]
|
|
following = source[index + 1] if index + 1 < len(source) else ""
|
|
if state == "code":
|
|
if char == "/" and following == "*":
|
|
state = "block"
|
|
index += 2
|
|
continue
|
|
if char == "/" and following == "/":
|
|
state = "line"
|
|
index += 2
|
|
continue
|
|
if char == '"':
|
|
state = "string"
|
|
elif char == "'":
|
|
state = "character"
|
|
elif char == "{":
|
|
depth += 1
|
|
elif char == "}":
|
|
depth -= 1
|
|
if depth == 0:
|
|
return source[start : index + 1]
|
|
elif state == "block" and char == "*" and following == "/":
|
|
state = "code"
|
|
index += 2
|
|
continue
|
|
elif state == "line" and char == "\n":
|
|
state = "code"
|
|
elif state in {"string", "character"}:
|
|
if char == "\\":
|
|
index += 2
|
|
continue
|
|
if (state == "string" and char == '"') or (
|
|
state == "character" and char == "'"
|
|
):
|
|
state = "code"
|
|
index += 1
|
|
raise SystemExit(f"unterminated function: {name}")
|
|
|
|
|
|
def load_fixture(path: Path, expected_encoding: set[str]) -> dict:
|
|
fixture = json.loads(path.read_text(encoding="ascii"))
|
|
if fixture.get("schema") != 1 or fixture.get("candidate") != "P15-018":
|
|
raise SystemExit(f"invalid B13 fixture identity: {path.name}")
|
|
if set(fixture.get("encoding", {})) != expected_encoding:
|
|
raise SystemExit(f"invalid B13 fixture encoding keys: {path.name}")
|
|
if not isinstance(fixture.get("cases"), list) or not fixture["cases"]:
|
|
raise SystemExit(f"empty B13 fixture: {path.name}")
|
|
return fixture
|
|
|
|
|
|
def decode_le(raw: str, width: int, *, signed: bool) -> int:
|
|
if not re.fullmatch(r"[0-9a-f]+", raw) or len(raw) != width * 2:
|
|
raise SystemExit(f"invalid {width}-byte lowercase hex value: {raw!r}")
|
|
return int.from_bytes(bytes.fromhex(raw), byteorder="little", signed=signed)
|
|
|
|
|
|
def status_for_range(value: int, minimum: int, maximum: int) -> str:
|
|
return "PASS" if minimum <= value <= maximum else "EINTEGRITY"
|
|
|
|
|
|
current = {
|
|
name: (src / name).read_text(encoding="utf-8")
|
|
for name in ("internal.h", "inode.c", "super.c", "erofs_fs.h", "erofs_vnops.c")
|
|
}
|
|
base = {name: committed(name) for name in current}
|
|
|
|
expected_internal = replace_once(
|
|
base["internal.h"], "\tuint64_t epoch;", "\tint64_t epoch;", "signed epoch field"
|
|
)
|
|
expected_internal = replace_once(
|
|
expected_internal, "\tuint64_t mtime;", "\ttime_t mtime;", "time_t inode field"
|
|
)
|
|
if current["internal.h"] != expected_internal:
|
|
raise SystemExit("internal.h differs from the two declared B13 type changes")
|
|
|
|
old_set_timestamp = """static int
|
|
erofs_set_timestamp(struct erofs_inode *vi, uint64_t seconds,
|
|
uint32_t nanoseconds)
|
|
{
|
|
\tif (nanoseconds >= 1000000000 || seconds > (uint64_t)INT64_MAX)
|
|
\t\treturn (EINTEGRITY);
|
|
\tvi->mtime = seconds;
|
|
\tvi->mtime_nsec = nanoseconds;
|
|
\treturn (0);
|
|
}
|
|
"""
|
|
new_set_timestamp = """static int
|
|
erofs_set_timestamp(struct erofs_inode *vi, int64_t seconds,
|
|
uint32_t nanoseconds)
|
|
{
|
|
\ttime_t mtime;
|
|
|
|
\tif (nanoseconds >= 1000000000 ||
|
|
\t __builtin_add_overflow(seconds, 0, &mtime))
|
|
\t\treturn (EINTEGRITY);
|
|
\tvi->mtime = mtime;
|
|
\tvi->mtime_nsec = nanoseconds;
|
|
\treturn (0);
|
|
}
|
|
"""
|
|
expected_inode = replace_once(
|
|
base["inode.c"], old_set_timestamp, new_set_timestamp, "timestamp conversion"
|
|
)
|
|
expected_inode = replace_once(
|
|
expected_inode,
|
|
"\tuint64_t addrmask, mtime;",
|
|
"\tuint64_t addrmask;\n\tint64_t mtime;",
|
|
"signed decoded mtime",
|
|
)
|
|
expected_inode = replace_once(
|
|
expected_inode,
|
|
"\t\t (uint64_t)le32toh(dic->i_mtime), &mtime)",
|
|
"\t\t (int64_t)le32toh(dic->i_mtime), &mtime)",
|
|
"compact signed checked add",
|
|
)
|
|
expected_inode = replace_once(
|
|
expected_inode,
|
|
"\t\terror = erofs_set_timestamp(vi, le64toh(die->i_mtime),\n"
|
|
"\t\t le32toh(die->i_mtime_nsec));",
|
|
"\t\terror = erofs_set_timestamp(vi,\n"
|
|
"\t\t (int64_t)le64toh(die->i_mtime),\n"
|
|
"\t\t le32toh(die->i_mtime_nsec));",
|
|
"extended signed decode",
|
|
)
|
|
if current["inode.c"] != expected_inode:
|
|
raise SystemExit("inode.c differs from the four declared B13 transforms")
|
|
|
|
expected_super = replace_once(
|
|
base["super.c"],
|
|
"\tsbi->epoch = le64toh(dsb->epoch);",
|
|
"\tsbi->epoch = (int64_t)le64toh(dsb->epoch);",
|
|
"super signed epoch decode",
|
|
)
|
|
if current["super.c"] != expected_super:
|
|
raise SystemExit("super.c differs from the exact signed epoch transform")
|
|
if current["erofs_fs.h"] != base["erofs_fs.h"]:
|
|
raise SystemExit("B13 changed ondisk endian types, widths, or layout")
|
|
if current["erofs_vnops.c"] != base["erofs_vnops.c"]:
|
|
raise SystemExit("B13 changed FreeBSD VOP/timespec publication code")
|
|
|
|
ondisk_required = (
|
|
"\t__le64 epoch;",
|
|
"\t__le32 i_mtime;",
|
|
"\t__le64 i_mtime;",
|
|
"\t__le32 i_mtime_nsec;",
|
|
)
|
|
for declaration in ondisk_required:
|
|
if current["erofs_fs.h"].count(declaration) != 1:
|
|
raise SystemExit(f"ondisk timestamp declaration changed: {declaration}")
|
|
|
|
timestamp_helper = extract_function(current["inode.c"], "erofs_set_timestamp")
|
|
read_inode = extract_function(current["inode.c"], "erofs_read_inode")
|
|
if timestamp_helper.count("__builtin_add_overflow(seconds, 0, &mtime)") != 1:
|
|
raise SystemExit("time_t checked conversion is absent or duplicated")
|
|
compact_pattern = re.compile(
|
|
r"__builtin_add_overflow\(sbi->epoch,\s*"
|
|
r"\(int64_t\)le32toh\(dic->i_mtime\), &mtime\)"
|
|
)
|
|
if len(compact_pattern.findall(read_inode)) != 1:
|
|
raise SystemExit("compact signed checked addition is absent or duplicated")
|
|
if read_inode.count("(int64_t)le64toh(die->i_mtime)") != 1:
|
|
raise SystemExit("extended signed seconds decode is absent or duplicated")
|
|
if "(uint64_t)le32toh(dic->i_mtime)" in read_inode:
|
|
raise SystemExit("unsigned compact timestamp arithmetic remains")
|
|
|
|
changed_sources = current["internal.h"] + current["inode.c"] + current["super.c"]
|
|
if "ckd_add" in changed_sources or "stdckdint" in changed_sources:
|
|
raise SystemExit("unsupported checked-arithmetic API entered B13")
|
|
if re.search(r"return\s*\(\s*-E[A-Z0-9_]+", changed_sources):
|
|
raise SystemExit("negative errno entered the FreeBSD timestamp path")
|
|
|
|
overflow_lines = []
|
|
for path in ("inode.c", "super.c"):
|
|
for line_number, line in enumerate(current[path].splitlines(), start=1):
|
|
if "__builtin_" in line and "overflow" in line:
|
|
overflow_lines.append(f"{path}:{line_number}:{line.strip()}")
|
|
(artifacts / "B13-overflow-scan.txt").write_text(
|
|
"\n".join(overflow_lines) + "\n", encoding="ascii"
|
|
)
|
|
|
|
compact = load_fixture(
|
|
compact_path, {"epoch", "fixed_nsec", "mtime_delta"}
|
|
)
|
|
extended = load_fixture(extended_path, {"mtime", "mtime_nsec"})
|
|
seen_ids: set[str] = set()
|
|
decoded_compact = []
|
|
decoded_extended = []
|
|
|
|
for case in compact["cases"]:
|
|
case_id = case.get("id")
|
|
if not isinstance(case_id, str) or case_id in seen_ids:
|
|
raise SystemExit(f"invalid or duplicate B13 case id: {case_id!r}")
|
|
seen_ids.add(case_id)
|
|
epoch = decode_le(case["epoch_le"], 8, signed=True)
|
|
delta = decode_le(case["mtime_delta_le"], 4, signed=False)
|
|
nanoseconds = decode_le(case["fixed_nsec_le"], 4, signed=False)
|
|
mathematical = epoch + delta
|
|
add_status = status_for_range(mathematical, int64_min, int64_max)
|
|
if add_status != case["expected_add"]:
|
|
raise SystemExit(f"compact add oracle mismatch: {case_id}")
|
|
if add_status == "PASS":
|
|
if mathematical != case["expected_seconds"]:
|
|
raise SystemExit(f"compact seconds oracle mismatch: {case_id}")
|
|
if status_for_range(mathematical, int64_min, int64_max) != case["expected_time64"]:
|
|
raise SystemExit(f"compact time64 oracle mismatch: {case_id}")
|
|
if status_for_range(mathematical, int32_min, int32_max) != case["expected_time32"]:
|
|
raise SystemExit(f"compact time32 oracle mismatch: {case_id}")
|
|
nsec_status = "PASS" if nanoseconds < 1_000_000_000 else "EINTEGRITY"
|
|
if nsec_status != case["expected_nsec"]:
|
|
raise SystemExit(f"compact nsec oracle mismatch: {case_id}")
|
|
elif any(case[key] != "NOT_REACHED" for key in (
|
|
"expected_time32", "expected_time64", "expected_nsec"
|
|
)) or case["expected_seconds"] is not None:
|
|
raise SystemExit(f"compact overflow must stop before conversion: {case_id}")
|
|
decoded_compact.append(
|
|
{
|
|
"id": case_id,
|
|
"epoch": epoch,
|
|
"delta": delta,
|
|
"nanoseconds": nanoseconds,
|
|
"mathematical_seconds": mathematical,
|
|
"add_status": add_status,
|
|
}
|
|
)
|
|
|
|
for case in extended["cases"]:
|
|
case_id = case.get("id")
|
|
if not isinstance(case_id, str) or case_id in seen_ids:
|
|
raise SystemExit(f"invalid or duplicate B13 case id: {case_id!r}")
|
|
seen_ids.add(case_id)
|
|
seconds = decode_le(case["mtime_le"], 8, signed=True)
|
|
nanoseconds = decode_le(case["mtime_nsec_le"], 4, signed=False)
|
|
if seconds != case["expected_seconds"]:
|
|
raise SystemExit(f"extended signed decode mismatch: {case_id}")
|
|
if status_for_range(seconds, int64_min, int64_max) != case["expected_time64"]:
|
|
raise SystemExit(f"extended time64 oracle mismatch: {case_id}")
|
|
if status_for_range(seconds, int32_min, int32_max) != case["expected_time32"]:
|
|
raise SystemExit(f"extended time32 oracle mismatch: {case_id}")
|
|
nsec_status = "PASS" if nanoseconds < 1_000_000_000 else "EINTEGRITY"
|
|
if nsec_status != case["expected_nsec"]:
|
|
raise SystemExit(f"extended nsec oracle mismatch: {case_id}")
|
|
decoded_extended.append(
|
|
{"id": case_id, "seconds": seconds, "nanoseconds": nanoseconds}
|
|
)
|
|
|
|
fixture_result = {
|
|
"status": "PASS",
|
|
"compact": decoded_compact,
|
|
"extended": decoded_extended,
|
|
}
|
|
(artifacts / "B13-decoded-fixtures.json").write_text(
|
|
json.dumps(fixture_result, ensure_ascii=True, indent=2, sort_keys=True) + "\n",
|
|
encoding="ascii",
|
|
)
|
|
|
|
|
|
def c_i64(value: int) -> str:
|
|
if value == int64_min:
|
|
return "INT64_MIN"
|
|
if value == int64_max:
|
|
return "INT64_MAX"
|
|
if value < 0:
|
|
return f"(-INT64_C({-value}))"
|
|
return f"INT64_C({value})"
|
|
|
|
|
|
def c_status(value: str) -> str:
|
|
if value == "PASS":
|
|
return "0"
|
|
if value == "EINTEGRITY":
|
|
return "EINTEGRITY"
|
|
raise SystemExit(f"cannot emit non-terminal status: {value}")
|
|
|
|
|
|
program = f'''#include <inttypes.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <time.h>
|
|
|
|
#define EINTEGRITY {eintegrity}
|
|
|
|
struct erofs_inode {{
|
|
\ttime_t mtime;
|
|
\tuint32_t mtime_nsec;
|
|
}};
|
|
|
|
{timestamp_helper}
|
|
|
|
static int
|
|
checked_compact(int64_t epoch, uint32_t delta, int64_t *seconds)
|
|
{{
|
|
\tif (__builtin_add_overflow(epoch, (int64_t)delta, seconds))
|
|
\t\treturn (EINTEGRITY);
|
|
\treturn (0);
|
|
}}
|
|
|
|
static int
|
|
checked_time32(int64_t seconds, int32_t *result)
|
|
{{
|
|
\treturn (__builtin_add_overflow(seconds, 0, result) ? EINTEGRITY : 0);
|
|
}}
|
|
|
|
static int
|
|
checked_time64(int64_t seconds, int64_t *result)
|
|
{{
|
|
\treturn (__builtin_add_overflow(seconds, 0, result) ? EINTEGRITY : 0);
|
|
}}
|
|
|
|
static int
|
|
check_timestamp(const char *id, int64_t seconds, uint32_t nanoseconds,
|
|
int expected_time32, int expected_time64, int expected_nsec)
|
|
{{
|
|
\tstruct erofs_inode inode = {{ 0 }};
|
|
\tint32_t time32;
|
|
\tint64_t time64;
|
|
\tint error, expected;
|
|
|
|
\tif (checked_time32(seconds, &time32) != expected_time32 ||
|
|
\t checked_time64(seconds, &time64) != expected_time64) {{
|
|
\t\tfprintf(stderr, "%s: representability\\n", id);
|
|
\t\treturn (1);
|
|
\t}}
|
|
\texpected = expected_nsec != 0 ? expected_nsec :
|
|
\t (sizeof(time_t) == sizeof(int32_t) ? expected_time32 : expected_time64);
|
|
\terror = erofs_set_timestamp(&inode, seconds, nanoseconds);
|
|
\tif (error != expected) {{
|
|
\t\tfprintf(stderr, "%s: timestamp errno %d != %d\\n", id, error, expected);
|
|
\t\treturn (1);
|
|
\t}}
|
|
\tif (error == 0 && ((int64_t)inode.mtime != seconds ||
|
|
\t inode.mtime_nsec != nanoseconds)) {{
|
|
\t\tfprintf(stderr, "%s: timestamp value\\n", id);
|
|
\t\treturn (1);
|
|
\t}}
|
|
\treturn (0);
|
|
}}
|
|
|
|
int
|
|
main(void)
|
|
{{
|
|
\tint64_t seconds;
|
|
\tint error;
|
|
|
|
\tif (sizeof(time_t) != sizeof(int32_t) && sizeof(time_t) != sizeof(int64_t))
|
|
\t\treturn (2);
|
|
'''
|
|
for case, decoded in zip(compact["cases"], decoded_compact, strict=True):
|
|
epoch = decoded["epoch"]
|
|
delta = decoded["delta"]
|
|
expected_add = c_status(case["expected_add"])
|
|
program += f'''\terror = checked_compact({c_i64(epoch)}, UINT32_C({delta}), &seconds);
|
|
\tif (error != {expected_add}) {{
|
|
\t\tfprintf(stderr, "{case['id']}: compact add errno\\n");
|
|
\t\treturn (1);
|
|
\t}}
|
|
'''
|
|
if case["expected_add"] == "PASS":
|
|
program += f'''\tif (seconds != {c_i64(case['expected_seconds'])} ||
|
|
\t check_timestamp("{case['id']}", seconds, UINT32_C({decoded['nanoseconds']}),
|
|
\t {c_status(case['expected_time32'])}, {c_status(case['expected_time64'])},
|
|
\t {c_status(case['expected_nsec'])}) != 0)
|
|
\t\treturn (1);
|
|
'''
|
|
for case, decoded in zip(extended["cases"], decoded_extended, strict=True):
|
|
program += f'''\tif (check_timestamp("{case['id']}", {c_i64(decoded['seconds'])},
|
|
\t UINT32_C({decoded['nanoseconds']}), {c_status(case['expected_time32'])},
|
|
\t {c_status(case['expected_time64'])}, {c_status(case['expected_nsec'])}) != 0)
|
|
\t\treturn (1);
|
|
'''
|
|
program += f'''\tprintf("B13 boundary PASS compact={len(compact['cases'])} extended={len(extended['cases'])} time_t=%zu\\n",
|
|
\t sizeof(time_t) * 8);
|
|
\treturn (0);
|
|
}}
|
|
'''
|
|
|
|
program_path = artifacts / "B13-time-boundary.c"
|
|
binary_path = artifacts / "B13-time-boundary"
|
|
program_path.write_text(program, encoding="ascii")
|
|
compiled = subprocess.run(
|
|
[
|
|
"cc", "-std=c11", "-O2", "-Wall", "-Wextra", "-Werror",
|
|
str(program_path), "-o", str(binary_path),
|
|
],
|
|
check=False,
|
|
text=True,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
)
|
|
(artifacts / "B13-time-boundary.compile.stdout").write_text(
|
|
compiled.stdout, encoding="utf-8"
|
|
)
|
|
(artifacts / "B13-time-boundary.compile.stderr").write_text(
|
|
compiled.stderr, encoding="utf-8"
|
|
)
|
|
if compiled.returncode != 0:
|
|
raise SystemExit("B13 boundary extractor compilation failed")
|
|
executed = subprocess.run(
|
|
[str(binary_path)],
|
|
check=False,
|
|
text=True,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
)
|
|
(artifacts / "B13-time-boundary.stdout").write_text(
|
|
executed.stdout, encoding="utf-8"
|
|
)
|
|
(artifacts / "B13-time-boundary.stderr").write_text(
|
|
executed.stderr, encoding="utf-8"
|
|
)
|
|
if executed.returncode != 0:
|
|
raise SystemExit("B13 boundary extractor execution failed")
|
|
|
|
program32 = program.replace("#include <time.h>\n", "#define time_t int32_t\n", 1)
|
|
program32_path = artifacts / "B13-time-boundary32.c"
|
|
binary32_path = artifacts / "B13-time-boundary32"
|
|
program32_path.write_text(program32, encoding="ascii")
|
|
compiled32 = subprocess.run(
|
|
[
|
|
"cc", "-std=c11", "-O2", "-Wall", "-Wextra", "-Werror",
|
|
str(program32_path), "-o", str(binary32_path),
|
|
],
|
|
check=False,
|
|
text=True,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
)
|
|
(artifacts / "B13-time-boundary32.compile.stdout").write_text(
|
|
compiled32.stdout, encoding="utf-8"
|
|
)
|
|
(artifacts / "B13-time-boundary32.compile.stderr").write_text(
|
|
compiled32.stderr, encoding="utf-8"
|
|
)
|
|
if compiled32.returncode != 0:
|
|
raise SystemExit("B13 32-bit time_t extractor compilation failed")
|
|
executed32 = subprocess.run(
|
|
[str(binary32_path)],
|
|
check=False,
|
|
text=True,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
)
|
|
(artifacts / "B13-time-boundary32.stdout").write_text(
|
|
executed32.stdout, encoding="utf-8"
|
|
)
|
|
(artifacts / "B13-time-boundary32.stderr").write_text(
|
|
executed32.stderr, encoding="utf-8"
|
|
)
|
|
if executed32.returncode != 0:
|
|
raise SystemExit("B13 32-bit time_t extractor execution failed")
|
|
|
|
result = {
|
|
"status": "PASS",
|
|
"final_id": "P15-018",
|
|
"baseline": baseline,
|
|
"compact_cases": len(compact["cases"]),
|
|
"extended_cases": len(extended["cases"]),
|
|
"signed_add_overflows": sum(
|
|
case["expected_add"] == "EINTEGRITY" for case in compact["cases"]
|
|
),
|
|
"negative_seconds_passes": sum(
|
|
case["expected_seconds"] is not None and case["expected_seconds"] < 0
|
|
for case in compact["cases"] + extended["cases"]
|
|
),
|
|
"time32_lower_rejections": sum(
|
|
case["expected_seconds"] is not None
|
|
and case["expected_seconds"] < int32_min
|
|
and case["expected_time32"] == "EINTEGRITY"
|
|
for case in compact["cases"] + extended["cases"]
|
|
),
|
|
"time32_upper_rejections": sum(
|
|
case["expected_seconds"] is not None
|
|
and case["expected_seconds"] > int32_max
|
|
and case["expected_time32"] == "EINTEGRITY"
|
|
for case in compact["cases"] + extended["cases"]
|
|
),
|
|
"nanosecond_rejections": sum(
|
|
case["expected_nsec"] == "EINTEGRITY"
|
|
for case in compact["cases"] + extended["cases"]
|
|
),
|
|
"ondisk_header_byte_identical": True,
|
|
"ondisk_seconds_width": 64,
|
|
"ondisk_compact_delta_width": 32,
|
|
"time_t_checked_conversion": True,
|
|
"extracted_helper_time_widths": [32, 64],
|
|
"unsupported_ckd_api": False,
|
|
"positive_errno_preserved": True,
|
|
"qemu": "NOT_RUN",
|
|
"full_feature_suite": "NOT_RUN",
|
|
}
|
|
(artifacts / "B13-result.json").write_text(
|
|
json.dumps(result, ensure_ascii=True, indent=2, sort_keys=True) + "\n",
|
|
encoding="ascii",
|
|
)
|
|
print(
|
|
f"B13 PASS compact={len(compact['cases'])} extended={len(extended['cases'])} "
|
|
f"signed-overflow={result['signed_add_overflows']}"
|
|
)
|
|
PY
|
|
then
|
|
:
|
|
else
|
|
pre15_dut_fail 'B13 signed timestamp source or boundary check failed'
|
|
fi
|
|
|
|
sha256sum "$artifacts/B13-overflow-scan.txt" \
|
|
"$artifacts/B13-decoded-fixtures.json" \
|
|
"$artifacts/B13-time-boundary.c" "$artifacts/B13-time-boundary" \
|
|
"$artifacts/B13-time-boundary32.c" "$artifacts/B13-time-boundary32" \
|
|
"$artifacts/B13-result.json" > "$artifacts/SHA256SUMS"
|
|
printf 'B13 PASS signed timestamps and checked time_t boundaries\n'
|