summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rwxr-xr-xtools/guest-diag/qga_fetch.py94
1 files changed, 75 insertions, 19 deletions
diff --git a/tools/guest-diag/qga_fetch.py b/tools/guest-diag/qga_fetch.py
index 5a940fc..2fd53fc 100755
--- a/tools/guest-diag/qga_fetch.py
+++ b/tools/guest-diag/qga_fetch.py
@@ -2,15 +2,29 @@
# Fetch a file from the guest via the QEMU guest agent protocol
# (/tmp/qemu-guest-agent.sock, the virtio-serial QGA chardev - the same
# socket vgpu_vm.py exec uses). Usage: qga_fetch.py <guest_path> <host_path> [chunk]
+#
+# Robustness (r1490): the r1489 evidence capture fetched nothing because the
+# guest-side frame was still being written / the agent answered late. Retry
+# the open a few times, tolerate error replies with a clear diagnostic, use a
+# long socket timeout, and write to a temporary file so a partial transfer is
+# never mistaken for a complete capture.
import base64
import json
+import os
import socket
import sys
+import time
+
+if len(sys.argv) < 3:
+ print("usage: qga_fetch.py <guest_path> <host_path> [chunk]", file=sys.stderr)
+ sys.exit(2)
GUEST = sys.argv[1]
HOST = sys.argv[2]
CHUNK = int(sys.argv[3]) if len(sys.argv) > 3 else 1 << 20
SOCK = "/tmp/qemu-guest-agent.sock"
+TRIES = 6
+TMP = HOST + ".part"
def recv_line(s):
@@ -29,25 +43,67 @@ def cmd(s, obj):
return json.loads(line)
-s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
-s.settimeout(30.0)
-s.connect(SOCK)
-r = cmd(s, {"execute": "guest-file-open",
- "arguments": {"path": GUEST, "mode": "r"}})
-h = r["return"]
+def open_guest():
+ last = None
+ for i in range(TRIES):
+ try:
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ s.settimeout(120.0)
+ s.connect(SOCK)
+ r = cmd(s, {"execute": "guest-file-open",
+ "arguments": {"path": GUEST, "mode": "r"}})
+ if "return" in r:
+ return s, r["return"], None
+ last = f"guest-file-open error: {r.get('error')}"
+ except Exception as e: # noqa: BLE001 - diagnostic surface
+ last = f"guest-file-open exception: {e}"
+ try:
+ s.close()
+ except Exception:
+ pass
+ time.sleep(1.5)
+ if i + 1 < TRIES:
+ print(f"retry {i + 1}/{TRIES}: {last}", file=sys.stderr)
+ return None, None, last
+
+
+s, h, err = open_guest()
+if s is None:
+ print(f"FETCH-FAIL {GUEST} -> {HOST}: {err}", file=sys.stderr)
+ sys.exit(1)
+
print(f"handle={h}")
total = 0
-with open(HOST, "wb") as out:
- while True:
- r = cmd(s, {"execute": "guest-file-read",
- "arguments": {"handle": h, "count": CHUNK}})
- d = r["return"]
- buf = base64.b64decode(d.get("buf-b64", ""))
- if buf:
- out.write(buf)
- total += len(buf)
- print(f"\r{total}", end="", flush=True)
- if d.get("eof"):
- break
-cmd(s, {"execute": "guest-file-close", "arguments": {"handle": h}})
+try:
+ with open(TMP, "wb") as out:
+ while True:
+ r = cmd(s, {"execute": "guest-file-read",
+ "arguments": {"handle": h, "count": CHUNK}})
+ if "return" not in r:
+ raise RuntimeError(f"guest-file-read error: {r.get('error')}")
+ d = r["return"]
+ buf = base64.b64decode(d.get("buf-b64", ""))
+ if buf:
+ out.write(buf)
+ total += len(buf)
+ print(f"\r{total}", end="", flush=True)
+ if d.get("eof"):
+ break
+ cmd(s, {"execute": "guest-file-close", "arguments": {"handle": h}})
+except Exception as e: # noqa: BLE001
+ print(f"\nFETCH-FAIL {GUEST} -> {HOST}: {e}", file=sys.stderr)
+ try:
+ if os.path.exists(TMP):
+ os.unlink(TMP)
+ except OSError:
+ pass
+ sys.exit(1)
+finally:
+ try:
+ s.close()
+ except Exception:
+ pass
+
+os.replace(TMP, HOST)
print(f"\nFETCHED {total} bytes -> {HOST}")
+sys.exit(0)