2 QEMU Console Socket Module:
4 This python module implements a ConsoleSocket object,
5 which can drain a socket and optionally dump the bytes to file.
7 # Copyright 2020 Linaro
10 # Robert Foley <robert.foley@linaro.org>
12 # This code is licensed under the GPL version 2 or later. See
13 # the COPYING file in the top-level directory.
16 from collections
import deque
20 from typing
import Deque
, Optional
23 class ConsoleSocket(socket
.socket
):
25 ConsoleSocket represents a socket attached to a char device.
27 Optionally (if drain==True), drains the socket and places the bytes
28 into an in memory buffer for later processing.
30 Optionally a file path can be passed in and we will also
31 dump the characters to this file for debugging purposes.
33 def __init__(self
, address
: str, file: Optional
[str] = None,
35 self
._recv
_timeout
_sec
= 300.0
36 self
._sleep
_time
= 0.5
37 self
._buffer
: Deque
[int] = deque()
38 socket
.socket
.__init
__(self
, socket
.AF_UNIX
, socket
.SOCK_STREAM
)
42 self
._logfile
= open(file, "bw")
44 self
._drain
_thread
= None
46 self
._drain
_thread
= self
._thread
_start
()
48 def _drain_fn(self
) -> None:
49 """Drains the socket and runs while the socket is open."""
53 except socket
.timeout
:
54 # The socket is expected to timeout since we set a
55 # short timeout to allow the thread to exit when
56 # self._open is set to False.
57 time
.sleep(self
._sleep
_time
)
59 def _thread_start(self
) -> threading
.Thread
:
60 """Kick off a thread to drain the socket."""
61 # Configure socket to not block and timeout.
62 # This allows our drain thread to not block
63 # on recieve and exit smoothly.
64 socket
.socket
.setblocking(self
, False)
65 socket
.socket
.settimeout(self
, 1)
66 drain_thread
= threading
.Thread(target
=self
._drain
_fn
)
67 drain_thread
.daemon
= True
71 def close(self
) -> None:
72 """Close the base object and wait for the thread to terminate"""
75 if self
._drain
_thread
is not None:
76 thread
, self
._drain
_thread
= self
._drain
_thread
, None
78 socket
.socket
.close(self
)
83 def _drain_socket(self
) -> None:
84 """process arriving characters into in memory _buffer"""
85 data
= socket
.socket
.recv(self
, 1)
87 self
._logfile
.write(data
)
89 self
._buffer
.extend(data
)
91 def recv(self
, bufsize
: int = 1, flags
: int = 0) -> bytes
:
92 """Return chars from in memory buffer.
93 Maintains the same API as socket.socket.recv.
95 if self
._drain
_thread
is None:
96 # Not buffering the socket, pass thru to socket.
97 return socket
.socket
.recv(self
, bufsize
, flags
)
98 assert not flags
, "Cannot pass flags to recv() in drained mode"
99 start_time
= time
.time()
100 while len(self
._buffer
) < bufsize
:
101 time
.sleep(self
._sleep
_time
)
102 elapsed_sec
= time
.time() - start_time
103 if elapsed_sec
> self
._recv
_timeout
_sec
:
105 return bytes((self
._buffer
.popleft() for i
in range(bufsize
)))
107 def setblocking(self
, value
: bool) -> None:
108 """When not draining we pass thru to the socket,
109 since when draining we control socket blocking.
111 if self
._drain
_thread
is None:
112 socket
.socket
.setblocking(self
, value
)
114 def settimeout(self
, value
: Optional
[float]) -> None:
115 """When not draining we pass thru to the socket,
116 since when draining we control the timeout.
118 if value
is not None:
119 self
._recv
_timeout
_sec
= value
120 if self
._drain
_thread
is None:
121 socket
.socket
.settimeout(self
, value
)