[patch] MI telnet service

Abid, Hafiz
2012-07-12T11:21:17Z

[-- Attachment #1: Type: text/plain, Size: 1805 bytes --]

Hi,
This patch provides implementation of telnet service. This is based on initial work by Grigory Tolstolytkin.(http://sourceware.org/ml/gdb-patches/2011-11/msg00466.html)

This service can accept and execute CLI commands from the remote user. It can be started by MI command "-start-telnet-service [port]". After the service is running, user can connect to the GDB via telnet and execute CLI commands in parallel with existing MI interface. This service can be stopped by another MI command "-stop-telnet-service". For these commands to work, GDB has to be configured with --enable-gdbmitel=yes.

This service can be useful, for example, when GDB is running as a backend of an IDE. Testing infrastructure can run the tests by sending commands through socket. In case of an error, a user can debug the problem in the IDE.

The test case that I added only checks that port is opened after the service is started. I am looking to improve it and would appreciate if the reviewers can help me with some ideas.

How does it all look?

Regards,
Abid

2012-07-12 Hafiz Abid Qadeer <abidh@codesourcery.com>

* Makefile.in: Variable definitions related to mi-telnet.c.
(mi-telnet.o): New rule.
* configure.ac: Add AC_ARG_ENABLE(gdbmitel).
* configure: Generated.
* mi/mi-cmds.c (mi_cmds): Add -start-telnet-service
and stop-telnet-service MI commands.
* mi/mi-cmds.h (mi_cmd_start_telnet_service): Add declaration.
(mi_cmd_stop_telnet_service): Add declaration.
* mi/mi-telnet.c: New file.
* gdb/testsuite:
gdb.mi/mi-telnet.exp: New file
gdb.mi/mi-telnet.c: New file
* gdb.texinfo (Miscellaneous GDB/MI Commands): Added
-start-telnet-service and -stop-telnet-service.

[-- Attachment #2: mi-telnet.patch --]
[-- Type: application/octet-stream, Size: 29537 bytes --]

Index: gdb/Makefile.in
===================================================================
RCS file: /cvs/src/src/gdb/Makefile.in,v
retrieving revision 1.1210
diff -u -r1.1210 Makefile.in
--- gdb/Makefile.in 2 Jul 2012 15:29:33 -0000 1.1210
+++ gdb/Makefile.in 12 Jul 2012 10:35:11 -0000
@@ -216,6 +216,15 @@
SUBDIR_MI_CFLAGS=

#
+# MI telnet definitions
+#
+SUBDIR_MITEL_OBS = mi-telnet.o
+SUBDIR_MITEL_SRCS = mi/mi-telnet.c
+SUBDIR_MITEL_DEPS=
+SUBDIR_MITEL_LDFLAGS=
+SUBDIR_MITEL_CFLAGS= -DENABLE_GDBMITEL
+
+#
# TUI sub directory definitions
#

@@ -1894,6 +1903,10 @@
$(COMPILE) $(srcdir)/mi/mi-common.c
$(POSTCOMPILE)

+mi-telnet.o: $(srcdir)/mi/mi-telnet.c
+ $(COMPILE) $(srcdir)/mi/mi-telnet.c
+ $(POSTCOMPILE)
+
# gdb/common/ dependencies
#
# Need to explicitly specify the compile rule as make will do nothing
Index: gdb/configure
===================================================================
RCS file: /cvs/src/src/gdb/configure,v
retrieving revision 1.370
diff -u -r1.370 configure
--- gdb/configure 2 Jul 2012 20:31:07 -0000 1.370
+++ gdb/configure 12 Jul 2012 10:35:13 -0000
@@ -784,6 +784,7 @@
enable_64_bit_bfd
enable_gdbcli
enable_gdbmi
+enable_gdbmitel
enable_tui
enable_gdbtk
with_libunwind_ia64
@@ -1462,6 +1463,7 @@
--enable-64-bit-bfd 64-bit support (on hosts with narrower word sizes)
--disable-gdbcli disable command-line interface (CLI)
--disable-gdbmi disable machine-interface (MI)
+ --enable-gdbmitel enable remote interface over telnet
--enable-tui enable full-screen terminal user interface (TUI)
--enable-gdbtk enable gdbtk graphical user interface (GUI)
--enable-profiling enable profiling of GDB
@@ -5237,6 +5239,28 @@
fi
fi

+# Enable MI telnet.
+# Check whether --enable-gdbmitel was given.
+if test "${enable_gdbmitel+set}" = set; then :
+ enableval=$enable_gdbmitel; case $enableval in
+ yes | no)
+ ;;
+ *)
+ as_fn_error "bad value $enableval for --enable-gdbmitel" "$LINENO" 5 ;;
+ esac
+else
+ enable_gdbmitel=no
+fi
+
+if test x"$enable_gdbmitel" = xyes; then
+ if test -d $srcdir/mi; then
+ CONFIG_OBS="$CONFIG_OBS \$(SUBDIR_MITEL_OBS)"
+ CONFIG_DEPS="$CONFIG_DEPS \$(SUBDIR_MITEL_DEPS)"
+ CONFIG_SRCS="$CONFIG_SRCS \$(SUBDIR_MITEL_SRCS)"
+ ENABLE_CFLAGS="$ENABLE_CFLAGS \$(SUBDIR_MITEL_CFLAGS)"
+ fi
+fi
+
# Enable TUI.
# Check whether --enable-tui was given.
if test "${enable_tui+set}" = set; then :
Index: gdb/configure.ac
===================================================================
RCS file: /cvs/src/src/gdb/configure.ac,v
retrieving revision 1.181
diff -u -r1.181 configure.ac
--- gdb/configure.ac 2 Jul 2012 20:31:09 -0000 1.181
+++ gdb/configure.ac 12 Jul 2012 10:35:13 -0000
@@ -329,6 +329,25 @@
fi
fi

+# Enable MI telnet.
+AC_ARG_ENABLE(gdbmitel,
+AS_HELP_STRING([--enable-gdbmitel], [enable remote interface over telnet]),
+ [case $enableval in
+ yes | no)
+ ;;
+ *)
+ AC_MSG_ERROR([bad value $enableval for --enable-gdbmitel]) ;;
+ esac],
+ [enable_gdbmitel=no])
+if test x"$enable_gdbmitel" = xyes; then
+ if test -d $srcdir/mi; then
+ CONFIG_OBS="$CONFIG_OBS \$(SUBDIR_MITEL_OBS)"
+ CONFIG_DEPS="$CONFIG_DEPS \$(SUBDIR_MITEL_DEPS)"
+ CONFIG_SRCS="$CONFIG_SRCS \$(SUBDIR_MITEL_SRCS)"
+ ENABLE_CFLAGS="$ENABLE_CFLAGS \$(SUBDIR_MITEL_CFLAGS)"
+ fi
+fi
+
# Enable TUI.
AC_ARG_ENABLE(tui,
AS_HELP_STRING([--enable-tui], [enable full-screen terminal user interface (TUI)]),
Index: gdb/doc/gdb.texinfo
===================================================================
RCS file: /cvs/src/src/gdb/doc/gdb.texinfo,v
retrieving revision 1.990
diff -u -r1.990 gdb.texinfo
--- gdb/doc/gdb.texinfo 5 Jul 2012 01:08:24 -0000 1.990
+++ gdb/doc/gdb.texinfo 12 Jul 2012 10:35:16 -0000
@@ -32892,6 +32892,61 @@
(gdb)
@end smallexample

+@subheading The @code{-start-telnet-service} Command
+@findex -start-telnet-service
+
+@subheading Synopsis
+
+@smallexample
+-start-telnet-service [port]
+@end smallexample
+
+Start a telnet service on the given port. After the service is started,
+user can connect to the gdb via telnet and execute CLI commands in
+parallel with existing MI interface. If no @samp{port} argument is given
+then default port 9950 is used. Only one service is allowed at one time.
+So user will have to stop the already running service before starting a
+new one. For telnet service to work, @value{GDBN}
+should be configured with --enable-gdbmitel=yes.
+
+@subheading @value{GDBN} Command
+
+No equivalent.
+
+@subheading Example
+
+@smallexample
+(gdb)
+-start-telnet-service 8002
+^done
+(gdb)
+@end smallexample
+
+@subheading The @code{-stop-telnet-service} Command
+@findex -stop-telnet-service
+
+@subheading Synopsis
+
+@smallexample
+-stop-telnet-service
+@end smallexample
+
+Stop the telnet service if it is already started. Otherwise this
+command has no effect.
+
+@subheading @value{GDBN} Command
+
+No equivalent.
+
+@subheading Example
+
+@smallexample
+(gdb)
+-stop-telnet-service
+^done
+(gdb)
+@end smallexample
+
@node Annotations
@chapter @value{GDBN} Annotations

Index: gdb/mi/mi-cmds.c
===================================================================
RCS file: /cvs/src/src/gdb/mi/mi-cmds.c,v
retrieving revision 1.60
diff -u -r1.60 mi-cmds.c
--- gdb/mi/mi-cmds.c 24 May 2012 00:33:46 -0000 1.60
+++ gdb/mi/mi-cmds.c 12 Jul 2012 10:35:16 -0000
@@ -137,6 +137,10 @@
{ "var-show-attributes", { NULL, 0 }, mi_cmd_var_show_attributes},
{ "var-show-format", { NULL, 0 }, mi_cmd_var_show_format},
{ "var-update", { NULL, 0 }, mi_cmd_var_update},
+#ifdef ENABLE_GDBMITEL
+ { "start-telnet-service", { NULL, 0 }, mi_cmd_start_telnet_service},
+ { "stop-telnet-service", { NULL, 0 }, mi_cmd_stop_telnet_service},
+#endif
{ NULL, }
};

Index: gdb/mi/mi-cmds.h
===================================================================
RCS file: /cvs/src/src/gdb/mi/mi-cmds.h,v
retrieving revision 1.55
diff -u -r1.55 mi-cmds.h
--- gdb/mi/mi-cmds.h 24 May 2012 00:33:46 -0000 1.55
+++ gdb/mi/mi-cmds.h 12 Jul 2012 10:35:16 -0000
@@ -118,6 +118,10 @@
extern mi_cmd_argv_ftype mi_cmd_var_update;
extern mi_cmd_argv_ftype mi_cmd_enable_pretty_printing;
extern mi_cmd_argv_ftype mi_cmd_var_set_update_range;
+#ifdef ENABLE_GDBMITEL
+extern mi_cmd_argv_ftype mi_cmd_start_telnet_service;
+extern mi_cmd_argv_ftype mi_cmd_stop_telnet_service;
+#endif

/* Description of a single command. */

Index: gdb/mi/mi-telnet.c
===================================================================
RCS file: gdb/mi/mi-telnet.c
diff -N gdb/mi/mi-telnet.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ gdb/mi/mi-telnet.c 12 Jul 2012 10:35:16 -0000
@@ -0,0 +1,670 @@
+/* MI telnet support
+
+ Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2007, 2008, 2009, 2010,
+ 2011, 2012 Free Software Foundation, Inc.
+
+ Contributed by Mentor Graphics Corporation <www.mentor.com>
+
+ 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/>. */
+
+/* MI telnet support allows remote user to connect to gdb using telnet.
+ GDB should be built with "--enable-gdbmitel=yes" option to enable MI telnet.
+
+ To Start the service use MI command "-start-telnet-service [port]".
+ Default port value is 9950.
+ Service can be stopped by issuing "-stop-telnet-service" command.
+
+ The following options are required for interactive commands
+ and can be turned on directly via telnet on the fly:
+ "set confirm on"
+ "set interactive on"
+*/
+
+#include "defs.h"
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include "top.h"
+#include "mi-cmds.h"
+#include "gdb_string.h"
+#include "event-loop.h"
+#include "interps.h"
+#include "main.h"
+#include "breakpoint.h"
+#include "cli/cli-cmds.h"
+#include "cli/cli-decode.h"
+#include "readline/readline.h"
+#include "readline/history.h"
+
+#define DEFAULT_PORT 9950
+#define MAX_CMD_LENGTH 512
+
+/* Hook to ask remote client for
+ additional 'yes or no' question. */
+static int telnet_query_hook (const char *msg, va_list argp);
+
+/* Execute telnet command. */
+static void execute_telnet_command (char* line);
+
+/* This is called when data is available on telnet port. */
+static void get_input_from_telnet (int err, gdb_client_data client_data);
+
+/* Accept client connection and register it
+ with event_loop to get data from telnet client. */
+static void telnet_accept_handler(int err, gdb_client_data);
+
+/* Remove the socket descriptor from the event handler and close it. */
+static void close_socket_desc (int *fd);
+
+/* A utility function to receive data from socket
+ and do error checking. */
+static int receive (int *fd, void *buf,
+ size_t n, int flags);
+
+/* A utility function to get a line of input from socket. */
+static int receive_line (int *fd, char* buf, int buf_size);
+
+/* A utility function to send the output to client. */
+static void reply_msg (const char *msg);
+
+/* Sends the reply to remote client with extra '\r' character
+ for every '\n' in the msg so that it will show up correctly on
+ telnet console. */
+static void reply_msg_with_carriage_return (const char *msg);
+
+/* Setup the hooks to do most of the communication work. */
+static void setup_hooks (int);
+
+/* Socket for accepting telnet connections. */
+static int telnet_s = -1;
+
+/* Client connection descriptor. */
+static int client_fd = -1;
+
+/* For output redirecting. */
+static struct ui_file *str_file = NULL;
+
+/* The Prompt to show on telnet. */
+static char* mi_prompt = "";
+
+/* Hooks which are called at various points during telnet
+ command processing. */
+
+/* This is called when we are about to process a command. */
+int (*cmd_start_hook) (int *fd);
+
+/* This is called when we have processed a command. */
+int (*cmd_end_hook) (int *fd);
+
+/* This is called after connection is complete to setup
+ the mode. */
+int (*init_mode_hook) (int *fd);
+
+/* This is called when data is avaialble on the socket. */
+void (*data_available_hook) (int *fd);
+
+/* This is called when we are closeing telnet service. */
+void (*stop_service_hook) ();
+
+/* This is called when user need to respond to a question. */
+char (*get_answer_hook) (int *fd);
+
+/* This is called when user needs to provide some input, mostly for
+ getting list of commands. */
+char* (*get_input_hook) (int *fd, char* prompt);
+
+
+/* Command to start listening for client on specified port. */
+
+void
+mi_cmd_start_telnet_service (char *command, char **argv, int argc)
+{
+ struct sockaddr_in serv, client;
+ socklen_t len;
+ int port;
+ int val;
+ char* tmp_name;
+
+ if (telnet_s != -1 || client_fd != -1)
+ {
+ error (_("Error: telnet service already started"));
+ return;
+ }
+
+ if (argc != 0 && argc != 1)
+ {
+ error (_("Usage: -start_telnet_service [port]"));
+ return;
+ }
+
+ if (argc == 1)
+ port = atoi (argv[0]);
+ else
+ port = DEFAULT_PORT;
+
+ if (port <= 0)
+ {
+ error (_("Error: wrong port value: %d"), port);
+ return;
+ }
+
+ /* Setup server side of telnet service. */
+ memset ((char *)&serv, 0, sizeof (serv));
+ serv.sin_family = AF_INET;
+ serv.sin_port = htons (port);
+ serv.sin_addr.s_addr = htonl (INADDR_ANY);
+
+ telnet_s = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (telnet_s == -1)
+ {
+ error (_("Error: socket can't be created"));
+ return;
+ }
+
+ if (bind (telnet_s, (const struct sockaddr *)&serv, sizeof (serv)) == -1)
+ {
+ close (telnet_s);
+ telnet_s = -1;
+ error (_("Error: port %d can't be bind"), port);
+ return;
+ }
+
+ /* one client so far allowed */
+ if (listen (telnet_s, 1) == -1)
+ {
+ close (telnet_s);
+ telnet_s = -1;
+ error (_("Error: %s"), safe_strerror (errno));
+ return;
+ }
+
+ /* Allocate file for output. */
+ if (!str_file)
+ str_file = mem_fileopen ();
+
+ mi_prompt = get_prompt ();
+
+ /* Setup the hooks to handle various situations. */
+ setup_hooks (0);
+
+ /* Register handler that will accept connections. */
+ add_file_handler (telnet_s, telnet_accept_handler, 0);
+}
+
+/* Command to stop listening for remote clients. */
+
+void
+mi_cmd_stop_telnet_service (char *command, char **argv, int argc)
+{
+ close_socket_desc (&client_fd);
+
+ close_socket_desc (&telnet_s);
+
+ if(str_file != NULL)
+ {
+ ui_file_delete (str_file);
+ str_file = NULL;
+ }
+
+ if (stop_service_hook)
+ (*stop_service_hook) ();
+}
+
+/* Hook for query to ask remote client for
+ additional 'yes or no' question. */
+
+static int
+telnet_query_hook (const char *msg, va_list argp)
+{
+ int retval = 0;
+ char *question = NULL;
+ char cmd;
+
+ gdb_flush (gdb_stdout);
+ question = xstrvprintf (msg, argp);
+
+ reply_msg_with_carriage_return (question);
+ reply_msg ("(y or n) ");
+
+ if(get_answer_hook)
+ {
+ cmd = (*get_answer_hook) (&client_fd);
+
+ /* Check the first letter in the answer. */
+ switch (cmd)
+ {
+ case 'y':
+ case 'Y':
+ retval = 1;
+ break;
+
+ case 'n':
+ case 'N':
+ default:
+ retval = 0;
+ break;
+ }
+ }
+
+ xfree (question);
+ return retval;
+}
+
+/* This function is called when GDB needs to get the list of
+ commands to run on breakpoints. */
+
+static char *
+telnet_readline_hook (char *prompt)
+{
+ if (get_input_hook)
+ return (*get_input_hook) (&client_fd, prompt);
+ else
+ return "";
+};
+
+/* Execute telnet command. */
+
+static void
+execute_telnet_command (char* line)
+{
+ struct gdb_exception e;
+ char *result = NULL;
+ struct cleanup *cleanup;
+ struct interp *old_interp = NULL;
+ struct interp *interp_to_use = NULL;
+ HIST_ENTRY *hist = NULL;
+ long len = 0;
+ void* old_query_hook;
+ void* old_readline_hook;
+
+ if (line == NULL)
+ return;
+
+ /* If this is a valid command then add it to history. */
+ if (*line != '\0' )
+ add_history (line);
+ else
+ {
+ /* This is an empty command. Look in the history. If nothing is
+ available in history then function will return. */
+ hist = previous_history ();
+ if ( (hist != NULL) && (hist->line != NULL) && (*hist->line != '\0') )
+ line = hist->line;
+ else
+ return;
+ }
+
+ if (cmd_start_hook)
+ (*cmd_start_hook) (&client_fd);
+
+ /* setup cleanups for input/output. */
+ cleanup = make_cleanup (null_cleanup, NULL);
+ make_cleanup_restore_ui_file (&gdb_stdin);
+ make_cleanup_restore_ui_file (&gdb_stdout);
+ make_cleanup_restore_ui_file (&gdb_stderr);
+ make_cleanup_restore_ui_file (&gdb_stdlog);
+ make_cleanup_restore_ui_file (&gdb_stdtarg);
+ make_cleanup_restore_ui_file (&gdb_stdtargerr);
+
+ /* execute command in sync, set batch mode to off */
+ make_cleanup_restore_integer (&batch_flag);
+ batch_flag = 0;
+
+ /* figure out current interpreter */
+ if (current_interp_named_p (INTERP_MI))
+ {
+ old_interp = interp_lookup (INTERP_MI);
+ }
+ else if (current_interp_named_p (INTERP_MI1))
+ {
+ old_interp = interp_lookup (INTERP_MI1);
+ }
+ else if (current_interp_named_p (INTERP_MI2))
+ {
+ old_interp = interp_lookup (INTERP_MI2);
+ }
+ else if (current_interp_named_p (INTERP_MI3))
+ {
+ old_interp = interp_lookup (INTERP_MI3);
+ }
+
+ /* switch to console interp */
+ interp_to_use = interp_lookup (INTERP_CONSOLE);
+ if (!interp_set (interp_to_use, 0))
+ {
+ reply_msg ("Error: unexpectedly failed to use CLI the interpreter\r\n");
+ do_cleanups (cleanup);
+ return;
+ }
+
+ /* redirect and catch all input/output into string */
+ if (ui_out_redirect (current_uiout, str_file) < 0)
+ reply_msg ("Error: unexpectedly failed to redirect user interface. "
+ "No command results will be print\r\n");
+ else
+ make_cleanup_ui_out_redirect_pop (current_uiout);
+
+ gdb_stdout = str_file;
+ gdb_stderr = str_file;
+ gdb_stdlog = str_file;
+ gdb_stdtarg = str_file;
+ gdb_stdtargerr = str_file;
+
+ /* Hook for command that may ask user for questions. */
+ old_query_hook = deprecated_query_hook;
+ old_readline_hook = deprecated_readline_hook;
+ deprecated_query_hook = telnet_query_hook;
+ deprecated_readline_hook = telnet_readline_hook;
+
+ /* Console interp is properly set up, execute the command. */
+ TRY_CATCH (e, RETURN_MASK_ALL)
+ {
+ execute_command (line, 1);
+ }
+
+ /* Do any breakpoint-related stuff. */
+ bpstat_do_actions ();
+
+ /* Restore the hooks. */
+ deprecated_query_hook = old_query_hook;
+ deprecated_readline_hook = old_readline_hook;
+
+ gdb_flush (str_file);
+
+ if (e.reason < 0)
+ {
+ reply_msg ("Error: ");
+ reply_msg (e.message);
+ reply_msg ("\r\n");
+ }
+
+ /* Send the reply to the client. */
+ result = ui_file_xstrdup (str_file, &len);
+
+ if (len > 0)
+ {
+ reply_msg_with_carriage_return (result);
+ }
+
+ xfree (result);
+
+ /* Clear output buffer and restore GDB settings. */
+ ui_file_rewind (str_file);
+ do_cleanups (cleanup);
+
+ /* Get back to MI interp. */
+ interp_set (old_interp, 0);
+
+ if (cmd_end_hook)
+ (*cmd_end_hook) (&client_fd);
+}
+
+/* This function is called when data is ready on teh socket and calls
+ the corresponding hook. */
+static void
+get_input_from_telnet (int err, gdb_client_data client_data)
+{
+ if (data_available_hook)
+ (*data_available_hook) (&client_fd);
+}
+
+/* Handler for the listening socket.
+ Accepts client connection and registers it with event_loop. */
+
+static void
+telnet_accept_handler(int err, gdb_client_data client_data)
+{
+ struct sockaddr_in client;
+ socklen_t len;
+ int s = -1;
+
+ if (client_fd != -1)
+ return; /* Another client is already connected. */
+
+ s = accept (telnet_s, (struct sockaddr *)&client, &len);
+ if (s != -1)
+ {
+ client_fd = s;
+
+ if (init_mode_hook)
+ {
+ if ((*init_mode_hook) (&client_fd) == 0)
+ {
+ close_socket_desc (&telnet_s);
+ return;
+ }
+ }
+
+ /* Initialize the history library so that we can use the
+ previous command if user just press return. */
+ using_history ();
+
+ /* Register the socket with event handler. */
+ add_file_handler (client_fd, get_input_from_telnet, 0);
+
+ /* Remote connection is set up, show prompt. */
+ send (client_fd, mi_prompt, strlen (mi_prompt), 0);
+ }
+
+ /* In any case, close the listening socket as we only
+ allow one connection. */
+ close_socket_desc (&telnet_s);
+}
+
+static void
+close_socket_desc (int *fd)
+{
+ if((fd != NULL) && (*fd != -1) )
+ {
+ delete_file_handler (*fd);
+ close (*fd);
+ *fd = -1;
+ }
+}
+
+/* This is wrapper over recv. If recv returns some error then it closes
+ the socket. */
+
+static int
+receive (int *fd, void *buf, size_t n, int flags)
+{
+ int ret;
+
+ if (fd == NULL)
+ return -1;
+
+ ret = recv (*fd, buf, n, flags);
+ if (ret < 1)
+ {
+ close_socket_desc (fd);
+ }
+ return ret;
+}
+
+/* A utility function to send the output to client. */
+
+static void
+reply_msg (const char *msg)
+{
+ char *result = NULL;
+ long len;
+
+ if (client_fd != -1 && msg != NULL)
+ {
+ /* flush additional output if any */
+ if (str_file)
+ {
+ gdb_flush (str_file);
+ result = ui_file_xstrdup (str_file, &len);
+
+ send (client_fd, result, len, 0);
+
+ xfree (result);
+ ui_file_rewind (str_file);
+ }
+ send (client_fd, msg, strlen (msg), 0);
+ }
+}
+
+/* Sends the reply to remote client with extra '\r' character
+ for every '\n' in the msg so that it will show up correctly on
+ telnet console. */
+static void
+reply_msg_with_carriage_return (const char *msg)
+{
+ int len;
+ char data;
+ int i =0;
+
+ if (client_fd != -1 && msg != NULL)
+ {
+ len = strlen (msg);
+
+ for(i=0; i < len; i++)
+ {
+ send (client_fd, &msg[i], 1, 0);
+
+ if(msg[i] == '\n')
+ {
+ data = '\r';
+ send (client_fd, &data, 1, 0);
+ }
+ }
+ }
+}
+
+/* Get a line from the client and put the terminating NULL
+ at correct place. */
+
+static int
+receive_line (int *fd, char* buf, int buf_size)
+{
+ int size;
+ int empty_string = 1;
+ int i;
+
+ /* Read a line from the socket. */
+ size = receive (fd, buf, (buf_size - 1), 0);
+
+ if (size <= 0)
+ return size;
+
+ /* Put a string terminating NULL character after the first character
+ that is not \n or \r. */
+ for (i = (size - 1); i >=0; i--)
+ {
+ if ( (buf[i] != '\n') && (buf[i] != '\r') )
+ {
+ buf[i + 1] = '\0';
+ empty_string = 0;
+ break;
+ }
+ }
+
+ /* The client did not send anything except \n and \r, so just send an
+ empty string to execute_telnet_command. */
+ if (empty_string == 1)
+ buf[0] = '\0';
+
+ return size;
+
+}
+
+/* This function is called when we need to take a yes or no answer
+ from the user. */
+
+static char
+get_answer_hook_line (int *fd)
+{
+ int size;
+ char cmd[MAX_CMD_LENGTH];
+
+ size = receive (fd, &cmd, MAX_CMD_LENGTH, 0);
+
+ if (size <= 0)
+ {
+ return 0; /* If error return 0 */
+ }
+ return cmd[0];
+}
+
+/* This function is called when GDB needs to get the list of
+ commands to run on breakpoints. See 'commands'. */
+
+static char*
+get_input_hook_line (int* fd, char* prompt)
+{
+ long len;
+ char buf[MAX_CMD_LENGTH + 1];
+
+ reply_msg (prompt);
+
+ len = receive_line (&client_fd, buf, MAX_CMD_LENGTH + 1);
+
+ if (len <= 0)
+ return NULL;
+
+ return xstrdup (buf);
+}
+
+/* This function is called when data is available on the socket to read. */
+
+static void
+data_available_hook_line (int *fd)
+{
+ int size;
+ unsigned char buf[MAX_CMD_LENGTH+1];
+
+ size = receive_line (&client_fd, buf, MAX_CMD_LENGTH + 1);
+
+ if (size <= 0)
+ return;
+
+ execute_telnet_command (buf);
+
+ /* Print the prompt. */
+ send (*fd, mi_prompt, strlen (mi_prompt), 0);
+}
+
+/* This function setup the hooks that are called at various points.
+ The mode parameter gives us flexibility to support other modes in
+ future. */
+
+static void
+setup_hooks (int mode)
+{
+ /* Currently, we are supporting only one mode where we get the
+ whole line from the client. So the _line in the end. We can
+ , in future, support getting input character by character. The
+ hook mechanism provides us the flexibility. */
+ if (mode == 0)
+ {
+ cmd_start_hook = NULL;
+
+ cmd_end_hook = NULL;
+
+ init_mode_hook = NULL;
+
+ data_available_hook = data_available_hook_line;
+
+ stop_service_hook = NULL;
+
+ get_answer_hook = get_answer_hook_line;
+
+ get_input_hook = get_input_hook_line;
+ }
+}
+
Index: gdb/testsuite/gdb.mi/mi-telnet.c
===================================================================
RCS file: gdb/testsuite/gdb.mi/mi-telnet.c
diff -N gdb/testsuite/gdb.mi/mi-telnet.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ gdb/testsuite/gdb.mi/mi-telnet.c 12 Jul 2012 10:35:17 -0000
@@ -0,0 +1,73 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2012 Free Software Foundation, Inc.
+
+ 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 <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <string.h>
+
+int test_connect(int port)
+{
+ struct sockaddr_in client;
+ struct hostent *hp;
+ int telnet_s;
+ int result = 0;
+ int err;
+
+ if ((hp = gethostbyname("localhost")) != 0)
+ {
+ memset ((char *)&client, 0, sizeof (client));
+ client.sin_family = AF_INET;
+ client.sin_port = htons (port);
+ client.sin_addr.s_addr = ((struct in_addr *)(hp->h_addr))->s_addr;
+
+ telnet_s = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (telnet_s != -1)
+ {
+ /* connect to PORT on HOST */
+ err = connect(telnet_s, (struct sockaddr *) &client, sizeof(client));
+ if (err == -1)
+ {
+ result = 0; /* Connect-fail-breakpoint */
+ }
+ else
+ {
+ result = 1; /* Connect-succeed-breakpoint */
+ }
+ close(telnet_s);
+ }
+ }
+ return result;
+}
+
+
+int main(int argc, char* argv[])
+{
+ int port;
+ int result = 1;
+
+ if (argc == 2)
+ {
+ port = atoi (argv[1]);
+
+ if (port > 0)
+ {
+ result = test_connect (port);
+ }
+ }
+ return result; /* program-end-breakpoint */
+}
Index: gdb/testsuite/gdb.mi/mi-telnet.exp
===================================================================
RCS file: gdb/testsuite/gdb.mi/mi-telnet.exp
diff -N gdb/testsuite/gdb.mi/mi-telnet.exp
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ gdb/testsuite/gdb.mi/mi-telnet.exp 12 Jul 2012 10:35:17 -0000
@@ -0,0 +1,109 @@
+# Copyright 1999-2002, 2004-2005, 2007-2012 Free Software Foundation,
+# Inc.
+#
+# 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/>.
+
+# Test -start-telnet-service and -stop-telnet-service comands.
+#
+
+load_lib mi-support.exp
+set MIFLAGS "-i=mi2"
+set PORT1 8005
+set PORT2 8006
+
+# Executes the test for telnet service.
+# port is the port on which telnet service will listen.
+# bp_line is the line in test C file where we want to hit the
+# breakpoint.
+# message is the message to print for this test.
+proc connect_test {port bp_line message} {
+ global srcfile
+ global connect_succeed_break
+ global connect_fail_break
+ global program_end_breakpoint
+
+ # set the argument of the test program
+ mi_gdb_test "-interpreter-exec console \"set args $port\"" \
+ {\^done} \
+ "-interpreter-exec console \"set args $port\""
+
+ mi_create_breakpoint "${srcfile}:${connect_succeed_break}" \
+ 2 .* test_connect ".*${srcfile}" ${connect_succeed_break} .* "Connection Succeed breakpoint."
+
+ mi_create_breakpoint "${srcfile}:${connect_fail_break}" \
+ 3 .* test_connect ".*${srcfile}" ${connect_fail_break} .* "Connection fail breakpoint."
+
+ mi_create_breakpoint "${srcfile}:${program_end_breakpoint}" \
+ 4 .* main ".*${srcfile}" ${program_end_breakpoint} .* "Program exit."
+
+ mi_run_cmd
+
+ mi_expect_stop "breakpoint-hit" "test_connect" ".*" ".*mi-telnet.c" "$bp_line" \
+ {"" "disp=\"keep\"" } $message
+}
+
+gdb_exit
+if [mi_gdb_start] {
+ continue
+}
+
+set testfile "mi-telnet"
+set srcfile ${testfile}.c
+set binfile ${objdir}/${subdir}/mi-telnet
+if { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
+ untested "could not compile test program"
+ return -1
+}
+
+mi_delete_breakpoints
+mi_gdb_reinitialize_dir $srcdir/$subdir
+mi_gdb_load ${binfile}
+
+mi_run_to_main
+
+set connect_succeed_break [gdb_get_line_number "Connect-succeed-breakpoint"]
+set connect_fail_break [gdb_get_line_number "Connect-fail-breakpoint"]
+set program_end_breakpoint [gdb_get_line_number "program-end-breakpoint"]
+
+# Send the command to start the telnet service
+mi_gdb_test "-start-telnet-service $PORT1" "\\\^done" "Start Telnet Service"
+
+# Check if test program can connect to the port.
+connect_test $PORT1 $connect_succeed_break "Client can connect when port is open."
+
+# Re-run the same test but after closing the telnet service. Test if client
+# can connect.
+mi_gdb_exit
+if [mi_gdb_start] {
+ continue
+}
+
+mi_delete_breakpoints
+mi_gdb_reinitialize_dir $srcdir/$subdir
+mi_gdb_load ${binfile}
+
+mi_run_to_main
+
+# Open the telnet service and then close it.
+mi_gdb_test "-start-telnet-service $PORT2" "\\\^done" "Start Telnet Service"
+
+mi_gdb_test "-stop-telnet-service" "\\\^done" "Stop Telnet Service"
+
+# Client program should not be able to connect now.
+connect_test $PORT2 $connect_fail_break "Client cannot connect when port is closed."
+
+mi_gdb_exit
+
+return 0
+