You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
esp-geiger/main/console/console_ioimpl.c

418 lines
12 KiB

//#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
#include <utils.h>
#include <stdlib.h>
#include <esp_log.h>
#include <driver/uart.h>
#include <errno.h>
#include <esp_vfs_dev.h>
#include <fcntl.h>
#include <settings.h>
#include "console_ioimpl.h"
#include "tasks.h"
#include "telnet_parser.h"
#include "cmd_common.h"
static const char *TAG = "console-io";
void console_internal_error_print(const char *msg) {
ESP_LOGE(TAG, "CONSOLE ERR: %s", msg);
}
void console_setup_uart_stdio(void)
{
assert(CONFIG_ESP_CONSOLE_UART_NUM == UART_NUM_0);
/* Minicom, screen, idf_monitor send CR when ENTER key is pressed */
esp_vfs_dev_uart_port_set_rx_line_endings(UART_NUM_0, ESP_LINE_ENDINGS_CR);
/* Move the caret to the beginning of the next line on '\n' */
esp_vfs_dev_uart_port_set_tx_line_endings(UART_NUM_0, ESP_LINE_ENDINGS_CRLF); // this is the default anyway
/* Disable buffering on stdin and stdout */
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
// fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); // make input non-blocking
#if 0
/* Configure UART. Note that REF_TICK is used so that the baud rate remains
* correct while APB frequency is changing in light sleep mode.
*/
const uart_config_t uart_config = {
.baud_rate = CONFIG_CONSOLE_UART_BAUDRATE,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.use_ref_tick = true
};
ESP_ERROR_CHECK( uart_param_config(CONFIG_CONSOLE_UART_NUM, &uart_config) );
#endif
/* Install UART driver for interrupt-driven reads and writes */
ESP_ERROR_CHECK(uart_driver_install(CONFIG_ESP_CONSOLE_UART_NUM,
/* rxbuf */ 256, /* txbuf */ 0, /* que */ 0, /* uart que */ NULL, /* alloc flags */ 0));
uart_flush(CONFIG_ESP_CONSOLE_UART_NUM);
/* Tell VFS to use UART driver */
esp_vfs_dev_uart_use_driver(CONFIG_ESP_CONSOLE_UART_NUM);
}
static void my_console_task_freertos(void *param) {
console_ctx_t *ctx = param;
assert(CONSOLE_CTX_MAGIC == ctx->__internal_magic); // just make sure it's OK
vTaskDelay(pdMS_TO_TICKS(50)); // ??
bool logged_in = true;
{
struct console_ioimpl *io = ctx->ioctx;
assert(CONSOLE_IOIMPL_MAGIC == io->__magic);
if (io->kind == CONSOLE_IO_TELNET) {
const size_t pwlen = strnlen(gSettings.console_pw, CONSOLE_PW_LEN);
if (pwlen != 0) {
ESP_LOGE(TAG, "Pw=\"%.*s\"", pwlen, gSettings.console_pw);
console_print_ctx(&io->ctx, "Password: ");
// Make the prompt fancy with asterisks and stuff. Backspace is not supported.
int pos = 0;
char buf[CONSOLE_PW_LEN] = {/*zeros*/};
while (1) {
char ch = 0;
console_read_ctx(ctx, &ch, 1);
if (ch == 10 || ch == 13) {
console_write_ctx(ctx, "\n", 1);
break;
}
if (ch >= 32 && ch <= 126) {
if (pos < CONSOLE_PW_LEN) {
buf[pos++] = ch;
}
console_write_ctx(ctx, "*", 1);
}
}
if (0 == strncmp(buf, gSettings.console_pw, CONSOLE_PW_LEN)) {
console_print_ctx(&io->ctx, "Login OK!\n");
} else {
console_print_ctx(&io->ctx, "Login failed!\n");
logged_in = false;
if (io->telnet.tcpcli) {
tcpd_kick(io->telnet.tcpcli);
}
}
}
}
}
if (logged_in) {
console_print_motd(ctx);
console_task(param);
}
ESP_LOGD(TAG, "Console task ended");
// This delay should ensure the TCP client is shut down completely
// before we proceed to free stuff. The delay is deliberately very generous,
// we are in no rush here.
vTaskDelay(pdMS_TO_TICKS(2000));
// Deallocate what console allocated inside ctx, ctx is part of ioimpl so it will NOT be freed
ESP_LOGD(TAG, "Clear console context");
console_ctx_destroy(ctx);
ESP_LOGD(TAG, "Clear IO context");
struct console_ioimpl *io = ctx->ioctx;
assert(CONSOLE_IOIMPL_MAGIC == io->__magic);
// Tear down IO
if (io->kind == CONSOLE_IO_TELNET) {
vRingbufferDelete(io->telnet.console_stdin_ringbuf);
}
// Free ioimpl
free(io);
ESP_LOGI(TAG, "Console task shutdown.");
// suicide the task
vTaskDelete(NULL);
}
static void telnet_shutdown_handler(console_ctx_t *ctx) {
assert(ctx);
assert(CONSOLE_CTX_MAGIC == ctx->__internal_magic); // just make sure it's OK
struct console_ioimpl *io = ctx->ioctx;
assert(io);
assert(CONSOLE_IOIMPL_MAGIC == io->__magic);
tcpd_kick(io->telnet.tcpcli);
}
/**
* Start console working with stdin and stdout
*/
static esp_err_t console_start_io(struct console_ioimpl **pIo, TaskHandle_t * hdl, enum console_iokind kind, TcpdClient_t client) {
struct console_ioimpl * io = calloc(1, sizeof(struct console_ioimpl));
if (io == NULL) {
return ESP_ERR_NO_MEM;
}
if (pIo != NULL) {
*pIo = io;
}
io->__magic = CONSOLE_IOIMPL_MAGIC;
io->kind = kind;
if (kind == CONSOLE_IO_TELNET) {
io->telnet.console_stdin_ringbuf = xRingbufferCreate(CONSOLE_BUFSIZE, RINGBUF_TYPE_BYTEBUF);
if (NULL == io->telnet.console_stdin_ringbuf) {
ESP_LOGE(TAG, "Failed to create RB!");
free(io);
if (pIo != NULL) {
*pIo = NULL;
}
return ESP_ERR_NO_MEM;
}
io->telnet.tcpcli = client;
// Store the "io" reference as "cctx" in the TCP client, so we know
// where to write received data in the callback
tcpd_set_client_ctx(client, io);
} else {
io->files.inf = stdin;
io->files.outf = stdout;
}
// using "static" allocation - context is part of the io struct
// Here "io" is stored as "ioctx" in "ctx" and then passed to the read/write functions
if (NULL == console_ctx_init(&io->ctx, io)) {
ESP_LOGE(TAG, "Console init failed!");
goto fail;
}
if (kind == CONSOLE_IO_FILES) {
io->ctx.exit_allowed = false;
} else {
/* TELNET */
io->ctx.shutdown_handler = telnet_shutdown_handler;
}
assert(io->ctx.__internal_magic == CONSOLE_CTX_MAGIC);
snprintf(io->ctx.prompt, CONSOLE_PROMPT_MAX_LEN, "\x1b[36;1m> \x1b[m");
if (pdPASS != xTaskCreate(
my_console_task_freertos, // func
"console", // name
CONSOLE_TASK_STACK, // stack
&io->ctx, // param
CONSOLE_TASK_PRIO, // prio
hdl // handle dest
)) {
ESP_LOGE(TAG, "Err create console task!");
goto fail;
}
return ESP_OK;
fail:
ESP_LOGE(TAG, "console_start_io FAILED, freeing resources!");
console_ctx_destroy(&io->ctx);
if (kind == CONSOLE_IO_TELNET) {
vRingbufferDelete(io->telnet.console_stdin_ringbuf);
io->telnet.console_stdin_ringbuf = NULL;
}
free(io);
if (pIo != NULL) {
*pIo = NULL;
}
return ESP_FAIL;
}
esp_err_t console_start_stdio(struct console_ioimpl **pIo, TaskHandle_t * hdl) {
ESP_LOGI(TAG, "Start STDIO console task");
return console_start_io(pIo, hdl, CONSOLE_IO_FILES, NULL);
}
esp_err_t console_start_tcp(struct console_ioimpl **pIo, TaskHandle_t * hdl, TcpdClient_t client) {
ESP_LOGI(TAG, "Start TCP console task");
return console_start_io(pIo, hdl, CONSOLE_IO_TELNET, client);
}
/**
* Write to console context.
*
* Return number of characters written, -1 on error.
*/
int console_write_ctx(console_ctx_t *ctx, const char *text, size_t len) {
struct console_ioimpl *io = ctx->ioctx;
assert(CONSOLE_IOIMPL_MAGIC == io->__magic);
if (io->kind == CONSOLE_IO_TELNET) {
const char *wp = (const char *)text;
const char *sp = (const char *)text;
int towrite = 0;
// hack to allow using bare \n
for (size_t i = 0; i < len; i++) {
char c = *sp++;
if (c == '\n') {
// LF: print the chunk before it and a CR.
if (towrite > 0) {
if (ESP_OK != tcpd_send(io->telnet.tcpcli, (uint8_t *) wp, towrite)) {
return -1;
}
}
if (ESP_OK != tcpd_send(io->telnet.tcpcli, (uint8_t*) "\r", 1)) {
return -1;
}
// The LF gets rolled into the next chunk.
wp = sp - 1;
towrite = 1;
} else {
// Non-LF character is printed as is
towrite++;
}
}
// Send the leftovers (chars from last LF or from the start)
if (towrite > 0) {
if (ESP_OK != tcpd_send(io->telnet.tcpcli, (uint8_t*) wp, towrite)) {
return -1;
}
}
return len;
} else {
// File IO
errno = 0;
// the UART driver takes care of encoding \n as \r\n
size_t n = fwrite(text, 1, len, io->files.outf);
if (n != len) {
if (errno || ferror(io->files.outf)) {
return -1;
}
}
return n;
}
}
/**
* Read from console context's input stream.
*
* Return number of characters read, -1 on error
*/
int console_read_ctx(console_ctx_t *ctx, char *dest, size_t count) {
if (!console_have_stdin_ctx(ctx)) {
ESP_LOGW(TAG, "Console stream has no stdin!");
return -1;
}
struct console_ioimpl *io = ctx->ioctx;
assert(CONSOLE_IOIMPL_MAGIC == io->__magic);
if (io->kind == CONSOLE_IO_TELNET) {
size_t remain = count;
char *wp = dest;
do {
size_t rcount = 0;
uint8_t *chunk = xRingbufferReceiveUpTo(io->telnet.console_stdin_ringbuf, &rcount, portMAX_DELAY, remain);
// telnet options negotiation
rcount = telnet_middleware_read(ctx, chunk, rcount);
if (rcount > 0) {
memcpy(wp, chunk, rcount);
wp += rcount;
remain -= rcount;
}
vRingbufferReturnItem(io->telnet.console_stdin_ringbuf, chunk);
} while (remain > 0);
return count;
} else {
// File IO
errno = 0;
// clearerr(io->files.inf);
size_t r = fread(dest, 1, count, io->files.inf);
if (errno != 0 || feof(io->files.inf) /*|| ferror(io->files.inf)*/) {
ESP_LOGW(TAG, "Console stream EOF or error.");
perror("Read err");
return -1;
}
return r;
}
}
/**
* Check if console input stream has bytes ready.
*
* @return number of queued bytes, 0 if none, -1 on error.
*/
int console_can_read_ctx(console_ctx_t *ctx) {
if (!console_have_stdin_ctx(ctx)) {
ESP_LOGW(TAG, "Console stream has no stdin!");
return -1;
}
struct console_ioimpl *io = ctx->ioctx;
assert(CONSOLE_IOIMPL_MAGIC == io->__magic);
if (io->kind == CONSOLE_IO_TELNET) {
uint32_t nitems = 0;
vRingbufferGetInfo(io->telnet.console_stdin_ringbuf, NULL, NULL, NULL, NULL, &nitems);
return nitems;
} else {
// File IO
// a hack with select - this is used rarely, we can afford the overhead
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(fileno(io->files.inf), &readfds);
struct timeval timeout = {0, 0};
int sel_rv = select(1, &readfds, NULL, NULL, &timeout);
if (sel_rv > 0) {
return 1; // at least one
} else if (sel_rv == -1) {
ESP_LOGW(TAG, "Console stream EOF or error.");
return -1; // error
} else {
return 0; // nothing
}
}
}
/**
* Test if console context is not NULL and has stdin stream available
*
* @return have stdin
*/
bool console_have_stdin_ctx(console_ctx_t *ctx) {
if (!ctx->ioctx) return false;
struct console_ioimpl *io = ctx->ioctx;
assert(CONSOLE_IOIMPL_MAGIC == io->__magic);
if (io->kind == CONSOLE_IO_TELNET) {
return io->telnet.tcpcli != NULL &&
io->telnet.console_stdin_ringbuf != NULL;
} else {
// File IO
// ESP_LOGW(TAG, "%p, %d, %d", io->files.inf,
// feof(io->files.inf),
// ferror(io->files.inf));
return io->files.inf != NULL &&
!feof(io->files.inf);
}
}