功能:接入Linux原生光标并完善设备接入工具

This commit is contained in:
songyiming committed 2026-11-05 19:18:54 +08:00
1 parent a14f028402
commit a47968496e
92 files changed
+3090 -22

No files matched your search

+44 -4
View File
@@ -116,13 +116,41 @@ def ensure(qmp, vm, port, bus, vendor=None, product=None):
attach(qmp, vm, port, bus, vendor, product)
def watch(vm, port, bus, vendor=None, product=None, interval=3):
"""Keep the runtime endpoint attached across VM restarts."""
def probe_status(port, host="127.0.0.1", timeout=0.7):
"""Return whether the box has an exportable storage device ready."""
try:
with socket.create_connection((host, port), timeout=timeout) as stream:
stream.settimeout(timeout)
return stream.recv(32).strip() == b"ready"
except OSError:
return False
def watch(vm, port, bus, vendor=None, product=None, interval=3,
status_port=None, remove_after=3):
"""Track the box device and keep the guest endpoint in sync.
With status_port, the QEMU endpoint is present only while the box reports
an exportable physical storage device. A short missing-device debounce
prevents a transient udev/export restart from causing a guest hot-unplug.
Without status_port this retains the legacy always-present behavior.
"""
missing = 0
while True:
try:
ready = True if status_port is None else probe_status(status_port)
qmp = Qmp(vm)
try:
ensure(qmp, vm, port, bus, vendor, product)
channel, device = ids(vm)
present = device in peripherals(qmp)
if ready:
missing = 0
if not present:
attach(qmp, vm, port, bus, vendor, product)
else:
missing += 1
if present and missing >= remove_after:
detach(qmp, vm)
finally:
qmp.close()
except (OSError, RuntimeError) as exc:
@@ -140,11 +168,21 @@ def main():
parser.add_argument("--product")
parser.add_argument("--any-storage", action="store_true",
help="Allow any pure USB Mass Storage device")
parser.add_argument("--status-port", type=int,
help="loopback port forwarded to the box readiness endpoint")
parser.add_argument("--interval", type=float, default=3,
help="watch polling interval in seconds")
parser.add_argument("--remove-after", type=int, default=3,
help="consecutive empty probes before guest removal")
parser.add_argument("--guest-ejected", action="store_true",
help="Confirm the guest has safely ejected the USB device before detach")
args = parser.parse_args()
if args.vm < 100 or not 1 <= args.port <= 65535:
parser.error("invalid VM ID or local tunnel port")
if args.status_port is not None and not 1 <= args.status_port <= 65535:
parser.error("invalid status port")
if args.interval <= 0 or args.remove_after < 1:
parser.error("interval must be positive and remove-after must be at least one")
if args.action in ("attach", "watch"):
exact = all(re.fullmatch(r"[0-9a-fA-F]{4}", x or "") for x in [args.vendor, args.product])
if args.any_storage and (args.vendor or args.product):
@@ -156,7 +194,9 @@ def main():
if args.action == "watch":
watch(args.vm, args.port, args.bus,
None if args.any_storage else args.vendor.lower(),
None if args.any_storage else args.product.lower())
None if args.any_storage else args.product.lower(),
interval=args.interval, status_port=args.status_port,
remove_after=args.remove_after)
return
if args.action == "detach" and not args.guest_ejected:
parser.error("safely eject in the guest first, then pass --guest-ejected")