aboutsummaryrefslogtreecommitdiff
path: root/python/qemu/machine/console_socket.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/qemu/machine/console_socket.py')
-rw-r--r--python/qemu/machine/console_socket.py129
1 files changed, 129 insertions, 0 deletions
diff --git a/python/qemu/machine/console_socket.py b/python/qemu/machine/console_socket.py
new file mode 100644
index 0000000..8c4ff59
--- /dev/null
+++ b/python/qemu/machine/console_socket.py
@@ -0,0 +1,129 @@
+"""
+QEMU Console Socket Module:
+
+This python module implements a ConsoleSocket object,
+which can drain a socket and optionally dump the bytes to file.
+"""
+# Copyright 2020 Linaro
+#
+# Authors:
+# Robert Foley <robert.foley@linaro.org>
+#
+# This code is licensed under the GPL version 2 or later. See
+# the COPYING file in the top-level directory.
+#
+
+from collections import deque
+import socket
+import threading
+import time
+from typing import Deque, Optional
+
+
+class ConsoleSocket(socket.socket):
+ """
+ ConsoleSocket represents a socket attached to a char device.
+
+ Optionally (if drain==True), drains the socket and places the bytes
+ into an in memory buffer for later processing.
+
+ Optionally a file path can be passed in and we will also
+ dump the characters to this file for debugging purposes.
+ """
+ def __init__(self, address: str, file: Optional[str] = None,
+ drain: bool = False):
+ self._recv_timeout_sec = 300.0
+ self._sleep_time = 0.5
+ self._buffer: Deque[int] = deque()
+ socket.socket.__init__(self, socket.AF_UNIX, socket.SOCK_STREAM)
+ self.connect(address)
+ self._logfile = None
+ if file:
+ # pylint: disable=consider-using-with
+ self._logfile = open(file, "bw")
+ self._open = True
+ self._drain_thread = None
+ if drain:
+ self._drain_thread = self._thread_start()
+
+ def __repr__(self) -> str:
+ tmp = super().__repr__()
+ tmp = tmp.rstrip(">")
+ tmp = "%s, logfile=%s, drain_thread=%s>" % (tmp, self._logfile,
+ self._drain_thread)
+ return tmp
+
+ def _drain_fn(self) -> None:
+ """Drains the socket and runs while the socket is open."""
+ while self._open:
+ try:
+ self._drain_socket()
+ except socket.timeout:
+ # The socket is expected to timeout since we set a
+ # short timeout to allow the thread to exit when
+ # self._open is set to False.
+ time.sleep(self._sleep_time)
+
+ def _thread_start(self) -> threading.Thread:
+ """Kick off a thread to drain the socket."""
+ # Configure socket to not block and timeout.
+ # This allows our drain thread to not block
+ # on recieve and exit smoothly.
+ socket.socket.setblocking(self, False)
+ socket.socket.settimeout(self, 1)
+ drain_thread = threading.Thread(target=self._drain_fn)
+ drain_thread.daemon = True
+ drain_thread.start()
+ return drain_thread
+
+ def close(self) -> None:
+ """Close the base object and wait for the thread to terminate"""
+ if self._open:
+ self._open = False
+ if self._drain_thread is not None:
+ thread, self._drain_thread = self._drain_thread, None
+ thread.join()
+ socket.socket.close(self)
+ if self._logfile:
+ self._logfile.close()
+ self._logfile = None
+
+ def _drain_socket(self) -> None:
+ """process arriving characters into in memory _buffer"""
+ data = socket.socket.recv(self, 1)
+ if self._logfile:
+ self._logfile.write(data)
+ self._logfile.flush()
+ self._buffer.extend(data)
+
+ def recv(self, bufsize: int = 1, flags: int = 0) -> bytes:
+ """Return chars from in memory buffer.
+ Maintains the same API as socket.socket.recv.
+ """
+ if self._drain_thread is None:
+ # Not buffering the socket, pass thru to socket.
+ return socket.socket.recv(self, bufsize, flags)
+ assert not flags, "Cannot pass flags to recv() in drained mode"
+ start_time = time.time()
+ while len(self._buffer) < bufsize:
+ time.sleep(self._sleep_time)
+ elapsed_sec = time.time() - start_time
+ if elapsed_sec > self._recv_timeout_sec:
+ raise socket.timeout
+ return bytes((self._buffer.popleft() for i in range(bufsize)))
+
+ def setblocking(self, value: bool) -> None:
+ """When not draining we pass thru to the socket,
+ since when draining we control socket blocking.
+ """
+ if self._drain_thread is None:
+ socket.socket.setblocking(self, value)
+
+ def settimeout(self, value: Optional[float]) -> None:
+ """When not draining we pass thru to the socket,
+ since when draining we control the timeout.
+ """
+ if value is not None:
+ self._recv_timeout_sec = value
+ if self._drain_thread is None:
+ socket.socket.settimeout(self, value)