X-Git-Url: https://review.openocd.org/gitweb?a=blobdiff_plain;f=src%2Fserver%2Ftelnet_server.c;h=06a9c7a6c74a74597d2f5f5626f5a9f90eabc932;hb=e0010c3e6fe09d9d332b279e661858fc449c0645;hp=5d2e3bc9c15f30b7efa899580a2ba2cd80336573;hpb=b48d1f66378fac886d5bc32d7302da48c89d8a75;p=openocd.git
diff --git a/src/server/telnet_server.c b/src/server/telnet_server.c
index 5d2e3bc9c1..06a9c7a6c7 100644
--- a/src/server/telnet_server.c
+++ b/src/server/telnet_server.c
@@ -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 . *
***************************************************************************/
#ifdef HAVE_CONFIG_H
@@ -30,8 +28,9 @@
#include "telnet_server.h"
#include
+#include
-static const char *telnet_port;
+static char *telnet_port;
static char *negotiate =
"\xFF\xFB\x03" /* IAC WILL Suppress Go Ahead */
@@ -40,6 +39,7 @@ static char *negotiate =
"\xFF\xFE\x01"; /* IAC DON'T Echo */
#define CTRL(c) (c - '@')
+#define TELNET_HISTORY ".openocd_history"
/* The only way we can detect that the socket is closed is the first time
* we write to it, we will fail. Subsequent write operations will
@@ -127,19 +127,101 @@ static void telnet_log_callback(void *priv, const char *file, unsigned line,
telnet_write(connection, "\b", 1);
}
+static void telnet_load_history(struct telnet_connection *t_con)
+{
+ FILE *histfp;
+ char buffer[TELNET_BUFFER_SIZE];
+ int i = 0;
+
+ char *history = get_home_dir(TELNET_HISTORY);
+
+ if (history == NULL) {
+ LOG_INFO("unable to get user home directory, telnet history will be disabled");
+ return;
+ }
+
+ histfp = fopen(history, "rb");
+
+ if (histfp) {
+
+ while (fgets(buffer, sizeof(buffer), histfp) != NULL) {
+
+ char *p = strchr(buffer, '\n');
+ if (p)
+ *p = '\0';
+ if (buffer[0] && i < TELNET_LINE_HISTORY_SIZE)
+ t_con->history[i++] = strdup(buffer);
+ }
+
+ t_con->next_history = i;
+ t_con->next_history %= TELNET_LINE_HISTORY_SIZE;
+ /* try to set to last entry - 1, that way we skip over any exit/shutdown cmds */
+ t_con->current_history = t_con->next_history > 0 ? i - 1 : 0;
+ fclose(histfp);
+ }
+
+ free(history);
+}
+
+static void telnet_save_history(struct telnet_connection *t_con)
+{
+ FILE *histfp;
+ int i;
+ int num;
+
+ char *history = get_home_dir(TELNET_HISTORY);
+
+ if (history == NULL) {
+ LOG_INFO("unable to get user home directory, telnet history will be disabled");
+ return;
+ }
+
+ histfp = fopen(history, "wb");
+
+ if (histfp) {
+
+ num = TELNET_LINE_HISTORY_SIZE;
+ i = t_con->current_history + 1;
+ i %= TELNET_LINE_HISTORY_SIZE;
+
+ while (t_con->history[i] == NULL && num > 0) {
+ i++;
+ i %= TELNET_LINE_HISTORY_SIZE;
+ num--;
+ }
+
+ if (num > 0) {
+ for (; num > 0; num--) {
+ fprintf(histfp, "%s\n", t_con->history[i]);
+ i++;
+ i %= TELNET_LINE_HISTORY_SIZE;
+ }
+ }
+ fclose(histfp);
+ }
+
+ free(history);
+}
+
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->line_size = 0;
telnet_connection->line_cursor = 0;
- telnet_connection->option_size = 0;
telnet_connection->prompt = strdup("> ");
telnet_connection->state = TELNET_STATE_DATA;
@@ -155,8 +237,8 @@ static int telnet_new_connection(struct connection *connection)
telnet_write(connection, "\r\n", 2);
}
- telnet_write(connection, "\r", 1); /* the prompt is always placed at the line beginning
- **/
+ /* the prompt is always placed at the line beginning */
+ telnet_write(connection, "\r", 1);
telnet_prompt(connection);
/* initialize history */
@@ -164,6 +246,7 @@ static int telnet_new_connection(struct connection *connection)
telnet_connection->history[i] = NULL;
telnet_connection->next_history = 0;
telnet_connection->current_history = 0;
+ telnet_load_history(telnet_connection);
log_add_callback(telnet_log_callback, connection);
@@ -187,6 +270,63 @@ static void telnet_clear_line(struct connection *connection,
t_con->line_cursor = 0;
}
+static void telnet_history_go(struct connection *connection, int idx)
+{
+ struct telnet_connection *t_con = connection->priv;
+
+ if (t_con->history[idx]) {
+ telnet_clear_line(connection, t_con);
+ t_con->line_size = strlen(t_con->history[idx]);
+ t_con->line_cursor = t_con->line_size;
+ memcpy(t_con->line, t_con->history[idx], t_con->line_size);
+ telnet_write(connection, t_con->line, t_con->line_size);
+ t_con->current_history = idx;
+ }
+ t_con->state = TELNET_STATE_DATA;
+}
+
+static void telnet_history_up(struct connection *connection)
+{
+ struct telnet_connection *t_con = connection->priv;
+
+ int last_history = (t_con->current_history > 0) ?
+ t_con->current_history - 1 :
+ TELNET_LINE_HISTORY_SIZE-1;
+ telnet_history_go(connection, last_history);
+}
+
+static void telnet_history_down(struct connection *connection)
+{
+ struct telnet_connection *t_con = connection->priv;
+
+ int next_history = (t_con->current_history + 1) % TELNET_LINE_HISTORY_SIZE;
+ telnet_history_go(connection, next_history);
+}
+
+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;
@@ -213,19 +353,11 @@ static int telnet_input(struct connection *connection)
else {
if (isprint(*buf_p)) { /* printable character */
/* watch buffer size leaving one spare character for
- *string null termination */
+ * string null termination */
if (t_con->line_size == TELNET_LINE_MAX_SIZE-1) {
- /* output audible bell if buffer is full */
- telnet_write(connection, "\x07", 1); /*
- *"\a"
- *does
- *not
- *work,
- *at
- *least
- *on
- *windows
- **/
+ /* output audible bell if buffer is full
+ * "\a" does not work, at least on windows */
+ telnet_write(connection, "\x07", 1);
} else if (t_con->line_cursor == t_con->line_size) {
telnet_write(connection, buf_p, 1);
t_con->line[t_con->line_size++] = *buf_p;
@@ -248,13 +380,10 @@ static int telnet_input(struct connection *connection)
if (*buf_p == 0x1b) { /* escape */
t_con->state = TELNET_STATE_ESCAPE;
t_con->last_escape = '\x00';
- } else if ((*buf_p == 0xd) || (*buf_p == 0xa)) { /*
- *CR/LF
- **/
+ } else if ((*buf_p == 0xd) || (*buf_p == 0xa)) { /* CR/LF */
int retval;
- /* skip over combinations with CR/LF and NUL
- *characters */
+ /* skip over combinations with CR/LF and NUL characters */
if ((bytes_read > 1) && ((*(buf_p + 1) == 0xa) ||
(*(buf_p + 1) == 0xd))) {
buf_p++;
@@ -271,10 +400,8 @@ static int telnet_input(struct connection *connection)
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 */
+ /* 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];
@@ -289,27 +416,23 @@ static int telnet_input(struct connection *connection)
continue;
}
- /* save only non-blank not repeating lines
- *in the history */
+ /* save only non-blank not repeating lines in the history */
char *prev_line = t_con->history[(t_con->current_history > 0) ?
t_con->current_history - 1 : TELNET_LINE_HISTORY_SIZE-1];
if (*t_con->line && (prev_line == NULL ||
strcmp(t_con->line, prev_line))) {
- /* if the history slot is already
- *taken, free it */
+ /* 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]);
/* add line to history */
t_con->history[t_con->next_history] = strdup(t_con->line);
- /* wrap history at
- *TELNET_LINE_HISTORY_SIZE */
+ /* wrap history at TELNET_LINE_HISTORY_SIZE */
t_con->next_history = (t_con->next_history + 1) %
TELNET_LINE_HISTORY_SIZE;
- /* current history line starts at
- *the new entry */
+ /* current history line starts at the new entry */
t_con->current_history =
t_con->next_history;
@@ -320,10 +443,11 @@ static int telnet_input(struct connection *connection)
t_con->line_size = 0;
- t_con->line_cursor = -1; /* to supress prompt
- *in log callback
- *during command
- *execution */
+ /* to suppress prompt in log callback during command execution */
+ t_con->line_cursor = -1;
+
+ if (strcmp(t_con->line, "shutdown") == 0)
+ telnet_save_history(t_con);
retval = command_run_line(command_context, t_con->line);
@@ -332,25 +456,14 @@ static int telnet_input(struct connection *connection)
if (retval == ERROR_COMMAND_CLOSE_CONNECTION)
return ERROR_SERVER_REMOTE_CLOSED;
- telnet_write(connection, "\r", 1); /*
- *the
- *prompt
- *is
- *always
- *placed
- *at
- *the
- *line
- *beginning
- **/
+ /* the prompt is always * placed at the line beginning */
+ telnet_write(connection, "\r", 1);
+
retval = telnet_prompt(connection);
if (retval == ERROR_SERVER_REMOTE_CLOSED)
return ERROR_SERVER_REMOTE_CLOSED;
- } else if ((*buf_p == 0x7f) || (*buf_p == 0x8)) { /*
- *delete
- *character
- **/
+ } 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;
@@ -372,10 +485,8 @@ static int telnet_input(struct connection *connection)
} else {
t_con->line_size--;
t_con->line_cursor--;
- /* back space: move the
- *'printer' head one char
- *back, overwrite with
- *space, move back again */
+ /* back space: move the 'printer' head one char
+ * back, overwrite with space, move back again */
telnet_write(connection, "\b \b", 3);
}
}
@@ -391,7 +502,15 @@ static int telnet_input(struct connection *connection)
if (t_con->line_cursor < t_con->line_size)
telnet_write(connection, t_con->line + t_con->line_cursor++, 1);
t_con->state = TELNET_STATE_DATA;
- } else
+ } else if (*buf_p == CTRL('P')) /* cursor up */
+ telnet_history_up(connection);
+ else if (*buf_p == CTRL('N')) /* cursor down */
+ telnet_history_down(connection);
+ 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
LOG_DEBUG("unhandled nonprintable: %2.2x", *buf_p);
}
}
@@ -436,28 +555,9 @@ static int telnet_input(struct connection *connection)
t_con->line + t_con->line_cursor++, 1);
t_con->state = TELNET_STATE_DATA;
} else if (*buf_p == 'A') { /* cursor up */
- int last_history = (t_con->current_history > 0) ?
- t_con->current_history - 1 : TELNET_LINE_HISTORY_SIZE-1;
- if (t_con->history[last_history]) {
- telnet_clear_line(connection, t_con);
- t_con->line_size = strlen(t_con->history[last_history]);
- t_con->line_cursor = t_con->line_size;
- memcpy(t_con->line, t_con->history[last_history], t_con->line_size);
- telnet_write(connection, t_con->line, t_con->line_size);
- t_con->current_history = last_history;
- }
- t_con->state = TELNET_STATE_DATA;
+ telnet_history_up(connection);
} else if (*buf_p == 'B') { /* cursor down */
- int next_history = (t_con->current_history + 1) % TELNET_LINE_HISTORY_SIZE;
- if (t_con->history[next_history]) {
- telnet_clear_line(connection, t_con);
- t_con->line_size = strlen(t_con->history[next_history]);
- t_con->line_cursor = t_con->line_size;
- memcpy(t_con->line, t_con->history[next_history], t_con->line_size);
- telnet_write(connection, t_con->line, t_con->line_size);
- t_con->current_history = next_history;
- }
- t_con->state = TELNET_STATE_DATA;
+ telnet_history_down(connection);
} else if (*buf_p == '3')
t_con->last_escape = *buf_p;
else
@@ -500,7 +600,7 @@ static int telnet_input(struct connection *connection)
break;
default:
LOG_ERROR("unknown telnet state");
- exit(-1);
+ return ERROR_FAIL;
}
bytes_read--;
@@ -522,6 +622,9 @@ static int telnet_connection_closed(struct connection *connection)
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]);
@@ -548,17 +651,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 */