Skip to content

Commit 03ea6f6

Browse files
committed
[#72940] manager: Enable raw mode in reverse shell
Previously, the shell simply read input line-by-line, which was good enough for running simple commands. For interactive applications to work properly, stdin needs to be put into raw mode to capture raw keypresses which are sent over the WS. Signed-off-by: Łukasz Kędziora <lkedziora@antmicro.com>
1 parent 05b5fcc commit 03ea6f6

File tree

1 file changed

+28
-5
lines changed

1 file changed

+28
-5
lines changed

manager/src/rdfm/reverse_shell.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
from rdfm.api import wrap_api_error
1010
import urllib
1111
import rdfm.ws
12+
import tty
13+
import termios
14+
import os
1215

1316

1417
def shell_ws_url(server_url: str, device: str) -> str:
@@ -99,6 +102,7 @@ def __init__(
99102
self.writer_thread = threading.Thread(
100103
target=self.__writer_thread, daemon=True
101104
)
105+
self.termattrs = None
102106

103107
def __reader_thread(self):
104108
"""WebSocket reader thread
@@ -117,25 +121,42 @@ def __reader_thread(self):
117121
):
118122
self.closed.set()
119123

124+
def __prepare_tty(self):
125+
"""Put the terminal into raw mode and preserve previous terminal
126+
attributes.
127+
"""
128+
fd = sys.stdin.fileno()
129+
if os.isatty(fd):
130+
self.termattrs = termios.tcgetattr(fd)
131+
tty.setraw(fd)
132+
133+
def __restore_tty(self):
134+
"""Restore the previous state of the terminal after shell was
135+
finished.
136+
"""
137+
fd = sys.stdin.fileno()
138+
if os.isatty(fd):
139+
termios.tcsetattr(fd, termios.TCSADRAIN, self.termattrs)
140+
120141
def __writer_thread(self):
121142
"""STDIN reader thread
122143
123144
This reads user input from STDIN and sends it to the shell WS
124145
"""
146+
fd = sys.stdin.fileno()
125147
try:
126-
# Avoid blocking on readline(). Otherwise, when the connection is
148+
# Avoid blocking on read(). Otherwise, when the connection is
127149
# already closed, the writer thread will be blocked in a kernel
128150
# call to read(). By avoiding the read call until we know data is
129151
# there, we can avoid an annoying input prompt when rdfm-mgmt is
130152
# closing.
131-
# FIXME: select does not allow for monitoring file fd's on Windows
132153
while True:
133-
r, _, _ = select.select([sys.stdin.fileno()], [], [], 1.0)
154+
r, _, _ = select.select([fd], [], [], 1.0)
134155
if len(r) > 0:
135-
data = sys.stdin.readline()
156+
data = os.read(fd, 4096)
136157
if len(data) == 0:
137158
break
138-
self.ws.send(data.encode())
159+
self.ws.send(data)
139160
if self.closed.is_set():
140161
break
141162
finally:
@@ -148,13 +169,15 @@ def run(self):
148169
(either by the server/device disconnecting, or user interrupting with
149170
Ctrl-C/Ctrl-D).
150171
"""
172+
self.__prepare_tty()
151173
self.reader_thread.start()
152174
self.writer_thread.start()
153175

154176
try:
155177
self.closed.wait()
156178
except (KeyboardInterrupt, EOFError):
157179
pass
180+
self.__restore_tty()
158181

159182
if self.ws.connected:
160183
# Normal exit (Ctrl-C / EOF on STDIN)

0 commit comments

Comments
 (0)