Add Explicit Session Exit Controls Mini-Features ä2+3
This commit is contained in:
+46
-5
@@ -460,6 +460,28 @@ static void report_command_result(esp_err_t error, int command_result)
|
||||
}
|
||||
}
|
||||
|
||||
static int command_exit(int argc, char **argv)
|
||||
{
|
||||
(void)argv;
|
||||
if (argc != 1) {
|
||||
printf("Usage: exit\n");
|
||||
return 1;
|
||||
}
|
||||
if (!admin_ssh_console_dispatch_is_remote()) {
|
||||
printf("The exit command is available only from an administrative SSH session.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
esp_err_t error = admin_ssh_console_dispatch_defer(
|
||||
ADMIN_SSH_DEFER_DISCONNECT, s_dispatch_token.session_id);
|
||||
if (error != ESP_OK) {
|
||||
printf("Could not schedule SSH session close: %s\n", esp_err_to_name(error));
|
||||
return 1;
|
||||
}
|
||||
printf("SSH session close scheduled after output drains.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dispatch_registered_command(admin_request_t *request)
|
||||
{
|
||||
FILE *saved_stdout = stdout;
|
||||
@@ -724,6 +746,18 @@ esp_err_t admin_ssh_console_init(void)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t admin_ssh_console_register_commands(void)
|
||||
{
|
||||
const esp_console_cmd_t command = {
|
||||
.command = "exit",
|
||||
.help = "Close the current administrative SSH session",
|
||||
.hint = NULL,
|
||||
.func = &command_exit,
|
||||
.argtable = NULL,
|
||||
};
|
||||
return esp_console_cmd_register(&command);
|
||||
}
|
||||
|
||||
esp_err_t admin_ssh_console_start_uart_frontend(void)
|
||||
{
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
@@ -1003,10 +1037,15 @@ bool admin_ssh_console_feed_input(const admin_ssh_console_token_t *token,
|
||||
secure_wipe(completed, sizeof(completed));
|
||||
continue;
|
||||
}
|
||||
if (value == '\r' || value == '\n') {
|
||||
bool exit_requested = value == 0x04U && session->input_length == 0U;
|
||||
if (value == '\r' || value == '\n' || exit_requested) {
|
||||
session->discard_next_lf = value == '\r';
|
||||
history_commit_locked(session);
|
||||
memcpy(request.line, session->input, session->input_length);
|
||||
if (exit_requested) {
|
||||
memcpy(request.line, "exit", sizeof("exit"));
|
||||
} else {
|
||||
history_commit_locked(session);
|
||||
memcpy(request.line, session->input, session->input_length);
|
||||
}
|
||||
request.origin = ADMIN_REQUEST_SSH;
|
||||
request.token = *token;
|
||||
request.principal = session->principal;
|
||||
@@ -1015,8 +1054,10 @@ bool admin_ssh_console_feed_input(const admin_ssh_console_token_t *token,
|
||||
session->input_cursor = 0U;
|
||||
session->history_position = -1;
|
||||
session->command_pending = true;
|
||||
(void)append_output_locked(session, (const uint8_t *)"\r\n",
|
||||
sizeof("\r\n") - 1U);
|
||||
(void)append_output_locked(session,
|
||||
(const uint8_t *)(exit_requested ? "^D\r\n" : "\r\n"),
|
||||
exit_requested ? sizeof("^D\r\n") - 1U
|
||||
: sizeof("\r\n") - 1U);
|
||||
submit = true;
|
||||
} else if (value == 0x03U) {
|
||||
secure_wipe(session->input, sizeof(session->input));
|
||||
|
||||
@@ -43,6 +43,8 @@ typedef struct {
|
||||
|
||||
/* Starts the single command worker. It is the sole esp_console_run() caller. */
|
||||
esp_err_t admin_ssh_console_init(void);
|
||||
/* Register administration-shell-only root commands after esp_console initialization. */
|
||||
esp_err_t admin_ssh_console_register_commands(void);
|
||||
/* Called after all ESP-IDF commands are registered; starts the UART0 frontend. */
|
||||
esp_err_t admin_ssh_console_start_uart_frontend(void);
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include "linenoise/linenoise.h"
|
||||
|
||||
static const char *const s_root_candidates[] = {
|
||||
"help", "debug", "display", "serial", "broker", "usb", "user",
|
||||
"help", "exit", "debug", "display", "serial", "broker", "usb", "user",
|
||||
"wifi", "web", "ssh", "ping", "nslookup", "traceroute", "reboot", "memory",
|
||||
};
|
||||
|
||||
|
||||
@@ -305,6 +305,7 @@ void app_main(void)
|
||||
ESP_ERROR_CHECK(ssh_console_register_commands());
|
||||
ESP_ERROR_CHECK(network_console_register_root_commands());
|
||||
ESP_ERROR_CHECK(system_console_register_commands());
|
||||
ESP_ERROR_CHECK(admin_ssh_console_register_commands());
|
||||
/* Upgrade late UART terminals safely and add nested completion. */
|
||||
console_completion_install();
|
||||
ESP_ERROR_CHECK(admin_ssh_console_start_uart_frontend());
|
||||
|
||||
+18
-2
@@ -119,6 +119,7 @@ static const char s_index_html[] =
|
||||
"<div class=\"button-row\">\n"
|
||||
"<button id=\"request-control\" class=\"button primary\" type=\"button\" disabled>Request control</button>\n"
|
||||
"<button id=\"release-control\" class=\"button danger\" type=\"button\" disabled>Release control</button>\n"
|
||||
"<button id=\"disconnect\" class=\"button danger\" type=\"button\">Disconnect</button>\n"
|
||||
"<button id=\"reconnect\" class=\"button\" type=\"button\">Reconnect</button>\n"
|
||||
"</div>\n"
|
||||
"<p id=\"input-state\" class=\"input-state\" data-enabled=\"false\" aria-live=\"polite\">"
|
||||
@@ -151,6 +152,7 @@ static const char s_app_js[] =
|
||||
"const connectionDetail = element('connection-detail');\n"
|
||||
"const requestControl = element('request-control');\n"
|
||||
"const releaseControl = element('release-control');\n"
|
||||
"const disconnectButton = element('disconnect');\n"
|
||||
"const reconnectButton = element('reconnect');\n"
|
||||
"const terminalHost = element('terminal');\n"
|
||||
"const terminal = new Terminal({\n"
|
||||
@@ -171,6 +173,7 @@ static const char s_app_js[] =
|
||||
"let ticketAbort = null;\n"
|
||||
"let reconnectTimer = null;\n"
|
||||
"let reconnectDelay = 1000;\n"
|
||||
"let reconnectEnabled = true;\n"
|
||||
"let connectionGeneration = 0;\n"
|
||||
"let role = 'observer';\n"
|
||||
"let clientId = null;\n"
|
||||
@@ -193,6 +196,7 @@ static const char s_app_js[] =
|
||||
" terminal.options.disableStdin = !writer;\n"
|
||||
" requestControl.disabled = !socketOpen() || writer;\n"
|
||||
" releaseControl.disabled = !socketOpen() || !writer;\n"
|
||||
" disconnectButton.disabled = unloading || (!reconnectEnabled && socket === null && ticketAbort === null);\n"
|
||||
" inputState.dataset.enabled = writer ? 'true' : 'false';\n"
|
||||
" inputState.textContent = writer\n"
|
||||
" ? 'Writer mode — terminal input is enabled.'\n"
|
||||
@@ -215,7 +219,7 @@ static const char s_app_js[] =
|
||||
" }\n"
|
||||
"};\n"
|
||||
"const scheduleReconnect = () => {\n"
|
||||
" if (unloading || reconnectTimer !== null) return;\n"
|
||||
" if (unloading || !reconnectEnabled || reconnectTimer !== null) return;\n"
|
||||
" const delay = reconnectDelay;\n"
|
||||
" reconnectDelay = Math.min(reconnectDelay * 2, 10000);\n"
|
||||
" setConnection('Disconnected', 'bad', `Reconnecting in ${Math.ceil(delay / 1000)} second(s)…`);\n"
|
||||
@@ -267,7 +271,7 @@ static const char s_app_js[] =
|
||||
" return ticket;\n"
|
||||
"}\n"
|
||||
"async function connect() {\n"
|
||||
" if (unloading) return;\n"
|
||||
" if (unloading || !reconnectEnabled) return;\n"
|
||||
" clearReconnectTimer();\n"
|
||||
" const generation = ++connectionGeneration;\n"
|
||||
" if (ticketAbort !== null) ticketAbort.abort();\n"
|
||||
@@ -331,7 +335,19 @@ static const char s_app_js[] =
|
||||
"releaseControl.addEventListener('click', () => {\n"
|
||||
" if (role === 'writer' && socketOpen()) socket.send('release-writer');\n"
|
||||
"});\n"
|
||||
"disconnectButton.addEventListener('click', () => {\n"
|
||||
" reconnectEnabled = false;\n"
|
||||
" ++connectionGeneration;\n"
|
||||
" clearReconnectTimer();\n"
|
||||
" if (ticketAbort !== null) { ticketAbort.abort(); ticketAbort = null; }\n"
|
||||
" if (socket !== null) { const previous = socket; socket = null; previous.close(); }\n"
|
||||
" clientId = null;\n"
|
||||
" clientIdField.textContent = '—';\n"
|
||||
" setRole('observer');\n"
|
||||
" setConnection('Disconnected', 'warn', 'Disconnected by user. Automatic reconnect is paused.');\n"
|
||||
"});\n"
|
||||
"reconnectButton.addEventListener('click', () => {\n"
|
||||
" reconnectEnabled = true;\n"
|
||||
" reconnectDelay = 1000;\n"
|
||||
" connect();\n"
|
||||
"});\n"
|
||||
|
||||
Reference in New Issue
Block a user