From e5781fc168988ff8be1361f3ecf099686b363671 Mon Sep 17 00:00:00 2001 From: Sebastian Jeong Date: Sun, 16 Aug 2026 14:46:33 +0900 Subject: [PATCH] feat: add TCP staging path for FreeDOS --- fec/tcp-stage.bat | 19 +++++++++++++ tools/tcp_stage.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 fec/tcp-stage.bat create mode 100644 tools/tcp_stage.py diff --git a/fec/tcp-stage.bat b/fec/tcp-stage.bat new file mode 100644 index 0000000..d4825c5 --- /dev/null +++ b/fec/tcp-stage.bat @@ -0,0 +1,19 @@ +@echo off +rem Pull a host-built TCP staging bundle into the authoritative C:\FEC tree. +rem QEMU user networking exposes the host as 10.0.2.2. HTGET and UNZIP are +rem supplied by the installed FreeDOS network tools. +if "%1"=="" goto usage +if exist C:\FEC\STAGE.ZIP del C:\FEC\STAGE.ZIP +htget -o C:\FEC\STAGE.ZIP %1 +if errorlevel 1 goto fail +unzip -o C:\FEC\STAGE.ZIP -d C:\FEC +if errorlevel 1 goto fail +if exist C:\FEC\TCPSTG.OK del C:\FEC\TCPSTG.OK +echo OK>C:\FEC\TCPSTG.OK +goto done +:usage +echo usage: TCP-STAGE.BAT http://10.0.2.2:8000/STAGE.ZIP +goto done +:fail +echo FAIL>C:\FEC\TCPSTG.FAIL +:done diff --git a/tools/tcp_stage.py b/tools/tcp_stage.py new file mode 100644 index 0000000..f29647d --- /dev/null +++ b/tools/tcp_stage.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +"""Build and serve a FreeDOS TCP staging bundle. + +The guest pulls STAGE.ZIP with its existing HTGET client from QEMU user-net's +host address (10.0.2.2), then extracts it into C:\\FEC. This avoids changing +the live vvfat exchange disk and keeps serial for bootstrap/recovery only. +""" +import argparse +import http.server +import os +import shutil +import zipfile +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +DEFAULT_STAGE = ROOT / ".tcp-stage" + + +def bundle(stage: Path, files: list[str]) -> Path: + if stage.exists(): + shutil.rmtree(stage) + stage.mkdir(parents=True) + archive = stage / "STAGE.ZIP" + with zipfile.ZipFile(archive, "w", zipfile.ZIP_DEFLATED) as zf: + for item in files: + source = (ROOT / item).resolve() + try: + relative = source.relative_to(ROOT / "fec") + except ValueError as exc: + raise SystemExit("only files below fec/ may be staged: " + item) from exc + if not source.is_file(): + raise SystemExit("not a file: " + item) + zf.write(source, relative.as_posix()) + print("created %s (%d bytes)" % (archive, archive.stat().st_size)) + return archive + + +def serve(stage: Path, port: int) -> None: + if not (stage / "STAGE.ZIP").is_file(): + raise SystemExit("missing STAGE.ZIP; run bundle first") + handler = lambda *args, **kwargs: http.server.SimpleHTTPRequestHandler( + *args, directory=str(stage), **kwargs) + server = http.server.ThreadingHTTPServer(("0.0.0.0", port), handler) + print("serving %s at http://10.0.2.2:%d/STAGE.ZIP" % (stage, port)) + try: + server.serve_forever() + finally: + server.server_close() + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--stage-dir", type=Path, default=DEFAULT_STAGE) + commands = parser.add_subparsers(dest="command", required=True) + make = commands.add_parser("bundle") + make.add_argument("files", nargs="+") + web = commands.add_parser("serve") + web.add_argument("--port", type=int, default=8000) + args = parser.parse_args() + if args.command == "bundle": + bundle(args.stage_dir, args.files) + else: + serve(args.stage_dir, args.port) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())