Skip to content

Commit 854f9a0

Browse files
author
Andy C
committed
[translation] Rewrite and translate PushTermAttrs(), PopTermAttrs()
Add a repro bug #1629 in spec/stateful, and fix it.
1 parent 5b6b75b commit 854f9a0

File tree

5 files changed

+92
-42
lines changed

5 files changed

+92
-42
lines changed

core/pyos.py

+26-26
Original file line numberDiff line numberDiff line change
@@ -236,34 +236,34 @@ def PrintTimes():
236236
TERM_ECHO = termios.ECHO
237237

238238

239-
class TermState(object):
239+
def PushTermAttrs(fd, mask):
240+
# type: (int, int) -> Tuple[int, Any]
240241
"""
241-
TODO: Make this into a context manager which is a C++ destructor?
242+
Returns opaque type (void* in C++) to be reused in the PopTermAttrs()
242243
"""
243-
def __init__(self, fd, mask):
244-
# type: (int, int) -> None
245-
self.fd = fd
246-
247-
# silly way to make a copy
248-
# https://docs.python.org/2/library/termios.html
249-
self.orig_attrs = termios.tcgetattr(fd)
250-
term_attrs = termios.tcgetattr(fd)
251-
252-
a3 = cast(int, term_attrs[3])
253-
# Disable canonical (buffered) mode. See `man termios` for an extended
254-
# discussion.
255-
term_attrs[3] = a3 & mask
256-
termios.tcsetattr(self.fd, termios.TCSANOW, term_attrs)
257-
258-
def Restore(self):
259-
# type: () -> None
260-
try:
261-
termios.tcsetattr(self.fd, termios.TCSANOW, self.orig_attrs)
262-
except termios.error as e:
263-
# Superficial fix for issue #1001. I'm not sure why we get errno.EIO,
264-
# but we can't really handle it here. In C++ I guess we ignore the
265-
# error.
266-
pass
244+
# https://docs.python.org/2/library/termios.html
245+
term_attrs = termios.tcgetattr(fd)
246+
247+
# Flip the bits in one field, e.g. ICANON to disable canonical (buffered)
248+
# mode.
249+
orig_local_modes = cast(int, term_attrs[3])
250+
term_attrs[3] = orig_local_modes & mask
251+
252+
termios.tcsetattr(fd, termios.TCSANOW, term_attrs)
253+
return orig_local_modes, term_attrs
254+
255+
256+
def PopTermAttrs(fd, orig_local_modes, term_attrs):
257+
# type: (int, int, Any) -> None
258+
259+
term_attrs[3] = orig_local_modes
260+
try:
261+
termios.tcsetattr(fd, termios.TCSANOW, term_attrs)
262+
except termios.error as e:
263+
# Superficial fix for issue #1001. I'm not sure why we get errno.EIO,
264+
# but we can't really handle it here. In C++ I guess we ignore the
265+
# error.
266+
pass
267267

268268

269269
def OsType():

cpp/core.cc

+27
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <sys/times.h> // tms / times()
1515
#include <sys/utsname.h> // uname
1616
#include <sys/wait.h> // waitpid()
17+
#include <termios.h> // tcgetattr(), tcsetattr()
1718
#include <time.h> // time()
1819
#include <unistd.h> // getuid(), environ
1920

@@ -265,6 +266,32 @@ Tuple2<Str*, int>* MakeDirCacheKey(Str* path) {
265266
return Alloc<Tuple2<Str*, int>>(path, st.st_mtime);
266267
}
267268

269+
Tuple2<int, void*> PushTermAttrs(int fd, int mask) {
270+
struct termios* term_attrs =
271+
static_cast<struct termios*>(malloc(sizeof(struct termios)));
272+
273+
if (tcgetattr(fd, term_attrs) < 0) {
274+
throw Alloc<OSError>(errno);
275+
}
276+
// Flip the bits in one field
277+
int orig_local_modes = term_attrs->c_lflag;
278+
term_attrs->c_lflag = orig_local_modes & mask;
279+
280+
if (tcsetattr(fd, TCSANOW, term_attrs) < 0) {
281+
throw Alloc<OSError>(errno);
282+
}
283+
284+
return Tuple2<int, void*>(orig_local_modes, term_attrs);
285+
}
286+
287+
void PopTermAttrs(int fd, int orig_local_modes, void* term_attrs) {
288+
struct termios* t = static_cast<struct termios*>(term_attrs);
289+
t->c_lflag = orig_local_modes;
290+
if (tcsetattr(fd, TCSANOW, t) < 0) {
291+
; // Like Python, ignore error because of issue #1001
292+
}
293+
}
294+
268295
} // namespace pyos
269296

270297
namespace pyutil {

cpp/core.h

+2-12
Original file line numberDiff line numberDiff line change
@@ -94,18 +94,8 @@ inline void FlushStdout() {
9494
fflush(stdout);
9595
}
9696

97-
class TermState {
98-
public:
99-
TermState(int fd, int mask) {
100-
assert(0);
101-
}
102-
void Restore() {
103-
assert(0);
104-
}
105-
static constexpr ObjHeader obj_header() {
106-
return ObjHeader::ClassFixed(kZeroMask, sizeof(TermState));
107-
}
108-
};
97+
Tuple2<int, void*> PushTermAttrs(int fd, int mask);
98+
void PopTermAttrs(int fd, int orig_local_modes, void* term_attrs);
10999

110100
// Make the signal queue slab 4096 bytes, including the GC header. See
111101
// cpp/core_test.cc.

osh/builtin_misc.py

+22-4
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,27 @@ def ReadAll():
260260
return ''.join(chunks)
261261

262262

263+
class ctx_TermAttrs(object):
264+
265+
def __init__(self, fd, local_modes):
266+
# type: (int, int) -> None
267+
self.fd = fd
268+
269+
# We change term_attrs[3] in Python, which is lflag "local modes"
270+
orig_local_modes, term_attrs = pyos.PushTermAttrs(fd, local_modes)
271+
272+
self.orig_local_modes = orig_local_modes
273+
self.term_attrs = term_attrs
274+
275+
def __enter__(self):
276+
# type: () -> None
277+
pass
278+
279+
def __exit__(self, type, value, traceback):
280+
# type: (Any, Any, Any) -> None
281+
pyos.PopTermAttrs(self.fd, self.orig_local_modes, self.term_attrs)
282+
283+
263284
class Read(vm._Builtin):
264285

265286
def __init__(self, splitter, mem, parse_ctx, cmd_ev, errfmt):
@@ -399,11 +420,8 @@ def _Run(self, cmd_val):
399420
if bits == 0:
400421
status = self._Read(arg, names)
401422
else:
402-
term = pyos.TermState(STDIN_FILENO, ~bits)
403-
try:
423+
with ctx_TermAttrs(STDIN_FILENO, ~bits):
404424
status = self._Read(arg, names)
405-
finally:
406-
term.Restore()
407425
return status
408426

409427
def _Read(self, arg, names):

spec/stateful/signals.py

+15
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,21 @@ def t2(sh):
316316
sh.expect('status=130')
317317

318318

319+
@register()
320+
def read_d(sh):
321+
'Ctrl-C during read -d'
322+
323+
sh.sendline('read -d :')
324+
325+
time.sleep(0.1)
326+
sh.sendintr() # SIGINT
327+
328+
expect_prompt(sh)
329+
330+
sh.sendline('echo status=$?')
331+
sh.expect('status=130')
332+
333+
319334
@register()
320335
def c_wait(sh):
321336
'Ctrl-C (untrapped) during wait builtin'

0 commit comments

Comments
 (0)