/* * tio - a serial device I/O tool * * Copyright (c) 2014-2024 Martin Lund * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 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., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include "misc.h" #include "print.h" #include "options.h" #include "tty.h" #include "xymodem.h" #include "log.h" #include "script.h" #include "fs.h" #define MAX_BUFFER_SIZE 2000 // Maximum size of circular buffer static int device_fd; static char circular_buffer[MAX_BUFFER_SIZE]; static char match_string[MAX_BUFFER_SIZE]; static int buffer_size = 0; static char script_init[] = "function set(arg)\n" " local dtr = arg.DTR or -1\n" " local rts = arg.RTS or -1\n" " local cts = arg.CTS or -1\n" " local dsr = arg.DSR or -1\n" " local cd = arg.CD or -1\n" " local ri = arg.RI or -1\n" " line_set(dtr, rts, cts, dsr, cd, ri)\n" "end\n"; // lua: sleep(seconds) static int sleep_(lua_State *L) { long seconds = lua_tointeger(L, 1); if (seconds < 0) { return 0; } tio_printf("Sleeping %ld seconds", seconds); sleep(seconds); return 0; } // lua: msleep(miliseconds) static int msleep(lua_State *L) { long mseconds = lua_tointeger(L, 1); long useconds = mseconds * 1000; if (useconds < 0) { return 0; } tio_printf("Sleeping %ld ms", mseconds); usleep(useconds); return 0; } // lua: line_set(dtr,rts,cts,dsr,cd,ri) static int line_set(lua_State *L) { tty_line_config_t line_config[6] = { }; int dtr = lua_tointeger(L, 1); int rts = lua_tointeger(L, 2); int cts = lua_tointeger(L, 3); int dsr = lua_tointeger(L, 4); int cd = lua_tointeger(L, 5); int ri = lua_tointeger(L, 6); if (dtr != -1) { line_config[0].mask = TIOCM_DTR; line_config[0].value = dtr; line_config[0].reserved = true; } if (rts != -1) { line_config[1].mask = TIOCM_RTS; line_config[1].value = rts; line_config[1].reserved = true; } if (cts != -1) { line_config[2].mask = TIOCM_CTS; line_config[2].value = cts; line_config[2].reserved = true; } if (dsr != -1) { line_config[3].mask = TIOCM_DSR; line_config[3].value = dsr; line_config[3].reserved = true; } if (cd != -1) { line_config[4].mask = TIOCM_CD; line_config[4].value = cd; line_config[4].reserved = true; } if (ri != -1) { line_config[5].mask = TIOCM_RI; line_config[5].value = ri; line_config[5].reserved = true; } tty_line_set(device_fd, line_config); return 0; } // lua: modem_send(file, protocol) static int modem_send(lua_State *L) { const char *file = lua_tostring(L, 1); int protocol = lua_tointeger(L, 2); if (file == NULL) { return 0; } switch (protocol) { case XMODEM_1K: tio_printf("Sending file '%s' using XMODEM-1K", file); tio_printf("%s", xymodem_send(device_fd, file, XMODEM_1K) < 0 ? "Aborted" : "Done"); break; case XMODEM_CRC: tio_printf("Sending file '%s' using XMODEM-CRC", file); tio_printf("%s", xymodem_send(device_fd, file, XMODEM_CRC) < 0 ? "Aborted" : "Done"); break; case YMODEM: tio_printf("Sending file '%s' using YMODEM", file); tio_printf("%s", xymodem_send(device_fd, file, YMODEM) < 0 ? "Aborted" : "Done"); break; } return 0; } // lua: send(string) static int send(lua_State *L) { const char *string = lua_tostring(L, 1); int ret; if (string == NULL) { return 0; } ret = write(device_fd, string, strlen(string)); lua_pushnumber(L, ret); return 1; } // Function to add a character to the circular expect buffer static void expect_buffer_add(char c) { if (buffer_size < MAX_BUFFER_SIZE) { circular_buffer[buffer_size++] = c; } else { // Shift the buffer to accommodate the new character memmove(circular_buffer, circular_buffer + 1, MAX_BUFFER_SIZE - 1); circular_buffer[MAX_BUFFER_SIZE - 1] = c; } } // Function to match against the circular expect buffer using regex static bool match_regex(regex_t *regex) { char buffer[MAX_BUFFER_SIZE + 1]; // Temporary buffer for regex matching const char *s = circular_buffer; regmatch_t pmatch[1]; regoff_t len; memcpy(buffer, circular_buffer, buffer_size); buffer[buffer_size] = '\0'; // Null-terminate the buffer // Match against the regex int ret = regexec(regex, buffer, 1, pmatch, 0); if (!ret) { // Match found len = pmatch[0].rm_eo - pmatch[0].rm_so; memcpy(match_string, s + pmatch[0].rm_so, len); match_string[len] = '\0'; return true; } else if (ret == REG_NOMATCH) { // No match found, do nothing } else { // Error occurred during matching tio_error_print("Regex match failed"); } return false; } // lua: ret,string = read_string(size, timeout) static int read_string(lua_State *L) { int size = lua_tointeger(L, 1); int timeout = lua_tointeger(L, 2); int ret = 0; char *buffer = malloc(size); if (buffer == NULL) { ret = -1; // Error goto error; } if (timeout == 0) { timeout = -1; // Wait forever } ssize_t bytes_read = read_poll(device_fd, buffer, size, timeout); if (bytes_read < 0) { ret = -1; // Error goto error; } else if (bytes_read == 0) { ret = 0; // Timeout goto error; } for (ssize_t i=0; i 0) { putchar(c); expect_buffer_add(c); if (option.log) { log_putc(c); } // Match against the entire buffer if (match_regex(®ex)) { ret = 1; break; } } else { // Timeout or error break; } } // Cleanup regfree(®ex); error: lua_pushnumber(L, ret); lua_pushstring(L, match_string); return 2; } // lua: exit(code) static int exit_(lua_State *L) { long code = lua_tointeger(L, 1); exit(code); return 0; } // lua: list = tty_search() static int tty_search_(lua_State *L) { UNUSED(L); GList *iter; int i = 1; GList *device_list = tty_search_for_serial_devices(); if (device_list == NULL) { return 0; } // Create a new table lua_newtable(L); // Iterate through found devices for (iter = device_list; iter != NULL; iter = g_list_next(iter)) { device_t *device = (device_t *) iter->data; // Create a new sub-table for each serial device lua_newtable(L); // Add elements to the table lua_pushstring(L, "path"); lua_pushstring(L, device->path); lua_settable(L, -3); lua_pushstring(L, "tid"); lua_pushstring(L, device->tid); lua_settable(L, -3); lua_pushstring(L, "uptime"); lua_pushnumber(L, device->uptime); lua_settable(L, -3); lua_pushstring(L, "driver"); lua_pushstring(L, device->driver); lua_settable(L, -3); lua_pushstring(L, "description"); lua_pushstring(L, device->description); lua_settable(L, -3); // Set the sub-table as a row in the main table lua_rawseti(L, -2, i++); } // Return table return 1; } static void script_buffer_run(lua_State *L, const char *script_buffer) { int error; error = luaL_loadbuffer(L, script_buffer, strlen(script_buffer), "tio") || lua_pcall(L, 0, 0, 0); if (error) { tio_warning_printf("lua: %s\n", lua_tostring(L, -1)); lua_pop(L, 1); /* Pop error message from the stack */ } } static const struct luaL_Reg tio_lib[] = { { "sleep", sleep_}, { "msleep", msleep}, { "line_set", line_set}, { "modem_send", modem_send}, { "send", send}, { "read", read_string}, { "expect", expect}, { "exit", exit_}, { "tty_search", tty_search_}, {NULL, NULL} }; #if !defined LUA_VERSION_NUM || LUA_VERSION_NUM==501 /* ** Adapted from Lua 5.2.0 (for backwards compatibility) */ static void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) { luaL_checkstack(L, nup+1, "too many upvalues"); for (; l->name != NULL; l++) { /* fill the table with given functions */ int i; lua_pushstring(L, l->name); for (i = 0; i < nup; i++) /* copy upvalues to the top */ lua_pushvalue(L, -(nup+1)); lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */ lua_settable(L, -(nup + 3)); } lua_pop(L, nup); /* remove upvalues */ } #endif static void script_load(lua_State *L) { int error; error = luaL_loadbuffer(L, script_init, strlen(script_init), "tio") || lua_pcall(L, 0, 0, 0); if (error) { tio_error_print("%s\n", lua_tostring(L, -1)); lua_pop(L, 1); // Pop error message from the stack } } int lua_register_tio(lua_State *L) { // Register lxi functions lua_getglobal(L, "_G"); luaL_setfuncs(L, tio_lib, 0); lua_pop(L, 1); // Load lua init script script_load(L); return 0; } void script_file_run(lua_State *L, const char *filename) { if (strlen(filename) == 0) { tio_warning_printf("Missing script filename\n"); return; } if (luaL_dofile(L, filename)) { tio_warning_printf("lua: %s\n", lua_tostring(L, -1)); lua_pop(L, 1); /* pop error message from the stack */ return; } } void script_set_global(lua_State *L, const char *name, long value) { lua_pushnumber(L, value); lua_setglobal(L, name); } void script_set_globals(lua_State *L) { script_set_global(L, "toggle", 2); script_set_global(L, "high", 1); script_set_global(L, "low", 0); script_set_global(L, "XMODEM_CRC", XMODEM_CRC); script_set_global(L, "XMODEM_1K", XMODEM_1K); script_set_global(L, "YMODEM", YMODEM); } void script_run(int fd, const char *script_filename) { lua_State *L; device_fd = fd; L = luaL_newstate(); luaL_openlibs(L); // Bind tio functions lua_register_tio(L); // Initialize globals script_set_globals(L); if (script_filename != NULL) { tio_printf("Running script %s", script_filename); script_file_run(L, script_filename); } else if (option.script_filename != NULL) { tio_printf("Running script %s", option.script_filename); script_file_run(L, option.script_filename); } else if (option.script != NULL) { tio_printf("Running script"); script_buffer_run(L, option.script); } lua_close(L); } const char *script_run_state_to_string(script_run_t state) { switch (state) { case SCRIPT_RUN_ONCE: return "once"; case SCRIPT_RUN_ALWAYS: return "always"; case SCRIPT_RUN_NEVER: return "never"; default: return "Unknown"; } }