server/telnet: Handle Ctrl+K
[fw/openocd] / src / server / telnet_server.c
index 090aa86fdee33b0961b3ad93d9ec66b211d7e39b..407ab68aefcd74d41d31f205e6e16fb3828cbef2 100644 (file)
@@ -19,9 +19,7 @@
  *   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, write to the                         *
- *   Free Software Foundation, Inc.,                                       *
- *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
  ***************************************************************************/
 
 #ifdef HAVE_CONFIG_H
@@ -32,7 +30,7 @@
 #include <target/target_request.h>
 #include <helper/configuration.h>
 
-static const char *telnet_port;
+static char *telnet_port;
 
 static char *negotiate =
        "\xFF\xFB\x03"                  /* IAC WILL Suppress Go Ahead */
@@ -56,7 +54,7 @@ static int telnet_write(struct connection *connection, const void *data,
 
        if (connection_write(connection, data, len) == len)
                return ERROR_OK;
-       t_con->closed = 1;
+       t_con->closed = true;
        return ERROR_SERVER_REMOTE_CLOSED;
 }
 
@@ -103,29 +101,36 @@ static void telnet_log_callback(void *priv, const char *file, unsigned line,
 {
        struct connection *connection = priv;
        struct telnet_connection *t_con = connection->priv;
-       int i;
+       size_t i;
+       size_t tmp;
 
-       /* if there is no prompt, simply output the message */
-       if (t_con->line_cursor < 0) {
+       /* If the prompt is not visible, simply output the message. */
+       if (!t_con->prompt_visible) {
                telnet_outputline(connection, string);
                return;
        }
 
-       /* clear the command line */
-       for (i = strlen(t_con->prompt) + t_con->line_size; i > 0; i -= 16)
-               telnet_write(connection, "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b", i > 16 ? 16 : i);
-       for (i = strlen(t_con->prompt) + t_con->line_size; i > 0; i -= 16)
-               telnet_write(connection, "                ", i > 16 ? 16 : i);
-       for (i = strlen(t_con->prompt) + t_con->line_size; i > 0; i -= 16)
-               telnet_write(connection, "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b", i > 16 ? 16 : i);
+       /* Clear the command line. */
+       tmp = strlen(t_con->prompt) + t_con->line_size;
+
+       for (i = 0; i < tmp; i += 16)
+               telnet_write(connection, "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b",
+                       MIN(tmp - i, 16));
+
+       for (i = 0; i < tmp; i += 16)
+               telnet_write(connection, "                ", MIN(tmp - i, 16));
+
+       for (i = 0; i < tmp; i += 16)
+               telnet_write(connection, "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b",
+                       MIN(tmp - i, 16));
 
-       /* output the message */
        telnet_outputline(connection, string);
 
-       /* put the command line to its previous state */
+       /* Put the command line to its previous state. */
        telnet_prompt(connection);
        telnet_write(connection, t_con->line, t_con->line_size);
-       for (i = t_con->line_size; i > t_con->line_cursor; i--)
+
+       for (i = t_con->line_cursor; i < t_con->line_size; i++)
                telnet_write(connection, "\b", 1);
 }
 
@@ -207,18 +212,25 @@ static void telnet_save_history(struct telnet_connection *t_con)
 
 static int telnet_new_connection(struct connection *connection)
 {
-       struct telnet_connection *telnet_connection = malloc(sizeof(struct telnet_connection));
+       struct telnet_connection *telnet_connection;
        struct telnet_service *telnet_service = connection->service->priv;
        int i;
 
+       telnet_connection = malloc(sizeof(struct telnet_connection));
+
+       if (!telnet_connection) {
+               LOG_ERROR("Failed to allocate telnet connection.");
+               return ERROR_FAIL;
+       }
+
        connection->priv = telnet_connection;
 
        /* initialize telnet connection information */
-       telnet_connection->closed = 0;
+       telnet_connection->closed = false;
        telnet_connection->line_size = 0;
        telnet_connection->line_cursor = 0;
-       telnet_connection->option_size = 0;
        telnet_connection->prompt = strdup("> ");
+       telnet_connection->prompt_visible = true;
        telnet_connection->state = TELNET_STATE_DATA;
 
        /* output goes through telnet connection */
@@ -285,7 +297,7 @@ static void telnet_history_up(struct connection *connection)
 {
        struct telnet_connection *t_con = connection->priv;
 
-       int last_history = (t_con->current_history > 0) ?
+       size_t last_history = (t_con->current_history > 0) ?
                                t_con->current_history - 1 :
                                TELNET_LINE_HISTORY_SIZE-1;
        telnet_history_go(connection, last_history);
@@ -294,11 +306,66 @@ static void telnet_history_up(struct connection *connection)
 static void telnet_history_down(struct connection *connection)
 {
        struct telnet_connection *t_con = connection->priv;
+       size_t next_history;
 
-       int next_history = (t_con->current_history + 1) % TELNET_LINE_HISTORY_SIZE;
+       next_history = (t_con->current_history + 1) % TELNET_LINE_HISTORY_SIZE;
        telnet_history_go(connection, next_history);
 }
 
+static int telnet_history_print(struct connection *connection)
+{
+       struct telnet_connection *tc;
+
+       tc = connection->priv;
+
+       for (size_t i = 1; i < TELNET_LINE_HISTORY_SIZE; i++) {
+               char *line;
+
+               /*
+                * The tc->next_history line contains empty string (unless NULL), thus
+                * it is not printed.
+                */
+               line = tc->history[(tc->next_history + i) % TELNET_LINE_HISTORY_SIZE];
+
+               if (line) {
+                       telnet_write(connection, line, strlen(line));
+                       telnet_write(connection, "\r\n\x00", 3);
+               }
+       }
+
+       tc->line_size = 0;
+       tc->line_cursor = 0;
+
+       /* The prompt is always placed at the line beginning. */
+       telnet_write(connection, "\r", 1);
+
+       return telnet_prompt(connection);
+}
+
+static void telnet_move_cursor(struct connection *connection, size_t pos)
+{
+       struct telnet_connection *tc;
+       size_t tmp;
+
+       tc = connection->priv;
+
+       if (pos < tc->line_cursor) {
+               tmp = tc->line_cursor - pos;
+
+               for (size_t i = 0; i < tmp; i += 16)
+                       telnet_write(connection, "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b",
+                               MIN(tmp - i, 16));
+       } else {
+               tmp = pos - tc->line_cursor;
+
+               for (size_t i = 0; i < tmp; i += 16)
+                       telnet_write(connection, tc->line + tc->line_cursor + i,
+                               MIN(tmp - i, 16));
+       }
+
+       tc->line_cursor = pos;
+}
+
 static int telnet_input(struct connection *connection)
 {
        int bytes_read;
@@ -335,7 +402,7 @@ static int telnet_input(struct connection *connection)
                                                        t_con->line[t_con->line_size++] = *buf_p;
                                                        t_con->line_cursor++;
                                                } else {
-                                                       int i;
+                                                       size_t i;
                                                        memmove(t_con->line + t_con->line_cursor + 1,
                                                                        t_con->line + t_con->line_cursor,
                                                                        t_con->line_size - t_con->line_cursor);
@@ -370,21 +437,11 @@ static int telnet_input(struct connection *connection)
                                                        telnet_write(connection, "\r\n\x00", 3);
 
                                                        if (strcmp(t_con->line, "history") == 0) {
-                                                               int i;
-                                                               for (i = 1; i < TELNET_LINE_HISTORY_SIZE; i++) {
-                                                                       /* the t_con->next_history line contains empty string
-                                                                        * (unless NULL), thus it is not printed */
-                                                                       char *history_line = t_con->history[(t_con->
-                                                                                       next_history + i) %
-                                                                                       TELNET_LINE_HISTORY_SIZE];
-                                                                       if (history_line) {
-                                                                               telnet_write(connection, history_line,
-                                                                                               strlen(history_line));
-                                                                               telnet_write(connection, "\r\n\x00", 3);
-                                                                       }
-                                                               }
-                                                               t_con->line_size = 0;
-                                                               t_con->line_cursor = 0;
+                                                               retval = telnet_history_print(connection);
+
+                                                               if (retval != ERROR_OK)
+                                                                       return retval;
+
                                                                continue;
                                                        }
 
@@ -394,8 +451,7 @@ static int telnet_input(struct connection *connection)
                                                        if (*t_con->line && (prev_line == NULL ||
                                                                        strcmp(t_con->line, prev_line))) {
                                                                /* if the history slot is already taken, free it */
-                                                               if (t_con->history[t_con->next_history])
-                                                                       free(t_con->history[t_con->next_history]);
+                                                               free(t_con->history[t_con->next_history]);
 
                                                                /* add line to history */
                                                                t_con->history[t_con->next_history] = strdup(t_con->line);
@@ -408,15 +464,14 @@ static int telnet_input(struct connection *connection)
                                                                t_con->current_history =
                                                                                t_con->next_history;
 
-                                                               if (t_con->history[t_con->current_history])
-                                                                       free(t_con->history[t_con->current_history]);
+                                                               free(t_con->history[t_con->current_history]);
                                                                t_con->history[t_con->current_history] = strdup("");
                                                        }
 
                                                        t_con->line_size = 0;
 
                                                        /* to suppress prompt in log callback during command execution */
-                                                       t_con->line_cursor = -1;
+                                                       t_con->prompt_visible = false;
 
                                                        if (strcmp(t_con->line, "shutdown") == 0)
                                                                telnet_save_history(t_con);
@@ -424,6 +479,7 @@ static int telnet_input(struct connection *connection)
                                                        retval = command_run_line(command_context, t_con->line);
 
                                                        t_con->line_cursor = 0;
+                                                       t_con->prompt_visible = true;
 
                                                        if (retval == ERROR_COMMAND_CLOSE_CONNECTION)
                                                                return ERROR_SERVER_REMOTE_CLOSED;
@@ -438,7 +494,7 @@ static int telnet_input(struct connection *connection)
                                                } else if ((*buf_p == 0x7f) || (*buf_p == 0x8)) {       /* delete character */
                                                        if (t_con->line_cursor > 0) {
                                                                if (t_con->line_cursor != t_con->line_size) {
-                                                                       int i;
+                                                                       size_t i;
                                                                        telnet_write(connection, "\b", 1);
                                                                        t_con->line_cursor--;
                                                                        t_con->line_size--;
@@ -478,7 +534,21 @@ static int telnet_input(struct connection *connection)
                                                        telnet_history_up(connection);
                                                else if (*buf_p == CTRL('N'))           /* cursor down */
                                                        telnet_history_down(connection);
-                                               else
+                                               else if (*buf_p == CTRL('A'))
+                                                       telnet_move_cursor(connection, 0);
+                                               else if (*buf_p == CTRL('E'))
+                                                       telnet_move_cursor(connection, t_con->line_size);
+                                               else if (*buf_p == CTRL('K')) {         /* kill line to end */
+                                                       if (t_con->line_cursor < t_con->line_size) {
+                                                               /* overwrite with space, until end of line, move back */
+                                                               for (size_t i = t_con->line_cursor; i < t_con->line_size; i++)
+                                                                       telnet_write(connection, " ", 1);
+                                                               for (size_t i = t_con->line_cursor; i < t_con->line_size; i++)
+                                                                       telnet_write(connection, "\b", 1);
+                                                               t_con->line[t_con->line_cursor] = '\0';
+                                                               t_con->line_size = t_con->line_cursor;
+                                                       }
+                                               } else
                                                        LOG_DEBUG("unhandled nonprintable: %2.2x", *buf_p);
                                        }
                                }
@@ -534,7 +604,7 @@ static int telnet_input(struct connection *connection)
                                        /* Remove character */
                                        if (*buf_p == '~') {
                                                if (t_con->line_cursor < t_con->line_size) {
-                                                       int i;
+                                                       size_t i;
                                                        t_con->line_size--;
                                                        /* remove char from line buffer */
                                                        memmove(t_con->line + t_con->line_cursor,
@@ -568,7 +638,7 @@ static int telnet_input(struct connection *connection)
                                break;
                        default:
                                LOG_ERROR("unknown telnet state");
-                               exit(-1);
+                               return ERROR_FAIL;
                }
 
                bytes_read--;
@@ -585,29 +655,22 @@ static int telnet_connection_closed(struct connection *connection)
 
        log_remove_callback(telnet_log_callback, connection);
 
-       if (t_con->prompt) {
-               free(t_con->prompt);
-               t_con->prompt = NULL;
-       }
+       free(t_con->prompt);
+       t_con->prompt = NULL;
 
        /* save telnet history */
        telnet_save_history(t_con);
 
        for (i = 0; i < TELNET_LINE_HISTORY_SIZE; i++) {
-               if (t_con->history[i]) {
-                       free(t_con->history[i]);
-                       t_con->history[i] = NULL;
-               }
+               free(t_con->history[i]);
+               t_con->history[i] = NULL;
        }
 
        /* if this connection registered a debug-message receiver delete it */
        delete_debug_msg_receiver(connection->cmd_ctx, NULL);
 
-       if (connection->priv) {
-               free(connection->priv);
-               connection->priv = NULL;
-       } else
-               LOG_ERROR("BUG: connection->priv == NULL");
+       free(connection->priv);
+       connection->priv = NULL;
 
        return ERROR_OK;
 }
@@ -619,17 +682,26 @@ int telnet_init(char *banner)
                return ERROR_OK;
        }
 
-       struct telnet_service *telnet_service = malloc(sizeof(struct telnet_service));
+       struct telnet_service *telnet_service =
+               malloc(sizeof(struct telnet_service));
+
+       if (!telnet_service) {
+               LOG_ERROR("Failed to allocate telnet service.");
+               return ERROR_FAIL;
+       }
 
        telnet_service->banner = banner;
 
-       return add_service("telnet",
-               telnet_port,
-               1,
-               telnet_new_connection,
-               telnet_input,
-               telnet_connection_closed,
+       int ret = add_service("telnet", telnet_port, CONNECTION_LIMIT_UNLIMITED,
+               telnet_new_connection, telnet_input, telnet_connection_closed,
                telnet_service);
+
+       if (ret != ERROR_OK) {
+               free(telnet_service);
+               return ret;
+       }
+
+       return ERROR_OK;
 }
 
 /* daemon configuration command telnet_port */
@@ -654,7 +726,7 @@ static const struct command_registration telnet_command_handlers[] = {
        {
                .name = "telnet_port",
                .handler = handle_telnet_port_command,
-               .mode = COMMAND_ANY,
+               .mode = COMMAND_CONFIG,
                .help = "Specify port on which to listen "
                        "for incoming telnet connections.  "
                        "Read help on 'gdb_port'.",
@@ -668,3 +740,8 @@ int telnet_register_commands(struct command_context *cmd_ctx)
        telnet_port = strdup("4444");
        return register_commands(cmd_ctx, NULL, telnet_command_handlers);
 }
+
+void telnet_service_free(void)
+{
+       free(telnet_port);
+}