功能:接入Linux原生光标并完善设备接入工具
This commit is contained in:
1 parent
a14f028402
commit
a47968496e
92 files changed
+3090
-22
No files matched your search
+44
-4
@@ -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")
|
||||
|
||||
Reference in new issue
Block a user