Fix pause module so it does not stack trace when redirecting stdout. (#42217)

* Use separate variables for stdin and stdout file descriptors

* Do not set stdout to raw mode when output is not a TTY
This commit is contained in:
Sam Doran 2018-07-06 17:19:55 -04:00 committed by GitHub
parent 39ec12f395
commit 1d1595b990
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 34 additions and 18 deletions

View file

@ -0,0 +1,2 @@
bugfixes:
- pause - do not set stdout to raw mode when redirecting to a file (https://github.com/ansible/ansible/issues/41717)

View file

@ -143,7 +143,7 @@ class ActionModule(ActionBase):
result['start'] = to_text(datetime.datetime.now()) result['start'] = to_text(datetime.datetime.now())
result['user_input'] = b'' result['user_input'] = b''
fd = None stdin_fd = None
old_settings = None old_settings = None
try: try:
if seconds is not None: if seconds is not None:
@ -167,7 +167,8 @@ class ActionModule(ActionBase):
# save the attributes on the existing (duped) stdin so # save the attributes on the existing (duped) stdin so
# that we can restore them later after we set raw mode # that we can restore them later after we set raw mode
fd = None stdin_fd = None
stdout_fd = None
try: try:
if PY3: if PY3:
stdin = self._connection._new_stdin.buffer stdin = self._connection._new_stdin.buffer
@ -175,52 +176,59 @@ class ActionModule(ActionBase):
else: else:
stdin = self._connection._new_stdin stdin = self._connection._new_stdin
stdout = sys.stdout stdout = sys.stdout
fd = stdin.fileno() stdin_fd = stdin.fileno()
stdout_fd = stdout.fileno()
except (ValueError, AttributeError): except (ValueError, AttributeError):
# ValueError: someone is using a closed file descriptor as stdin # ValueError: someone is using a closed file descriptor as stdin
# AttributeError: someone is using a null file descriptor as stdin on windoez # AttributeError: someone is using a null file descriptor as stdin on windoez
stdin = None stdin = None
if fd is not None: if stdin_fd is not None:
if isatty(fd): if isatty(stdin_fd):
# grab actual Ctrl+C sequence # grab actual Ctrl+C sequence
try: try:
intr = termios.tcgetattr(fd)[6][termios.VINTR] intr = termios.tcgetattr(stdin_fd)[6][termios.VINTR]
except Exception: except Exception:
# unsupported/not present, use default # unsupported/not present, use default
intr = b'\x03' # value for Ctrl+C intr = b'\x03' # value for Ctrl+C
# get backspace sequences # get backspace sequences
try: try:
backspace = termios.tcgetattr(fd)[6][termios.VERASE] backspace = termios.tcgetattr(stdin_fd)[6][termios.VERASE]
except Exception: except Exception:
backspace = [b'\x7f', b'\x08'] backspace = [b'\x7f', b'\x08']
old_settings = termios.tcgetattr(fd) old_settings = termios.tcgetattr(stdin_fd)
tty.setraw(fd) tty.setraw(stdin_fd)
tty.setraw(stdout.fileno())
# Only set stdout to raw mode if it is a TTY. This is needed when redirecting
# stdout to a file since a file cannot be set to raw mode.
if isatty(stdout_fd):
tty.setraw(stdout_fd)
# Only echo input if no timeout is specified # Only echo input if no timeout is specified
if not seconds and echo: if not seconds and echo:
new_settings = termios.tcgetattr(fd) new_settings = termios.tcgetattr(stdin_fd)
new_settings[3] = new_settings[3] | termios.ECHO new_settings[3] = new_settings[3] | termios.ECHO
termios.tcsetattr(fd, termios.TCSANOW, new_settings) termios.tcsetattr(stdin_fd, termios.TCSANOW, new_settings)
# flush the buffer to make sure no previous key presses # flush the buffer to make sure no previous key presses
# are read in below # are read in below
termios.tcflush(stdin, termios.TCIFLUSH) termios.tcflush(stdin, termios.TCIFLUSH)
while True: while True:
try: try:
if fd is not None: if stdin_fd is not None:
key_pressed = stdin.read(1) key_pressed = stdin.read(1)
if key_pressed == intr: # value for Ctrl+C if key_pressed == intr: # value for Ctrl+C
clear_line(stdout) clear_line(stdout)
raise KeyboardInterrupt raise KeyboardInterrupt
if not seconds: if not seconds:
if fd is None or not isatty(fd): if stdin_fd is None or not isatty(stdin_fd):
display.warning("Not waiting for response to prompt as stdin is not interactive") display.warning("Not waiting for response to prompt as stdin is not interactive")
break break
@ -255,9 +263,9 @@ class ActionModule(ActionBase):
pass pass
finally: finally:
# cleanup and save some information # cleanup and save some information
# restore the old settings for the duped stdin fd # restore the old settings for the duped stdin stdin_fd
if not(None in (fd, old_settings)) and isatty(fd): if not(None in (stdin_fd, old_settings)) and isatty(stdin_fd):
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) termios.tcsetattr(stdin_fd, termios.TCSADRAIN, old_settings)
duration = time.time() - start duration = time.time() - start
result['stop'] = to_text(datetime.datetime.now()) result['stop'] = to_text(datetime.datetime.now())

View file

@ -15,6 +15,12 @@ ansible-playbook test-pause-no-tty.yml -i ../../inventory 2>&1 | \
} }
EOF EOF
# Test redirecting stdout
# Issue #41717
ansible-playbook pause-3.yml -i ../../inventory > /dev/null \
&& echo "Successfully redirected stdout" \
|| echo "Failure when attempting to redirect stdout"
# Test pause with seconds and minutes specified # Test pause with seconds and minutes specified
ansible-playbook test-pause.yml -i ../../inventory "$@" ansible-playbook test-pause.yml -i ../../inventory "$@"