From 0634c145af32d17e2e272a30fba4c5421c23761f Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Sat, 8 Aug 2020 23:10:59 +0200 Subject: [PATCH 060/116] Make sure the output from custom_getpass() is serialized after stdout custom_getpass() writes the prompt to the file descriptor of the console where the last input came from. At the same time, run() races with it, piping the sys.stdout data to the same file descriptor (and others). To make matters worse, the stdout data is buffered and with nobody flushing it always loses the race, with important stuff being written out only after custom_getpass() returns and somebody cares to flush it out: Password: Password (confirm): Password: Password (confirm): ================================================================================ ================================================================================ Root password Please select new root password. You will have to type it twice. The passwords you entered were different. Please try again. ================================================================================ ================================================================================ Root password Please select new root password. You will have to type it twice. The password is too short This patch turns on line buffering, removing the necessity of explicit flushes from the stdout writer side. That alone wouldn't be sufficient because the stdout traffic could still be delayed until the piper thread is awoken; though things wouldn't be mixed up nearly as severely. To cope with this race the active console traffic is piped to the piper thread as well and it is now responsible for ordering things. It is still ugly but hey. --- initial_setup/tui/tui.py | 90 ++++++++++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 31 deletions(-) diff --git a/initial_setup/tui/tui.py b/initial_setup/tui/tui.py index 47f876d..347053e 100644 --- a/initial_setup/tui/tui.py +++ b/initial_setup/tui/tui.py @@ -34,9 +34,14 @@ class MultipleTTYHandler(object): self._tui_stdin_fd = tui_stdin_fd self._tui_stdin = os.fdopen(tui_stdin_fd, "w") + self._tui_active_out_fd, active_out_fd = os.pipe() + self._tui_active_out = os.fdopen(self._tui_active_out_fd, "r") + self._active_out = os.fdopen(active_out_fd, "w") + self._shutdown = False - self._active_console = None + self._active_console_in = None + self._active_console_out = None self._console_read_fos = {} self._console_write_fos = [] @@ -82,6 +87,7 @@ class MultipleTTYHandler(object): fds = list(self._console_read_fos.keys()) # as well as from the anaconda stdout fds.append(self._tui_stdout_fd) + fds.append(self._tui_active_out_fd) log.info("multi TTY handler starting") while True: # Watch the consoles and IS TUI stdout for data and @@ -93,41 +99,58 @@ class MultipleTTYHandler(object): if self._shutdown: log.info("multi TTY handler shutting down") break - for fd in rlist: - if fd == self._tui_stdout_fd: - # We need to set the TUI stdout fd to non-blocking, - # as otherwise reading from it would (predictably) result in - # the readline() function blocking once it runs out of data. - os.set_blocking(fd, False) - - # The IS TUI wants to write something, - # read all the lines. - lines = self._tui_stdout.readlines() - - # After we finish reading all the data we need to set - # the TUI stdout fd to blocking again. - # Otherwise the fd will not be usable when we try to read from - # it again for unclear reasons. - os.set_blocking(fd, True) - - lines.append("\n") # seems to get lost somewhere on the way - - # Write all the lines IS wrote to stdout to all consoles - # that we consider usable for the IS TUI. - for console_fo in self._console_write_fos.values(): - for one_line in lines: - try: - console_fo.write(one_line) - except OSError: - log.exception("failed to write %s to console %s", one_line, console_fo) - else: + if self._tui_stdout_fd in rlist: + # We need to set the TUI stdout fd to non-blocking, + # as otherwise reading from it would (predictably) result in + # the readline() function blocking once it runs out of data. + os.set_blocking(self._tui_stdout_fd, False) + + # The IS TUI wants to write something, + # read all the lines. + lines = self._tui_stdout.readlines() + + # After we finish reading all the data we need to set + # the TUI stdout fd to blocking again. + # Otherwise the fd will not be usable when we try to read from + # it again for unclear reasons. + os.set_blocking(self._tui_stdout_fd, True) + + lines.append("\n") # seems to get lost somewhere on the way + + # Write all the lines IS wrote to stdout to all consoles + # that we consider usable for the IS TUI. + for console_fo in self._console_write_fos.values(): + for one_line in lines: + try: + console_fo.write(one_line) + except OSError: + log.exception("failed to write %s to console %s", one_line, console_fo) + + # Don't go processing the events on other file descriptors until + # we're done with everything that's supposed to be on stdout + continue + elif self._tui_active_out_fd in rlist: + # Essentially the same as above but for the acrive console only + os.set_blocking(self._tui_active_out_fd, False) + lines = self._tui_active_out.readlines() + os.set_blocking(self._tui_active_out_fd, True) + write_fo = self._active_console_out + try: + for one_line in lines: + write_fo.write(one_line) + write_fo.flush() + except OSError: + log.exception("failed to write %s to active console", lines) + else: + for fd in rlist: # Someone typed some input to a console and hit enter, # forward the input to the IS TUI stdin. read_fo = self._console_read_fos[fd] write_fo = self._console_write_fos[fd] # as the console is getting input we consider it to be # the currently active console - self._active_console = read_fo, write_fo + self._active_console_in = read_fo + self._active_console_out = write_fo try: data = read_fo.readline() except TypeError: @@ -148,7 +171,10 @@ class MultipleTTYHandler(object): Always restores terminal settings before returning. """ - input_fo, output_fo = self._active_console + + input_fo = self._active_console_in + output_fo = self._active_out + passwd = None with contextlib.ExitStack() as stack: input_fd = input_fo.fileno() @@ -179,6 +205,7 @@ class MultipleTTYHandler(object): passwd = self._fallback_getpass(prompt, output_fo, input_fo) output_fo.write('\n') + output_fo.flush() return passwd def _fallback_getpass(self, prompt='Password: ', output_fo=None, input_fo=None): @@ -239,6 +266,7 @@ class InitialSetupTextUserInterface(TextUserInterface): # stdout tui_stdout_fd, stdout_fd = os.pipe() sys.stdout = os.fdopen(stdout_fd, "w") + sys.stdout.reconfigure(line_buffering=True) # instantiate and start the multi TTY handler self.multi_tty_handler = MultipleTTYHandler(tui_stdin_fd=tui_stdin_fd, tui_stdout_fd=tui_stdout_fd) -- 2.38.1.windows.1