#!/usr/bin/env python3
# DF-5561 — persistent writer for the guest's socket-backed serial console.
#
# Key details:
#  * The QEMU chardev socket stays connected across FIFO EOFs (a disconnect
#    would drop bytes still buffered in the emulated 16550).
#  * Lines are sent in <=8-byte chunks with pacing: the guest's UART RX FIFO
#    is 16 bytes and ddb polls cngetc without flow control.
# Usage:
#   echo 'print db_active' > /tmp/opencode/df-5561-ser.in
import os, socket, threading, time

SOCK = "/tmp/opencode/df-5561-ser.sock"
FIFO = "/tmp/opencode/df-5561-ser.in"
DRAIN = "/tmp/opencode/df-5561-drain.log"   # socket-side copy of guest output

def connect():
    while True:
        try:
            s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
            s.connect(SOCK)
            return s
        except (FileNotFoundError, ConnectionRefusedError) as e:
            print(f"connect failed: {e}; retrying...", flush=True)
            time.sleep(2)

def drainer(s):
    # MUST continuously recv(): QEMU applies backpressure to the guest UART
    # (poll-mode cnputc spins forever) once the chardev client buffer fills.
    with open(DRAIN, "ab", buffering=0) as d:
        while True:
            try:
                b = s.recv(65536)
            except OSError:
                return
            if not b:
                return
            d.write(b)

def send_line(s, line):
    data = (line if line.endswith("\n") else line + "\n").encode()
    for i in range(0, len(data), 8):
        s.sendall(data[i:i+8])
        time.sleep(0.15)

def main():
    try: os.remove(FIFO)
    except FileNotFoundError: pass
    os.mkfifo(FIFO)
    print(f"serial writer: {FIFO} -> {SOCK} (pid {os.getpid()})", flush=True)
    s = connect()
    print("connected to serial socket", flush=True)
    threading.Thread(target=drainer, args=(s,), daemon=True).start()
    while True:
        with open(FIFO, "r") as f:
            for line in f:
                line = line.rstrip("\n")
                if not line:
                    continue
                try:
                    print(f">>> {line}", flush=True)
                    send_line(s, line)
                except OSError as e:
                    print(f"send failed: {e}; reconnecting", flush=True)
                    s = connect()
                    send_line(s, line)
        # FIFO EOF (all writers closed) — keep socket, reopen FIFO.
        print("[fifo eof — keeping socket]", flush=True)

if __name__ == "__main__":
    main()
