This is an automated email from Gerrit.

"Antonio Borneo <[email protected]>" just uploaded a new patch set to 
Gerrit, which you can find at https://review.openocd.org/c/openocd/+/9771

-- gerrit

commit e5d95700adfcb07579dc5038a08663c298d691bc
Author: Antonio Borneo <[email protected]>
Date:   Sun Jul 5 23:15:33 2026 +0200

    server: override stdin and stdout with server's connection
    
    The stdin and stdout of the Tcl commands are the same as OpenOCD,
    thus corresponding to the console that launched OpenOCD.
    This is not optimal as:
    - Tcl command 'puts' writes 'stdout' in the main console only, not
      in the telnet console that run it;
    - Tcl commands 'gets' and 'read' too read 'stdin' from the main
      console.
    
    Duplicate the stdin and stdout of the main console and let JimTcl
    to use such file descriptors as stdin and stdout.
    Then, while running a Tcl command in the telnet console, override
    the file descriptors so the Tcl command will refer to the telnet
    console as stdin and stdout.
    
    Due to the encoding of the telnet protocol, terminating a line
    with the 'enter' key is not terminating the Tcl command 'puts'. In
    fact the character '\n' is transmitted as '\r\0'.
    The way to terminate 'puts' over telnet is by pressing CRTL-J in
    the telnet terminal.
    
    Add some example in the documentation for terminating an infinite
    loop by pressing a key either in the telnet or in the main console
    of OpenOCD.
    
    No tests done on Windows.
    
    Change-Id: Ifeca20849a9e36cc874ed98b0e55efff25023ac8
    Signed-off-by: Antonio Borneo <[email protected]>

diff --git a/doc/openocd.texi b/doc/openocd.texi
index 89900b06a7..080f6ade89 100644
--- a/doc/openocd.texi
+++ b/doc/openocd.texi
@@ -14242,6 +14242,59 @@ foreach who @{A B C D E@}
 @}
 @end example
 
+@b{getchar() emulation}
+
+OpenOCD does not terminate an infinite loop by pressing @b CTRL-C .
+It is suggested to read a character from the user to terminate an infinite
+loop.
+
+The Tcl commands @command{read} and @command{gets} are used for reading the
+user input, but @command{gets} is not suitable in this case because it waits
+for a whole line, till newline.
+
+The command @command{read} can be instrumented to read a single char and, by
+setting the input channel as non-blocking, it return one character if present
+or zero characters when there is no user input.
+
+In a telnet console, the following example will terminate by pressing any key
+in the console.
+@example
+fconfigure stdin -blocking 0
+while 1 @{
+   puts Hello
+   if @{[llength [read stdin 1]]@} @{
+      break
+   @}
+@}
+@end example
+
+Note that in the example above the complex condition is not evaluated inside
+the condition of @command{while} because Tcl will evaluate it only at the
+first iteration, thus failing to quit the loop.
+
+More complex is having a getchar() emulation in a script started by OpenOCD
+command line on Linux, because Linux console is a tty that buffers the line
+and does not pass the single characters to @command{read}.
+
+In such case, the script should include the call to Linux tool @command{stty}
+and, of course, the user should press the key in the console where OpenOCD has
+been started:
+@example
+fconfigure stdin -blocking 0
+set oldmode [exec stty -g]
+exec stty raw -echo
+while 1 @{
+   puts Hello
+   if @{[llength [read stdin 1]]@} @{
+      break
+   @}
+@}
+exec stty $oldmode
+@end example
+
+The tool @command{stty} cannot be used on telnet console. It's also not present
+in Windows host. This make complex to implement a generic @command{getchar()}.
+
 @node License
 @appendix The GNU Free Documentation License.
 @include fdl.texi
diff --git a/src/helper/command.c b/src/helper/command.c
index bf2c91814e..abddaf7b25 100644
--- a/src/helper/command.c
+++ b/src/helper/command.c
@@ -1164,11 +1164,137 @@ static const struct command_registration 
command_builtin_handlers[] = {
        COMMAND_REGISTRATION_DONE
 };
 
+/*
+ * JimTcl caches the file descriptors for stdin, stdout and stderr during
+ * the initialization will use them for commands puts, gets and read.
+ * There is no way to change the cached file descriptors at runtime.
+ * The code below creates a dup() of stdin and stdout before init, so JimTcl
+ * uses the duplicated file descriptor.
+ * At runtime, we will dup2() the file descriptors used by JimTcl to re-direct
+ * stdin and stdout to a specific server.
+ */
+
+/*
+ * Duplicate the file descriptors of stdin and stdout and create the
+ * associated streams.
+ */
+static int command_stdinout_init(struct command_context *cmd_ctx)
+{
+       cmd_ctx->dup_stdin_fileno  = -1;
+       cmd_ctx->dup_stdout_fileno = -1;
+       cmd_ctx->dup_stdin  = NULL;
+       cmd_ctx->dup_stdout = NULL;
+
+       int dup_stdin_fileno = dup(fileno(stdin));
+       if (dup_stdin_fileno < 0)
+               return ERROR_FAIL;
+
+       int dup_stdout_fileno = dup(fileno(stdout));
+       if (dup_stdout_fileno < 0) {
+               close(dup_stdin_fileno);
+               return ERROR_FAIL;
+       }
+
+       FILE *dup_stdin = fdopen(dup_stdin_fileno, "r");
+       if (!dup_stdin) {
+               close(dup_stdout_fileno);
+               close(dup_stdin_fileno);
+               return ERROR_FAIL;
+       }
+
+       FILE *dup_stdout = fdopen(dup_stdout_fileno, "w");
+       if (!dup_stdout) {
+               close(dup_stdout_fileno);
+               fclose(dup_stdin);
+               return ERROR_FAIL;
+       }
+
+       // Note: from now-on fclose(dup_std*) causes close(dup_std*_fileno)
+
+       cmd_ctx->dup_stdin_fileno  = dup_stdin_fileno;
+       cmd_ctx->dup_stdout_fileno = dup_stdout_fileno;
+       cmd_ctx->dup_stdin  = dup_stdin;
+       cmd_ctx->dup_stdout = dup_stdout;
+
+       return ERROR_OK;
+}
+
+/*
+ * Close the duplicated file descriptors by closing the associated streams.
+ */
+static void command_stdinout_close(struct command_context *cmd_ctx)
+{
+       if (cmd_ctx->dup_stdout_fileno)
+               fclose(cmd_ctx->dup_stdout);
+
+       if (cmd_ctx->dup_stdin_fileno)
+               fclose(cmd_ctx->dup_stdin);
+
+       cmd_ctx->dup_stdin_fileno  = -1;
+       cmd_ctx->dup_stdout_fileno = -1;
+       cmd_ctx->dup_stdin  = NULL;
+       cmd_ctx->dup_stdout = NULL;
+}
+
+int command_stdinout_set_fd(struct command_context *cmd_ctx, int fd)
+{
+       if (fd < 0 || cmd_ctx->dup_stdin_fileno < 0
+                       || cmd_ctx->dup_stdout_fileno < 0)
+               return ERROR_OK;
+
+       int tmp_in_fd = dup2(fd, cmd_ctx->dup_stdin_fileno);
+       if (tmp_in_fd < 0) {
+               LOG_ERROR("Failed to dup2(%d)", fd);
+               return ERROR_FAIL;
+       }
+
+       int tmp_out_fd = dup2(fd, cmd_ctx->dup_stdout_fileno);
+       if (tmp_out_fd < 0) {
+               LOG_ERROR("Failed to dup2(%d)", fd);
+               cmd_ctx->dup_stdin_fileno = dup2(fileno(stdin),
+                       cmd_ctx->dup_stdin_fileno);
+               if (cmd_ctx->dup_stdin_fileno < 0)
+                       LOG_ERROR("Failed to recover stdin");
+               return ERROR_FAIL;
+       }
+
+       cmd_ctx->dup_stdin_fileno = tmp_in_fd;
+       cmd_ctx->dup_stdout_fileno = tmp_out_fd;
+
+       return ERROR_OK;
+}
+
+int command_stdinout_restore(struct command_context *cmd_ctx)
+{
+       if (cmd_ctx->dup_stdin_fileno < 0 || cmd_ctx->dup_stdout_fileno < 0)
+               return ERROR_OK;
+
+       int retval = ERROR_OK;
+
+       cmd_ctx->dup_stdin_fileno = dup2(fileno(stdin),
+               cmd_ctx->dup_stdin_fileno);
+       if (cmd_ctx->dup_stdin_fileno < 0) {
+               LOG_ERROR("Failed to recover stdin");
+               retval = ERROR_FAIL;
+       }
+
+       cmd_ctx->dup_stdout_fileno = dup2(fileno(stdout),
+               cmd_ctx->dup_stdout_fileno);
+       if (cmd_ctx->dup_stdout_fileno < 0) {
+               LOG_ERROR("Failed to recover stdout");
+               retval = ERROR_FAIL;
+       }
+
+       return retval;
+}
+
 struct command_context *command_init(const char *startup_tcl, Jim_Interp 
*interp)
 {
        struct command_context *context = calloc(1, sizeof(struct 
command_context));
 
        context->mode = COMMAND_EXEC;
+       context->dup_stdin_fileno  = -1;
+       context->dup_stdout_fileno = -1;
 
        /* context can be duplicated. Put list head on separate mem-chunk to 
keep list consistent */
        context->help_list = malloc(sizeof(*context->help_list));
@@ -1176,11 +1302,25 @@ struct command_context *command_init(const char 
*startup_tcl, Jim_Interp *interp
 
        /* Create a jim interpreter if we were not handed one */
        if (!interp) {
+               FILE *old_stdin = stdin;
+               FILE *old_stdout = stdout;
+
+               int retval = command_stdinout_init(context);
+               if (retval == ERROR_OK) {
+                       stdin =  context->dup_stdin;
+                       stdout = context->dup_stdout;
+               } else {
+                       LOG_ERROR("Failed to redirect stdio");
+               }
+
                /* Create an interpreter */
                interp = Jim_CreateInterp();
                /* Add all the Jim core commands */
                Jim_RegisterCoreCommands(interp);
                Jim_InitStaticExtensions(interp);
+
+               stdin = old_stdin;
+               stdout = old_stdout;
        }
 
        context->interp = interp;
@@ -1206,6 +1346,7 @@ void command_exit(struct command_context *context)
 
        Jim_FreeInterp(context->interp);
        free(context->help_list);
+       command_stdinout_close(context);
        command_done(context);
 }
 
diff --git a/src/helper/command.h b/src/helper/command.h
index aa23574067..1a720895d7 100644
--- a/src/helper/command.h
+++ b/src/helper/command.h
@@ -12,6 +12,7 @@
 #define OPENOCD_HELPER_COMMAND_H
 
 #include <stdint.h>
+#include <stdio.h>
 #include <stdbool.h>
 
 #include <helper/jim-nvp.h>
@@ -64,6 +65,12 @@ struct command_context {
        command_output_handler_t output_handler;
        void *output_handler_priv;
        struct list_head *help_list;
+
+       // Used to redirect JimTcl stdin and stdout
+       int dup_stdin_fileno;
+       int dup_stdout_fileno;
+       FILE *dup_stdin;
+       FILE *dup_stdout;
 };
 
 struct command;
@@ -379,6 +386,9 @@ struct command_context *copy_command_context(struct 
command_context *cmd_ctx);
  */
 void command_done(struct command_context *context);
 
+int command_stdinout_set_fd(struct command_context *cmd_ctx, int fd);
+int command_stdinout_restore(struct command_context *cmd_ctx);
+
 /*
  * command_print() and command_print_sameline() are used to produce the TCL
  * output of OpenOCD commands. command_print() automatically adds a '\n' at
diff --git a/src/server/telnet_server.c b/src/server/telnet_server.c
index 52115a529d..ea44ceaafd 100644
--- a/src/server/telnet_server.c
+++ b/src/server/telnet_server.c
@@ -513,7 +513,9 @@ static int telnet_exec_line(struct connection *connection)
        if (strcmp(t_con->line, "shutdown") == 0)
                telnet_save_history(t_con);
 
+       command_stdinout_set_fd(command_context, connection->fd);
        retval = command_run_line(command_context, t_con->line);
+       command_stdinout_restore(command_context);
 
        t_con->line_cursor = 0;
        t_con->prompt_visible = true;

-- 

Reply via email to