SchOS/tools/qrcheck.py
Nexoniarz e04ca28812 Rename Schunix to SchOS; the kernel becomes SchIFT
Removes the "-unix" element and with it any trademark exposure to The
Open Group.

The split follows the ecosystem layout: SchOS is the system, and the
kernel has its own name. Kernel-identity strings are the only places
that take SchIFT -- uname(2) sysname, /proc/version, and the two licence
sentences that say "kernel" outright. That is what every Unix does here:
`uname -s` on Linux reports the kernel, not the distribution. Everything
a user reads as the product -- panic screen, boot menu, error prefixes,
docs, LICENSE, NOTICE -- is SchOS.

schunix.img is schos.img, the logo assets and the musl TLS patch are
renamed, and __SCHUNIX__ is __SCHOS__ (the define and the patch that
consumes it moved together).

Two things the rename broke, both found by the bootcheck suite:

  - shmtest wrote "SCHUNIX-MMAP-OK" with a hardcoded length of 15 and
    compared it back by hand-counted offsets (m[3]=='U', m[14]=='K').
    The shorter marker left the write running two bytes past the literal
    and the compare reading past the data, failing a file-backed mmap
    that was working correctly. Now written and compared through one
    constant with sizeof.
  - bootcheck asserted a 31-byte file after appending to
    "hello-from-schunix"; the shorter string makes it 29.

Both are the same mistake: a string literal whose length is written out
somewhere else. Full suite green -- base, Tty, Ps, Sock, Fb, Usb, Evdev.
2026-08-09 14:09:59 +02:00

185 lines
6.9 KiB
Python

#!/usr/bin/env python3
"""Cross-check kernel/core/qr.c against independent implementations.
The self-checks inside tools/qrtest.c verify what has a local notion of
correctness -- table arithmetic, the Reed-Solomon generator polynomials, whether
a finder pattern is where it should be. They cannot verify the parts that only
mean something to a decoder: zigzag placement order, mask selection, and block
interleaving. All three have the same failure mode, which is a symbol that looks
exactly like a QR code and is noise.
Two oracles, in order of authority:
1. **A real decoder** (OpenCV). Does the symbol read back as the exact bytes
that went in? This is the only test that matches what the feature is for --
somebody pointing a phone at a crashed machine -- and it is the one that
decides pass or fail.
2. **A second encoder** (segno), matrix compared module for module. Stricter
than decoding when it agrees, because it pins down the exact mask and
placement rather than merely something readable. Applied only to payloads
that exactly fill their version, for the reason below.
segno adds a spurious zero pad codeword to every byte-mode symbol. Its
write_padding_bits() does `buff.extend([0] * (8 - (length % 8)))`, which
emits a whole byte when the stream is already on a codeword boundary -- and
in byte mode the stream is always 8L+16 bits after the terminator, so it
always is. ISO/IEC 18004 makes that conditional ("if the bit stream length is
such that it does not end at a codeword boundary"), and segno quotes exactly
that sentence above the line. It is harmless -- decoders read `count` bytes
and ignore padding -- but it means the two encoders cannot agree bit-for-bit
on any payload that needs padding, so those are compared by decoding only.
pip install --target <dir> opencv-python-headless numpy segno
PYTHONPATH=<dir> python tools/qrcheck.py
Requires build/tools/qrtest (make qrtest). Both libraries are optional; whatever
is installed gets used.
"""
import os
import subprocess
import sys
# Normalised and absolute: Windows CreateProcess rejects a relative path written
# with forward slashes, even when the file is plainly there.
QRTEST = os.path.abspath(os.path.join("build", "tools",
"qrtest.exe" if os.name == "nt" else "qrtest"))
try:
import cv2
import numpy as np
except ImportError:
cv2 = None
try:
import segno
except ImportError:
segno = None
if cv2 is None and segno is None:
sys.exit("qrcheck: needs opencv-python-headless and/or segno")
if not os.path.exists(QRTEST):
sys.exit(f"qrcheck: {QRTEST} not found -- run `make qrtest`")
def ours(text):
"""Our encoder's matrix, as a list of strings of '0'/'1'."""
out = subprocess.run([QRTEST, "-m", text], capture_output=True, text=True)
if out.returncode != 0:
raise RuntimeError(f"qrtest failed on {text!r}")
return out.stdout.split()
def our_mask(m):
"""The mask our encoder chose, read back out of the format bits.
Read from the symbol rather than reported by it, because the point is to
check what was actually written.
"""
bits = 0
for i in range(15):
if i <= 5: b = int(m[i][8])
elif i == 6: b = int(m[7][8])
elif i == 7: b = int(m[8][8])
elif i == 8: b = int(m[8][7])
else: b = int(m[8][14 - i])
bits |= b << i
return ((bits ^ 0x5412) >> 10) & 7
def render(m, scale, border):
n = len(m)
side = (n + 2 * border) * scale
img = np.full((side, side), 255, np.uint8)
for y in range(n):
for x in range(n):
if m[y][x] == "1":
img[(y + border) * scale:(y + border + 1) * scale,
(x + border) * scale:(x + border + 1) * scale] = 0
return img
# OpenCV's detector is not a reference implementation and does fail on symbols
# that are perfectly valid -- sensitive to scale and quiet-zone width in ways
# that have nothing to do with the encoding. Several renderings are tried before
# concluding anything, and a payload that still will not decode is re-tested
# against segno's symbol for the same input: if that fails too, the detector is
# what is at fault, not the encoder. Without that second step this check reports
# false failures, which is worse than not running it.
RENDERINGS = [(8, 4), (6, 6), (8, 6), (4, 4), (12, 6), (16, 4)]
def decode(m):
for scale, border in RENDERINGS:
text, _, _ = cv2.QRCodeDetector().detectAndDecode(render(m, scale, border))
if text:
return text
return ""
# Byte capacity per version: total - ecc*blocks - 2. Mirrors the table in qr.c;
# the exact-fill cases below are built from it, and a disagreement would show up
# as a version mismatch rather than passing quietly.
CAPACITY = {1: 17, 2: 32, 3: 53, 4: 78, 5: 106, 6: 134}
alpha = lambda n: "".join(chr(65 + i % 26) for i in range(n))
CASES = [
"HELLO",
"SCHOS",
"a",
"0123456789",
"SCHOS PANIC\ntrap 14 err 4 cpu 0\nrip ffffffff80104e2a",
bytes(range(1, 40)).decode("latin-1"), # non-printable payload
]
# One payload that exactly fills each version, and one just under, so both the
# padded and unpadded paths are covered at every version -- including 6, which
# is the only one with more than one Reed-Solomon block.
for v, cap in CAPACITY.items():
CASES.append(alpha(cap))
CASES.append(alpha(cap - 1))
fails = 0
print("decode (OpenCV)" if cv2 else "decode -- skipped, no opencv")
for text in CASES:
if not cv2:
break
m = ours(text)
v = (len(m) - 17) // 4
got = decode(m)
if got == text:
print(f" [ok] v{v} {len(text):3d}B")
continue
# Before blaming the encoder, check whether an independently generated
# symbol for the same payload decodes either.
if segno and not decode([
"".join(str(x) for x in row)
for row in segno.make(text.encode("latin-1"), error="l", version=v,
boost_error=False, mode="byte").matrix]):
print(f" [detector] v{v} {len(text):3d}B -- segno's symbol does not "
f"decode either; OpenCV's limitation, not ours")
continue
fails += 1
print(f" [FAIL] v{v} {len(text):3d}B decoded {got[:40]!r}")
print("\nmatrix vs segno (exact-fill payloads only)" if segno else "\nsegno -- skipped")
for v, cap in CAPACITY.items():
if not segno:
break
text = alpha(cap)
m = ours(text)
mask = our_mask(m)
q = segno.make(text.encode("latin-1"), error="l", version=v,
mask=mask, boost_error=False, mode="byte")
ref = ["".join(str(x) for x in row) for row in q.matrix]
ok = m == ref
fails += not ok
n = len(m)
diff = "" if ok else f" {sum(1 for a, b in zip(''.join(m), ''.join(ref)) if a != b)}/{n*n} differ"
print(f" [{'ok' if ok else 'FAIL'}] v{v} mask {mask} {cap:3d}B{diff}")
print()
print("qrcheck: FAILED" if fails else "qrcheck: passed")
sys.exit(1 if fails else 0)