FDS/OS 1.0
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Cross-check the actual ARM media builder using disposable regular files only."""
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import zlib
|
||||
|
||||
project = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(project/'tools'))
|
||||
from image_formats import digest, gpt, LINUX_FILESYSTEM
|
||||
|
||||
work = Path(tempfile.mkdtemp(prefix='m9-images.', dir=project/'out'))
|
||||
root = work/'root'
|
||||
source = (project/'out/rootfs-cli.tar').resolve().parent/'root'
|
||||
assert source.is_dir(), 'Build the configured rootfs first'
|
||||
subprocess.run(['cp', '-al', str(source), str(root)], check=True)
|
||||
fixture = root/'m9-test'
|
||||
fixture.mkdir()
|
||||
shutil.copy2(project/'target/aarch64-unknown-linux-musl/release/fds-burn', fixture/'fds-burn')
|
||||
subprocess.run(['cp', '-al', str(source), str(fixture/'system')], check=True)
|
||||
|
||||
def manifest(kind, label, profile=''):
|
||||
text = f'''format = 1
|
||||
[cartridge]
|
||||
id = "fds.test.{kind}"
|
||||
name = "{label}"
|
||||
class = "{kind}"
|
||||
version = "0.1.0"
|
||||
[media]
|
||||
writable = {str(kind == 'data').lower()}
|
||||
'''
|
||||
if profile:
|
||||
text += f'\n[activation]\nprofile = "{profile}"\n'
|
||||
return text
|
||||
|
||||
for kind in ('system', 'data', 'program', 'environment'):
|
||||
tree = fixture/kind
|
||||
(tree/'FDS').mkdir(parents=True)
|
||||
(tree/'FDS/CARTRIDGE.TOML').write_text(manifest(kind, f'TEST {kind.upper()}', 'windowmaker' if kind == 'environment' else ''))
|
||||
if kind == 'program':
|
||||
(tree/'app/bin').mkdir(parents=True)
|
||||
shutil.copy2(project/'out/fds', tree/'app/bin/fds')
|
||||
if kind == 'data':
|
||||
(tree/'read-me.txt').write_text('Created on ARM by FDS.\n')
|
||||
|
||||
# One namespace call supplies a private ARM binfmt handler for child utilities.
|
||||
script = '''#!/usr/bin/bash
|
||||
set -euo pipefail
|
||||
cd /m9-test
|
||||
for kind in system data environment; do
|
||||
options=()
|
||||
if [[ $kind == data ]]; then options=(--size-mib 32); fi
|
||||
./fds-burn create "$kind" "$kind" "$kind.img" "${options[@]}" >"$kind.build.log" 2>&1
|
||||
./fds-burn inspect "$kind.img" >"$kind.json"
|
||||
done
|
||||
if ./fds-burn create data data data.img --size-mib 32 >overwrite.log 2>&1; then exit 91; fi
|
||||
if ./fds-burn create system data wrong.img >wrong-class.log 2>&1; then exit 92; fi
|
||||
if ./fds-burn create data data data/recursive.img >recursive.log 2>&1; then exit 93; fi
|
||||
ln -s data.img image-link
|
||||
if ./fds-burn inspect image-link >symlink.log 2>&1; then exit 94; fi
|
||||
if ./fds-burn create program program forbidden.img >program-build-rejected.log 2>&1; then exit 95; fi
|
||||
printf 'PASS: actual ARM SYSTEM/DATA/ENVIRONMENT builder; PROGRAM creation requires a workstation\\n'
|
||||
'''
|
||||
(fixture/'run').write_text(script)
|
||||
result = subprocess.run([str(project/'tools/in-rootfs'), str(root), '/usr/bin/bash', '/m9-test/run'],
|
||||
capture_output=True, text=True, timeout=600)
|
||||
(work/'arm.log').write_text(result.stdout+result.stderr)
|
||||
assert result.returncode == 0, f'ARM image checks failed: {work}/arm.log; per-class logs: {fixture}'
|
||||
print(result.stdout.strip(), flush=True)
|
||||
|
||||
# Legacy PROGRAM reading/writing remains supported, but new software creation
|
||||
# belongs to the workstation tool. Build this compatibility fixture on the host.
|
||||
legacy = work / 'legacy-program.erofs'
|
||||
subprocess.run([str(project/'tools/in-image-tools'), 'mkfs.erofs', '--quiet', '-b', '4096', '-T', '0', '-L', 'FDS_PROGRAM', str(legacy), str(fixture/'program')], check=True)
|
||||
gpt(fixture/'program.img', [('FDS_PROGRAM', LINUX_FILESYSTEM, legacy)])
|
||||
with (fixture/'program.json').open('w') as output:
|
||||
subprocess.run([str(project/'tools/in-void'), 'qemu-aarch64', str(fixture/'fds-burn'), 'inspect', str(fixture/'program.img')], check=True, stdout=output)
|
||||
for kind in ('system', 'data', 'program', 'environment'):
|
||||
image = fixture/f'{kind}.img'
|
||||
reported = json.loads((fixture/f'{kind}.json').read_text())
|
||||
table = json.loads(subprocess.check_output(['sfdisk', '--json', str(image)]))['partitiontable']
|
||||
assert table['label'] == 'gpt' and len(table['partitions']) == 1
|
||||
part = table['partitions'][0]
|
||||
assert part['name'] == f'FDS_{kind.upper()}'
|
||||
assert part['start']*512 == reported['partition_start']
|
||||
assert part['size']*512 == reported['partition_bytes']
|
||||
assert digest(image) == reported['sha256']
|
||||
assert image.stat().st_size == reported['bytes']
|
||||
payload = work/f'{kind}.filesystem'
|
||||
with image.open('rb') as src, payload.open('wb') as dst:
|
||||
src.seek(part['start']*512)
|
||||
remaining = part['size']*512
|
||||
while remaining:
|
||||
chunk = src.read(min(1024*1024, remaining))
|
||||
assert chunk
|
||||
dst.write(chunk)
|
||||
remaining -= len(chunk)
|
||||
if kind == 'data':
|
||||
subprocess.run(['e2fsck', '-fn', str(payload)], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
|
||||
value = subprocess.check_output(['debugfs', '-R', 'cat /FDS/CARTRIDGE.TOML', str(payload)], stderr=subprocess.DEVNULL).decode()
|
||||
assert value == manifest(kind, 'TEST DATA')
|
||||
else:
|
||||
subprocess.run([str(project/'tools/in-image-tools'), 'fsck.erofs', '--extract', str(payload)], check=True, stdout=subprocess.DEVNULL)
|
||||
shutil.copy2(fixture/f'{kind}.json', work/f'{kind}.json')
|
||||
print('PASS: independent GPT geometry, Python SHA-256, EROFS and ext4 integrity checks', flush=True)
|
||||
|
||||
# Valid CRCs are not sufficient: reject a second partition, out-of-range extents,
|
||||
# an internal label and disagreeing backup geometry. Use the host writer as an
|
||||
# independent source, then exercise the ARM parser against deliberate mutations.
|
||||
base = work/'negative-base.img'
|
||||
gpt(base, [('FDS_ENVIRONMENT', LINUX_FILESYSTEM, work/'environment.filesystem')])
|
||||
|
||||
def arm_inspect(path, ok):
|
||||
result = subprocess.run([str(project/'tools/in-void'), 'qemu-aarch64',
|
||||
str(fixture/'fds-burn'), 'inspect', str(path)],
|
||||
capture_output=True, text=True, timeout=60)
|
||||
assert (result.returncode == 0) == ok, (path, result.stdout, result.stderr)
|
||||
|
||||
arm_inspect(base, True)
|
||||
for case in ('extra_partition', 'out_of_range', 'internal_label', 'bad_backup'):
|
||||
target = work/f'{case}.img'
|
||||
shutil.copyfile(base, target)
|
||||
data = bytearray(target.read_bytes())
|
||||
last = len(data)//512-1
|
||||
table = bytearray(data[1024:1024+16384])
|
||||
if case == 'extra_partition':
|
||||
table[128:256] = table[:128]
|
||||
elif case == 'out_of_range':
|
||||
struct.pack_into('<Q', table, 40, last)
|
||||
elif case == 'internal_label':
|
||||
table[56:128] = 'FDS_INTERNAL'.encode('utf-16le').ljust(72, b'\0')
|
||||
for start in (1024, (last-32)*512):
|
||||
data[start:start+16384] = table
|
||||
for sector in (1, last):
|
||||
header = bytearray(data[sector*512:(sector+1)*512])
|
||||
struct.pack_into('<I', header, 88, zlib.crc32(table))
|
||||
if case == 'bad_backup' and sector == last:
|
||||
header[56] ^= 1
|
||||
struct.pack_into('<I', header, 16, 0)
|
||||
struct.pack_into('<I', header, 16, zlib.crc32(header[:92]))
|
||||
data[sector*512:(sector+1)*512] = header
|
||||
target.write_bytes(data)
|
||||
arm_inspect(target, False)
|
||||
print('PASS: malformed image rejection with recomputed valid GPT CRCs', flush=True)
|
||||
print(f'PASS: M9 image construction evidence: {work}')
|
||||
Reference in New Issue
Block a user