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 __repr__(self
) -> str:
49 s
= super().__repr
__()
51 s
= "%s, logfile=%s, drain_thread=%s>" % (s
, self
._logfile
,
55 def _drain_fn(self
) -> None:
56 """Drains the socket and runs while the socket is open."""
60 except socket
.timeout
:
61 # The socket is expected to timeout since we set a
62 # short timeout to allow the thread to exit when
63 # self._open is set to False.
64 time
.sleep(self
._sleep
_time
)
66 def _thread_start(self
) -> threading
.Thread
:
67 """Kick off a thread to drain the socket."""
68 # Configure socket to not block and timeout.
69 # This allows our drain thread to not block
70 # on recieve and exit smoothly.
71 socket
.socket
.setblocking(self
, False)
72 socket
.socket
.settimeout(self
, 1)
73 drain_thread
= threading
.Thread(target
=self
._drain
_fn
)
74 drain_thread
.daemon
= True
78 def close(self
) -> None:
79 """Close the base object and wait for the thread to terminate"""
82 if self
._drain
_thread
is not None:
83 thread
, self
._drain
_thread
= self
._drain
_thread
, None
85 socket
.socket
.close(self
)
90 def _drain_socket(self
) -> None:
91 """process arriving characters into in memory _buffer"""
92 data
= socket
.socket
.recv(self
, 1)
94 self
._logfile
.write(data
)
96 self
._buffer
.extend(data
)
98 def recv(self
, bufsize
: int = 1, flags
: int = 0) -> bytes
:
99 """Return chars from in memory buffer.
100 Maintains the same API as socket.socket.recv.
102 if self
._drain
_thread
is None:
103 # Not buffering the socket, pass thru to socket.
104 return socket
.socket
.recv(self
, bufsize
, flags
)
105 assert not flags
, "Cannot pass flags to recv() in drained mode"
106 start_time
= time
.time()
107 while len(self
._buffer
) < bufsize
:
108 time
.sleep(self
._sleep
_time
)
109 elapsed_sec
= time
.time() - start_time
110 if elapsed_sec
> self
._recv
_timeout
_sec
:
112 return bytes((self
._buffer
.popleft() for i
in range(bufsize
)))
114 def setblocking(self
, value
: bool) -> None:
115 """When not draining we pass thru to the socket,
116 since when draining we control socket blocking.
118 if self
._drain
_thread
is None:
119 socket
.socket
.setblocking(self
, value
)
121 def settimeout(self
, value
: Optional
[float]) -> None:
122 """When not draining we pass thru to the socket,
123 since when draining we control the timeout.
125 if value
is not None:
126 self
._recv
_timeout
_sec
= value
127 if self
._drain
_thread
is None:
128 socket
.socket
.settimeout(self
, value
)