Node helpers and utilities

The terminal, QMP servers and snapshot files for Node, and the parsers, encoders and errors both platforms share.

warm64/wemu-node

Node's host and the pieces the wemu-system-aarch64 command is built from. Import WEMU itself from warm64.

import { WEMU } from "warm64";
import { nodeWEMUHost, attachStdio, serveQmp, fileSnapshots } from "warm64/wemu-node";

nodeWEMUHost()

The host for Node. See Hosts.

attachStdio()

Connects the terminal to the machine, as -serial stdio and -nographic do: the console on standard input and output, the terminal in raw mode, and the Ctrl+A keys (x quits, c switches to the monitor, b sends a break, h lists them).

attachStdio(vm, { monitor?: boolean, monitorOnly?: boolean }): () => void
OptionDescription
monitorLet Ctrl+A c switch to the monitor, as -nographic does.
monitorOnlyStart in the monitor, as -monitor stdio does.

Returns a function that detaches the terminal again.

const vm = await WEMU.launch("-m 1G -kernel vmlinuz-virt -initrd initramfs-virt -append console=ttyAMA0 -nographic", nodeWEMUHost());
attachStdio(vm, { monitor: true });
process.exitCode = (await vm.wait()).code;

serveQmp()

Serves QMP to outside clients, as -qmp does.

serveQmp(vm, endpoint): Promise<{ close(): void }>

endpoint is a parsed -qmp value: a Unix socket, a TCP port or stdio. Every client gets the QMP greeting, negotiates with qmp_capabilities, and from then on receives every event.

import { parseWEMUArgs } from "warm64";

const { qmp } = parseWEMUArgs("-qmp unix:/tmp/wemu.sock,server,nowait -kernel vmlinuz-virt");
const server = await serveQmp(vm, qmp[0]);

fileSnapshots()

A snapshot store that keeps each savevm as a file named NAME.warm64snap in a directory. Characters other than letters, digits, ., _ and - become _.

const host = { ...nodeWEMUHost(), snapshots: fileSnapshots("./snapshots") };

nodeWEMUHost({ snapshotDir }) does the same.

main()

The whole wemu-system-aarch64 command: parses argv, launches, attaches the terminal and QMP servers, and resolves with the exit status.

process.exitCode = await main(process.argv.slice(2));

imgMain()

The whole wemu-img command: imgMain(argv) returns the exit status.

Utilities

Exported from warm64.

parseWEMUArgs()

parseWEMUArgs(args: string | string[]): WEMUConfig

Parses a command line the way WEMU.launch() does, without launching anything, and returns the WEMUConfig a machine would be built from. Throws WEMUError for a line that would be refused.

splitCommandLine()

splitCommandLine(line: string): string[]

Splits a command line as a POSIX shell would (quotes and backslashes), dropping a leading qemu-system-* or wemu-system-*.

parseSubopts()

parseSubopts(value: string, impliedKey?: string): Record<string, string>

Splits QEMU's key=value,key=value form, where ,, is a literal comma and a leading bare word belongs to impliedKey.

parseSize()

parseSize(value: string, unit = 1): number

"1G" to bytes. A bare number is multiplied by unit.

encodePng() and encodePpm()

encodePng(width: number, height: number, pixels: Uint8Array): Uint8Array
encodePpm(width: number, height: number, pixels: Uint8Array): Uint8Array

Encode a8r8g8b8 pixels (as a display's frame receives them) as a PNG or a PPM image, the formats screendump writes.

QCODE_TO_LINUX

QEMU's key names ("ret", "a", "ctrl"…) to Linux key codes, as send-key and input-send-event use them.

DEVICE_DRIVERS

The -device drivers this machine accepts, and the kind of each.

VERSION

The package version, "0.1.0".

WEMUConfig

The parsed command line: what parseWEMUArgs() returns and vm.config holds. WEMU.launch() accepts one in place of a command line.

interface WEMUConfig {
  machine: { type: "virt"; gicVersion: 2 | 3; virtualization: boolean; iommu: "none" | "smmuv3"; dumpdtb?: string };
  cpu: { model: string; off: Set<string>; max: boolean };  // off: features turned off, such as "sve"
  accel: "tcg";
  memory: number;                  // bytes
  smp: { cpus: number };
  kernel?: string; initrd?: string; append: string; dtb?: string; bios?: string;
  pflash: string[];
  drives: WEMUDrive[];
  netdevs: WEMUNetdev[];
  devices: WEMUDevice[];
  chardevs: WEMUChardev[];
  fsdevs: WEMUFsdev[];
  serials: WEMUSerial[];
  monitor: WEMUMonitor;
  qmp: WEMUQmp[];
  display: "none" | "gui";
  gl?: boolean;
  audiodevs: { id: string; driver: string; path?: string }[];
  nographic: boolean;
  startPaused: boolean;            // -S
  actions: WEMUActions;
  snapshot: boolean;               // -snapshot
  rtc: { base: "utc" | "localtime" | number; clock: "host" | "vm" | "rt" };
  name?: string; uuid?: string;
  boot?: { order?: string; menu?: boolean };
  loadvm?: string; incoming?: string;
  usb: boolean;
  version: boolean; help: boolean;
  ignored: string[];               // options accepted and ignored, as written
}

The parts:

interface WEMUDrive {
  id: string; file?: string; if: "virtio" | "none" | "pflash"; format: string;
  readonly: boolean; discard: "ignore" | "unmap"; snapshot: boolean; media: "disk" | "cdrom";
  unit?: number; bound: boolean;   // bound: named by a -device
}
interface WEMUNetdev {
  id: string; type: "user" | "socket"; connect?: string;
  net?: string; host?: string; dns?: string; dhcpstart?: string; bound: boolean;
}
interface WEMUDevice { driver: string; props: Record<string, string> }
interface WEMUChardev {
  id: string; backend: "stdio" | "null" | "file" | "socket" | "pty" | "memory" | "ringbuf";
  props: Record<string, string>;
}
interface WEMUFsdev { id: string; path?: string; mountTag?: string; bound: boolean }
type WEMUSerial = { kind: "stdio"; monitor: boolean } | { kind: "none" } | { kind: "null" }
  | { kind: "file"; path: string } | { kind: "chardev"; id: string } | { kind: "vc" };
type WEMUMonitor = { kind: "stdio" } | { kind: "none" } | { kind: "chardev"; id: string; mode: "readline" | "control" };
type WEMUQmp = { kind: "unix"; path: string; server: boolean; wait: boolean }
  | { kind: "tcp"; host: string; port: number; server: boolean; wait: boolean }
  | { kind: "stdio" } | { kind: "chardev"; id: string };
interface WEMUActions {
  shutdown: "poweroff" | "pause"; reboot: "reset" | "shutdown"; panic: "pause" | "shutdown" | "none";
}

Types

Every type the package exports, and where it's described:

TypeFromDescribed in
WEMUHost, WEMUCoreKind, WasmSourcewarm64Hosts
WEMUDiskFile, DiskBackingwarm64openDisk
WEMUDisplay, WEMUAudio, WEMUSnapshotStorewarm64display, audio, snapshots
Connector, StackSocket, SocketHandlerswarm64connector
BrowserWEMUHostOptionswarm64browserWEMUHost()
NodeWEMUHostOptionswarm64/wemu-nodenodeWEMUHost()
WEMURunState, QmpEvent, WEMUSharewarm64WEMU
WEMUConfig and its partswarm64above

Package entry points

ImportWhat
warm64WEMU, the browser host and everything shared.
warm64/wemu-nodeNode's host and helpers.
warm64/wasm, warm64/wasm64, warm64/wasm-threads, warm64/wasm64-threads, warm64/wasm-gpu, warm64/wasm-gpu-threadsThe core modules' files, for bundlers that copy assets.

The commands wemu-system-aarch64 and wemu-img are the package's bin entries.

Errors

WEMUError

Thrown by WEMU.launch() and parseWEMUArgs() for a command line that is refused. Its message is what qemu-system-aarch64 would print, prefixed wemu-system-aarch64: .

try {
  await WEMU.launch("-enable-kvm -kernel vmlinuz-virt", host);
} catch (e) {
  if (e instanceof WEMUError) console.error(e.message);
  // wemu-system-aarch64: KVM is not available here (the CPU is emulated, as with -accel tcg)
}

QmpError

Rejects vm.qmp() and vm.hmp() when QEMU would answer with an error.

PropertyDescription
classQEMU's error class: GenericError, CommandNotFound, DeviceNotFound…
descThe message.

See also