import json from pathlib import Path import re import signal import socket import struct import subprocess import sys import tempfile import time from live import message ROOT = Path(__file__).resolve().parents[1] ENTRY = struct.Struct('= 38: raise ValueError('RedSea filename exceeds 37 bytes') entries.append((0x800, name.encode('ascii'), append(payload), len(payload), 0)) once = b'Cd("::/");;\n#include "Remy.HC"\ndirectory="::/";\nif (Verify()) Live(0);\n' once_sector = append(once) home = next(row for row in entries if row[1].split(b'\0')[0] == b'Home') home_start, home_size = home[2:4] for at in range(home_start*512, home_start*512+home_size, ENTRY.size): if not data[at+2]: data[at:at+ENTRY.size] = ENTRY.pack(0x800, b'Once.HC', once_sector, len(once), 0) break else: raise ValueError('template Home directory has no spare entry') new_root = len(data) // 512 new_size = ((len(entries)*ENTRY.size+511) // 512)*512 entries[0] = (entries[0][0], entries[0][1], new_root, new_size, entries[0][4]) entries[1] = (entries[1][0], entries[1][1], new_root, 0, entries[1][4]) append(b''.join(ENTRY.pack(*row) for row in entries)) for row in entries[2:]: if row[0] & 0x10: parent = row[2]*512+ENTRY.size if ENTRY.unpack_from(data, parent)[1].split(b'\0')[0] == b'..': struct.pack_into('= bitmap*512*8: raise ValueError('template allocation bitmap is too small') struct.pack_into('I', data, sector*2048+84, len(data)//2048) struct.pack_into('I', data, sector*2048+156, new_root) destination.write_bytes(data) def exact(connection, count): result = bytearray() while len(result) < count: data = connection.recv(count-len(result)) if not data: raise ConnectionError('guest serial connection closed') result.extend(data) return bytes(result) def reply(connection): prefix = bytearray(exact(connection, 4)) while prefix != b'RMA1': prefix.pop(0) prefix.extend(exact(connection, 1)) return struct.unpack('= 560 and height >= 400 pixels = data[header.end():] return b''.join(pixels[(y*width+80)*3:(y*width+560)*3] for y in range(64,400)) def main(): if len(sys.argv) != 2 or not sys.argv[1]: raise SystemExit('usage: python3 tests/guest.py TempleOS.ISO') destination = ROOT / '.build/guest.iso' image(Path(sys.argv[1]), destination) with tempfile.TemporaryDirectory(prefix='remy') as directory: directory = Path(directory) serial, monitor = directory / 'serial', directory / 'qmp' log = (ROOT / '.build/guest.log').open('wb') process = subprocess.Popen(['qemu-system-x86_64','-m','512','-smp','2', '-accel','tcg,thread=multi','-cdrom',destination,'-boot','d','-display','none', '-monitor','none','-no-reboot','-chardev',f'socket,id=live,path={serial},server=on,wait=off', '-serial','chardev:live','-qmp',f'unix:{monitor},server=on,wait=off'],stdout=log,stderr=log) connection = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) control = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) qmp = None watcher = None try: until = time.monotonic()+15 while not serial.exists() or not monitor.exists(): if time.monotonic()>until or process.poll() is not None: raise RuntimeError('QEMU did not start') time.sleep(0.1) connection.connect(str(serial)) connection.settimeout(90) control.connect(str(monitor)) control.settimeout(5) qmp = control.makefile('rwb',buffering=0) json.loads(qmp.readline()) qmp.write(b'{"execute":"qmp_capabilities"}\n') while 'return' not in json.loads(qmp.readline()): pass assert reply(connection) == (0,0,0) print('guest booted and verified all three payloads',flush=True) connection.settimeout(30) connection.sendall(packet('(+ x y)',1,directory)) assert reply(connection) == (1,1,1) time.sleep(1) before = screen(qmp,ROOT / '.build/first.ppm') second = packet('(if (< x y) (+ x 3) (- y 9))',2,directory) connection.sendall(second) assert reply(connection) == (2,1,2) time.sleep(1) after = screen(qmp,ROOT / '.build/second.ppm') assert before != after, 'accepted revision did not change rendered pixels' print('guest changed rendered pixels after two verified revisions',flush=True) corrupt = bytearray(packet('(xor x y)',3,directory)) corrupt[-1] ^= 1 connection.sendall(corrupt) assert reply(connection) == (3,2,2) corrupt = bytearray(packet('(xor x y)',4,directory)) corrupt[-1] ^= 1 struct.pack_into('