| /* Copyright (C) 2023 Free Software Foundation, Inc. | 
 |  | 
 |    This file is part of GDB. | 
 |  | 
 |    This program is free software; you can redistribute it and/or modify | 
 |    it under the terms of the GNU General Public License as published by | 
 |    the Free Software Foundation; either version 3 of the License, or | 
 |    (at your option) any later version. | 
 |  | 
 |    This program is distributed in the hope that it will be useful, | 
 |    but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 |    GNU General Public License for more details. | 
 |  | 
 |    You should have received a copy of the GNU General Public License | 
 |    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ | 
 |  | 
 | #include "defs.h" | 
 | #include "ui.h" | 
 |  | 
 | #include "cli/cli-cmds.h" | 
 | #include "event-top.h" | 
 | #include "gdbsupport/buildargv.h" | 
 | #include "gdbsupport/filestuff.h" | 
 | #include "gdbsupport/gdb_file.h" | 
 | #include "gdbsupport/scoped_fd.h" | 
 | #include "interps.h" | 
 | #include "pager.h" | 
 | #include "main.h" | 
 | #include "top.h" | 
 |  | 
 | /* See top.h.  */ | 
 |  | 
 | struct ui *main_ui; | 
 | struct ui *current_ui; | 
 | struct ui *ui_list; | 
 |  | 
 | /* The highest UI number ever assigned.  */ | 
 |  | 
 | static int highest_ui_num; | 
 |  | 
 | /* See top.h.  */ | 
 |  | 
 | ui::ui (FILE *instream_, FILE *outstream_, FILE *errstream_) | 
 |   : num (++highest_ui_num), | 
 |     stdin_stream (instream_), | 
 |     instream (instream_), | 
 |     outstream (outstream_), | 
 |     errstream (errstream_), | 
 |     input_fd (fileno (instream)), | 
 |     m_input_interactive_p (ISATTY (instream)), | 
 |     m_gdb_stdout (new pager_file (new stdio_file (outstream))), | 
 |     m_gdb_stdin (new stdio_file (instream)), | 
 |     m_gdb_stderr (new stderr_file (errstream)), | 
 |     m_gdb_stdlog (new timestamped_file (m_gdb_stderr)) | 
 | { | 
 |   unbuffer_stream (instream_); | 
 |  | 
 |   if (ui_list == NULL) | 
 |     ui_list = this; | 
 |   else | 
 |     { | 
 |       struct ui *last; | 
 |  | 
 |       for (last = ui_list; last->next != NULL; last = last->next) | 
 | 	; | 
 |       last->next = this; | 
 |     } | 
 | } | 
 |  | 
 | ui::~ui () | 
 | { | 
 |   struct ui *ui, *uiprev; | 
 |  | 
 |   uiprev = NULL; | 
 |  | 
 |   for (ui = ui_list; ui != NULL; uiprev = ui, ui = ui->next) | 
 |     if (ui == this) | 
 |       break; | 
 |  | 
 |   gdb_assert (ui != NULL); | 
 |  | 
 |   if (uiprev != NULL) | 
 |     uiprev->next = next; | 
 |   else | 
 |     ui_list = next; | 
 |  | 
 |   delete m_gdb_stdin; | 
 |   delete m_gdb_stdout; | 
 |   delete m_gdb_stderr; | 
 | } | 
 |  | 
 |  | 
 | /* Returns whether GDB is running on an interactive terminal.  */ | 
 |  | 
 | bool | 
 | ui::input_interactive_p () const | 
 | { | 
 |   if (batch_flag) | 
 |     return false; | 
 |  | 
 |   if (interactive_mode != AUTO_BOOLEAN_AUTO) | 
 |     return interactive_mode == AUTO_BOOLEAN_TRUE; | 
 |  | 
 |   return m_input_interactive_p; | 
 | } | 
 |  | 
 |  | 
 | /* When there is an event ready on the stdin file descriptor, instead | 
 |    of calling readline directly throught the callback function, or | 
 |    instead of calling gdb_readline_no_editing_callback, give gdb a | 
 |    chance to detect errors and do something.  */ | 
 |  | 
 | static void | 
 | stdin_event_handler (int error, gdb_client_data client_data) | 
 | { | 
 |   struct ui *ui = (struct ui *) client_data; | 
 |  | 
 |   if (error) | 
 |     { | 
 |       /* Switch to the main UI, so diagnostics always go there.  */ | 
 |       current_ui = main_ui; | 
 |  | 
 |       ui->unregister_file_handler (); | 
 |       if (main_ui == ui) | 
 | 	{ | 
 | 	  /* If stdin died, we may as well kill gdb.  */ | 
 | 	  gdb_printf (gdb_stderr, _("error detected on stdin\n")); | 
 | 	  quit_command ((char *) 0, 0); | 
 | 	} | 
 |       else | 
 | 	{ | 
 | 	  /* Simply delete the UI.  */ | 
 | 	  delete ui; | 
 | 	} | 
 |     } | 
 |   else | 
 |     { | 
 |       /* Switch to the UI whose input descriptor woke up the event | 
 | 	 loop.  */ | 
 |       current_ui = ui; | 
 |  | 
 |       /* This makes sure a ^C immediately followed by further input is | 
 | 	 always processed in that order.  E.g,. with input like | 
 | 	 "^Cprint 1\n", the SIGINT handler runs, marks the async | 
 | 	 signal handler, and then select/poll may return with stdin | 
 | 	 ready, instead of -1/EINTR.  The | 
 | 	 gdb.base/double-prompt-target-event-error.exp test exercises | 
 | 	 this.  */ | 
 |       QUIT; | 
 |  | 
 |       do | 
 | 	{ | 
 | 	  call_stdin_event_handler_again_p = 0; | 
 | 	  ui->call_readline (client_data); | 
 | 	} | 
 |       while (call_stdin_event_handler_again_p != 0); | 
 |     } | 
 | } | 
 |  | 
 | /* See top.h.  */ | 
 |  | 
 | void | 
 | ui::register_file_handler () | 
 | { | 
 |   if (input_fd != -1) | 
 |     add_file_handler (input_fd, stdin_event_handler, this, | 
 | 		      string_printf ("ui-%d", num), true); | 
 | } | 
 |  | 
 | /* See top.h.  */ | 
 |  | 
 | void | 
 | ui::unregister_file_handler () | 
 | { | 
 |   if (input_fd != -1) | 
 |     delete_file_handler (input_fd); | 
 | } | 
 |  | 
 | /* Open file named NAME for read/write, making sure not to make it the | 
 |    controlling terminal.  */ | 
 |  | 
 | static gdb_file_up | 
 | open_terminal_stream (const char *name) | 
 | { | 
 |   scoped_fd fd = gdb_open_cloexec (name, O_RDWR | O_NOCTTY, 0); | 
 |   if (fd.get () < 0) | 
 |     perror_with_name  (_("opening terminal failed")); | 
 |  | 
 |   return fd.to_file ("w+"); | 
 | } | 
 |  | 
 | /* Implementation of the "new-ui" command.  */ | 
 |  | 
 | static void | 
 | new_ui_command (const char *args, int from_tty) | 
 | { | 
 |   int argc; | 
 |   const char *interpreter_name; | 
 |   const char *tty_name; | 
 |  | 
 |   dont_repeat (); | 
 |  | 
 |   gdb_argv argv (args); | 
 |   argc = argv.count (); | 
 |  | 
 |   if (argc < 2) | 
 |     error (_("Usage: new-ui INTERPRETER TTY")); | 
 |  | 
 |   interpreter_name = argv[0]; | 
 |   tty_name = argv[1]; | 
 |  | 
 |   { | 
 |     scoped_restore save_ui = make_scoped_restore (¤t_ui); | 
 |  | 
 |     /* Open specified terminal.  Note: we used to open it three times, | 
 |        once for each of stdin/stdout/stderr, but that does not work | 
 |        with Windows named pipes.  */ | 
 |     gdb_file_up stream = open_terminal_stream (tty_name); | 
 |  | 
 |     std::unique_ptr<ui> ui | 
 |       (new struct ui (stream.get (), stream.get (), stream.get ())); | 
 |  | 
 |     ui->async = 1; | 
 |  | 
 |     current_ui = ui.get (); | 
 |  | 
 |     set_top_level_interpreter (interpreter_name); | 
 |  | 
 |     interp_pre_command_loop (top_level_interpreter ()); | 
 |  | 
 |     /* Make sure the file is not closed.  */ | 
 |     stream.release (); | 
 |  | 
 |     ui.release (); | 
 |   } | 
 |  | 
 |   gdb_printf ("New UI allocated\n"); | 
 | } | 
 |  | 
 | void _initialize_ui (); | 
 | void | 
 | _initialize_ui () | 
 | { | 
 |   cmd_list_element *c = add_cmd ("new-ui", class_support, new_ui_command, _("\ | 
 | Create a new UI.\n\ | 
 | Usage: new-ui INTERPRETER TTY\n\ | 
 | The first argument is the name of the interpreter to run.\n\ | 
 | The second argument is the terminal the UI runs on."), &cmdlist); | 
 |   set_cmd_completer (c, interpreter_completer); | 
 | } |