2018-10-31 19:43:23 -03:00
|
|
|
/*
|
|
|
|
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 3 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, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2023-06-07 02:56:50 -03:00
|
|
|
#include "AP_Scripting_config.h"
|
|
|
|
|
|
|
|
#if AP_SCRIPTING_ENABLED
|
|
|
|
|
2018-10-31 19:43:23 -03:00
|
|
|
#include "lua_scripts.h"
|
|
|
|
#include <AP_HAL/AP_HAL.h>
|
2019-10-15 04:02:08 -03:00
|
|
|
#include "AP_Scripting.h"
|
2022-03-03 23:29:47 -04:00
|
|
|
#include <AP_Logger/AP_Logger.h>
|
2018-10-31 19:43:23 -03:00
|
|
|
|
2020-05-10 06:37:10 -03:00
|
|
|
#include <AP_Scripting/lua_generated_bindings.h>
|
2019-03-14 04:38:12 -03:00
|
|
|
|
2022-01-31 19:37:04 -04:00
|
|
|
#define DISABLE_INTERRUPTS_FOR_SCRIPT_RUN 0
|
|
|
|
|
2018-10-31 19:43:23 -03:00
|
|
|
extern const AP_HAL::HAL& hal;
|
2023-03-09 00:47:50 -04:00
|
|
|
#define ENABLE_DEBUG_MODULE 0
|
2018-10-31 19:43:23 -03:00
|
|
|
|
|
|
|
bool lua_scripts::overtime;
|
2018-11-14 19:44:15 -04:00
|
|
|
jmp_buf lua_scripts::panic_jmp;
|
2021-08-25 21:29:41 -03:00
|
|
|
char *lua_scripts::error_msg_buf;
|
2022-08-30 17:47:36 -03:00
|
|
|
HAL_Semaphore lua_scripts::error_msg_buf_sem;
|
2021-08-25 21:29:41 -03:00
|
|
|
uint8_t lua_scripts::print_error_count;
|
|
|
|
uint32_t lua_scripts::last_print_ms;
|
2018-10-31 19:43:23 -03:00
|
|
|
|
2023-11-25 13:18:37 -04:00
|
|
|
uint32_t lua_scripts::loaded_checksum;
|
|
|
|
uint32_t lua_scripts::running_checksum;
|
|
|
|
HAL_Semaphore lua_scripts::crc_sem;
|
|
|
|
|
2024-05-23 18:00:45 -03:00
|
|
|
lua_scripts::lua_scripts(const AP_Int32 &vm_steps, const AP_Int32 &heap_size, const AP_Int8 &debug_options)
|
2019-03-22 12:19:59 -03:00
|
|
|
: _vm_steps(vm_steps),
|
2024-05-23 18:00:45 -03:00
|
|
|
_debug_options(debug_options)
|
2022-12-21 18:36:11 -04:00
|
|
|
{
|
|
|
|
_heap.create(heap_size, 4);
|
2018-10-31 19:43:23 -03:00
|
|
|
}
|
|
|
|
|
2022-04-19 20:34:23 -03:00
|
|
|
lua_scripts::~lua_scripts() {
|
2022-12-21 18:36:11 -04:00
|
|
|
_heap.destroy();
|
2022-04-19 20:34:23 -03:00
|
|
|
}
|
|
|
|
|
2018-10-31 19:43:23 -03:00
|
|
|
void lua_scripts::hook(lua_State *L, lua_Debug *ar) {
|
|
|
|
lua_scripts::overtime = true;
|
|
|
|
|
|
|
|
// we need to aggressively bail out as we are over time
|
|
|
|
// so we will aggressively trap errors until we clear out
|
|
|
|
lua_sethook(L, hook, LUA_MASKCOUNT, 1);
|
|
|
|
|
|
|
|
luaL_error(L, "Exceeded CPU time");
|
|
|
|
}
|
|
|
|
|
2021-08-25 21:29:41 -03:00
|
|
|
void lua_scripts::print_error(MAV_SEVERITY severity) {
|
2022-08-30 17:47:36 -03:00
|
|
|
error_msg_buf_sem.take_blocking();
|
2021-08-25 21:29:41 -03:00
|
|
|
if (error_msg_buf == nullptr) {
|
2022-08-30 17:47:36 -03:00
|
|
|
error_msg_buf_sem.give();
|
2021-08-25 21:29:41 -03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
last_print_ms = AP_HAL::millis();
|
|
|
|
GCS_SEND_TEXT(severity, "Lua: %s", error_msg_buf);
|
2022-08-30 17:47:36 -03:00
|
|
|
error_msg_buf_sem.give();
|
2021-08-25 21:29:41 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
void lua_scripts::set_and_print_new_error_message(MAV_SEVERITY severity, const char *fmt, ...) {
|
2022-08-30 17:47:36 -03:00
|
|
|
error_msg_buf_sem.take_blocking();
|
|
|
|
|
2021-08-25 21:29:41 -03:00
|
|
|
// reset buffer and print count
|
|
|
|
print_error_count = 0;
|
|
|
|
if (error_msg_buf) {
|
2022-12-21 18:36:11 -04:00
|
|
|
_heap.deallocate(error_msg_buf);
|
2021-08-25 21:29:41 -03:00
|
|
|
error_msg_buf = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
// generate va_list and create a copy
|
|
|
|
va_list arg_list, arg_list_copy;
|
|
|
|
va_start(arg_list, fmt);
|
|
|
|
va_copy(arg_list_copy, arg_list);
|
|
|
|
|
|
|
|
// dry run to work out the required length
|
2022-05-04 12:52:13 -03:00
|
|
|
int len = hal.util->vsnprintf(nullptr, 0, fmt, arg_list_copy);
|
2021-08-25 21:29:41 -03:00
|
|
|
|
|
|
|
// finished with copy
|
|
|
|
va_end(arg_list_copy);
|
|
|
|
|
|
|
|
if (len <= 0) {
|
|
|
|
// nothing to print, something has gone wrong
|
|
|
|
va_end(arg_list);
|
2022-08-30 17:47:36 -03:00
|
|
|
error_msg_buf_sem.give();
|
2021-08-25 21:29:41 -03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// allocate buffer on scripting heap
|
2022-12-21 18:36:11 -04:00
|
|
|
error_msg_buf = (char *)_heap.allocate(len+1);
|
2021-08-25 21:29:41 -03:00
|
|
|
if (!error_msg_buf) {
|
|
|
|
// allocation failed
|
|
|
|
va_end(arg_list);
|
2022-08-30 17:47:36 -03:00
|
|
|
error_msg_buf_sem.give();
|
2021-08-25 21:29:41 -03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// do actual print to buffer and clear va list
|
|
|
|
hal.util->vsnprintf(error_msg_buf, len+1, fmt, arg_list);
|
|
|
|
va_end(arg_list);
|
|
|
|
|
|
|
|
// print to cosole and GCS
|
2022-03-21 06:40:15 -03:00
|
|
|
DEV_PRINTF("Lua: %s\n", error_msg_buf);
|
2022-08-30 17:47:36 -03:00
|
|
|
|
|
|
|
error_msg_buf_sem.give();
|
2021-08-25 21:29:41 -03:00
|
|
|
print_error(severity);
|
|
|
|
}
|
|
|
|
|
2018-11-14 19:44:15 -04:00
|
|
|
int lua_scripts::atpanic(lua_State *L) {
|
2021-08-25 21:29:41 -03:00
|
|
|
set_and_print_new_error_message(MAV_SEVERITY_CRITICAL, "Panic: %s", lua_tostring(L, -1));
|
2018-11-14 19:44:15 -04:00
|
|
|
longjmp(panic_jmp, 1);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-10-30 21:16:57 -03:00
|
|
|
// helper for print and log of runtime stats
|
|
|
|
void lua_scripts::update_stats(const char *name, uint32_t run_time, int total_mem, int run_mem)
|
|
|
|
{
|
|
|
|
if ((_debug_options.get() & uint8_t(DebugLevel::RUNTIME_MSG)) != 0) {
|
2023-09-21 22:24:30 -03:00
|
|
|
GCS_SEND_TEXT(MAV_SEVERITY_DEBUG, "Lua: Time: %u Mem: %d + %d",
|
2022-10-30 21:16:57 -03:00
|
|
|
(unsigned int)run_time,
|
|
|
|
(int)total_mem,
|
|
|
|
(int)run_mem);
|
|
|
|
}
|
2023-10-04 13:29:34 -03:00
|
|
|
#if HAL_LOGGING_ENABLED
|
2022-10-30 21:16:57 -03:00
|
|
|
if ((_debug_options.get() & uint8_t(DebugLevel::LOG_RUNTIME)) != 0) {
|
|
|
|
struct log_Scripting pkt {
|
|
|
|
LOG_PACKET_HEADER_INIT(LOG_SCRIPTING_MSG),
|
|
|
|
time_us : AP_HAL::micros64(),
|
|
|
|
name : {},
|
|
|
|
run_time : run_time,
|
|
|
|
total_mem : total_mem,
|
|
|
|
run_mem : run_mem
|
|
|
|
};
|
|
|
|
const char * name_short = strrchr(name, '/');
|
|
|
|
if ((strlen(name) > sizeof(pkt.name)) && (name_short != nullptr)) {
|
|
|
|
strncpy_noterm(pkt.name, name_short+1, sizeof(pkt.name));
|
|
|
|
} else {
|
|
|
|
strncpy_noterm(pkt.name, name, sizeof(pkt.name));
|
|
|
|
}
|
|
|
|
AP::logger().WriteBlock(&pkt, sizeof(pkt));
|
|
|
|
}
|
2023-10-04 13:29:34 -03:00
|
|
|
#endif // HAL_LOGGING_ENABLED
|
2022-10-30 21:16:57 -03:00
|
|
|
}
|
|
|
|
|
2018-11-01 01:08:37 -03:00
|
|
|
lua_scripts::script_info *lua_scripts::load_script(lua_State *L, char *filename) {
|
2018-10-31 19:43:23 -03:00
|
|
|
if (int error = luaL_loadfile(L, filename)) {
|
|
|
|
switch (error) {
|
|
|
|
case LUA_ERRSYNTAX:
|
2021-08-25 21:29:41 -03:00
|
|
|
set_and_print_new_error_message(MAV_SEVERITY_CRITICAL, "Error: %s", lua_tostring(L, -1));
|
2019-07-17 01:27:25 -03:00
|
|
|
lua_pop(L, lua_gettop(L));
|
2018-10-31 19:43:23 -03:00
|
|
|
return nullptr;
|
|
|
|
case LUA_ERRMEM:
|
2021-08-25 21:29:41 -03:00
|
|
|
set_and_print_new_error_message(MAV_SEVERITY_CRITICAL, "Insufficent memory loading %s", filename);
|
2019-07-17 01:27:25 -03:00
|
|
|
lua_pop(L, lua_gettop(L));
|
2018-10-31 19:43:23 -03:00
|
|
|
return nullptr;
|
2018-11-14 19:44:15 -04:00
|
|
|
case LUA_ERRFILE:
|
2021-08-25 21:29:41 -03:00
|
|
|
set_and_print_new_error_message(MAV_SEVERITY_CRITICAL, "Unable to load the file: %s", lua_tostring(L, -1));
|
2019-07-17 01:27:25 -03:00
|
|
|
lua_pop(L, lua_gettop(L));
|
2018-11-14 19:44:15 -04:00
|
|
|
return nullptr;
|
2018-10-31 19:43:23 -03:00
|
|
|
default:
|
2021-08-25 21:29:41 -03:00
|
|
|
set_and_print_new_error_message(MAV_SEVERITY_CRITICAL, "Unknown error (%d) loading %s", error, filename);
|
2019-07-17 01:27:25 -03:00
|
|
|
lua_pop(L, lua_gettop(L));
|
2018-10-31 19:43:23 -03:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-30 21:16:57 -03:00
|
|
|
const int loadMem = lua_gc(L, LUA_GCCOUNT, 0) * 1024 + lua_gc(L, LUA_GCCOUNTB, 0);
|
|
|
|
const uint32_t loadStart = AP_HAL::micros();
|
|
|
|
|
2022-12-21 18:36:11 -04:00
|
|
|
script_info *new_script = (script_info *)_heap.allocate(sizeof(script_info));
|
2018-10-31 19:43:23 -03:00
|
|
|
if (new_script == nullptr) {
|
|
|
|
// No memory, shouldn't happen, we even attempted to do a GC
|
2021-08-25 21:29:41 -03:00
|
|
|
set_and_print_new_error_message(MAV_SEVERITY_CRITICAL, "Insufficent memory loading %s", filename);
|
2018-10-31 19:43:23 -03:00
|
|
|
lua_pop(L, 1); // we can't use the function we just loaded, so ditch it
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-02-05 18:58:33 -04:00
|
|
|
create_sandbox(L);
|
2024-07-25 18:34:22 -03:00
|
|
|
lua_pushvalue(L, -1); // duplicate environment for reference below
|
|
|
|
lua_setupvalue(L, -3, 1);
|
2020-02-05 18:58:33 -04:00
|
|
|
|
2022-10-30 21:16:57 -03:00
|
|
|
const uint32_t loadEnd = AP_HAL::micros();
|
|
|
|
const int endMem = lua_gc(L, LUA_GCCOUNT, 0) * 1024 + lua_gc(L, LUA_GCCOUNTB, 0);
|
|
|
|
|
|
|
|
update_stats(filename, loadEnd-loadStart, endMem, loadMem);
|
|
|
|
|
|
|
|
new_script->name = filename;
|
2024-07-25 18:34:22 -03:00
|
|
|
new_script->env_ref = luaL_ref(L, LUA_REGISTRYINDEX); // store reference to script's environment
|
2024-07-25 00:25:29 -03:00
|
|
|
new_script->run_ref = luaL_ref(L, LUA_REGISTRYINDEX); // store reference to function to run
|
2020-02-05 18:58:33 -04:00
|
|
|
new_script->next_run_ms = AP_HAL::millis64() - 1; // force the script to be stale
|
2018-10-31 19:43:23 -03:00
|
|
|
|
2023-11-25 13:18:37 -04:00
|
|
|
// Get checksum of file
|
|
|
|
uint32_t crc = 0;
|
|
|
|
if (AP::FS().crc32(filename, crc)) {
|
|
|
|
// Record crc of this script
|
|
|
|
new_script->crc = crc;
|
|
|
|
{
|
|
|
|
// Apply crc to checksum of all scripts
|
|
|
|
WITH_SEMAPHORE(crc_sem);
|
|
|
|
loaded_checksum ^= crc;
|
|
|
|
running_checksum ^= crc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-05 18:58:33 -04:00
|
|
|
return new_script;
|
|
|
|
}
|
|
|
|
|
|
|
|
void lua_scripts::create_sandbox(lua_State *L) {
|
2020-02-13 16:50:17 -04:00
|
|
|
lua_newtable(L);
|
|
|
|
luaopen_base_sandbox(L);
|
2023-03-09 00:47:50 -04:00
|
|
|
|
|
|
|
#if ENABLE_DEBUG_MODULE
|
|
|
|
lua_pushstring(L, "debug");
|
|
|
|
luaopen_debug(L);
|
|
|
|
lua_settable(L, -3);
|
|
|
|
#endif
|
2020-02-13 16:50:17 -04:00
|
|
|
lua_pushstring(L, "math");
|
|
|
|
luaopen_math(L);
|
|
|
|
lua_settable(L, -3);
|
|
|
|
lua_pushstring(L, "table");
|
|
|
|
luaopen_table(L);
|
|
|
|
lua_settable(L, -3);
|
|
|
|
lua_pushstring(L, "string");
|
|
|
|
luaopen_string(L);
|
|
|
|
lua_settable(L, -3);
|
|
|
|
lua_pushstring(L, "io");
|
|
|
|
luaopen_io(L);
|
|
|
|
lua_settable(L, -3);
|
|
|
|
lua_pushstring(L, "utf8");
|
|
|
|
luaopen_utf8(L);
|
|
|
|
lua_settable(L, -3);
|
2023-03-09 00:47:50 -04:00
|
|
|
lua_pushstring(L, "package");
|
|
|
|
luaopen_package(L);
|
|
|
|
lua_settable(L, -3);
|
2018-10-31 19:43:23 -03:00
|
|
|
|
2023-03-09 00:47:50 -04:00
|
|
|
load_generated_sandbox(L);
|
2018-10-31 19:43:23 -03:00
|
|
|
}
|
|
|
|
|
2018-11-01 01:08:37 -03:00
|
|
|
void lua_scripts::load_all_scripts_in_dir(lua_State *L, const char *dirname) {
|
|
|
|
if (dirname == nullptr) {
|
|
|
|
return;
|
|
|
|
}
|
2020-03-11 22:58:41 -03:00
|
|
|
auto *d = AP::FS().opendir(dirname);
|
2018-11-01 01:08:37 -03:00
|
|
|
if (d == nullptr) {
|
2024-09-04 11:31:30 -03:00
|
|
|
// this disk_space check will return 0 if we don't have a real
|
|
|
|
// filesystem (ie. no Posix or FatFs). Do not warn in this case.
|
|
|
|
if (AP::FS().disk_space(dirname) != 0) {
|
|
|
|
GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "Lua: open directory (%s) failed", dirname);
|
|
|
|
}
|
2018-11-01 01:08:37 -03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// load anything that ends in .lua
|
2019-08-01 07:55:03 -03:00
|
|
|
for (struct dirent *de=AP::FS().readdir(d); de; de=AP::FS().readdir(d)) {
|
2018-11-01 01:08:37 -03:00
|
|
|
uint8_t length = strlen(de->d_name);
|
|
|
|
if (length < 5) {
|
|
|
|
// not long enough
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-06-30 14:20:05 -03:00
|
|
|
if ((de->d_name[0] == '.') || strncmp(&de->d_name[length-4], ".lua", 4)) {
|
|
|
|
// starts with . (hidden file) or doesn't end in .lua
|
2018-11-01 01:08:37 -03:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: because chunk name fetching is not working we are allocating and storing an extra string we shouldn't need to
|
|
|
|
size_t size = strlen(dirname) + strlen(de->d_name) + 2;
|
2022-12-21 18:36:11 -04:00
|
|
|
char * filename = (char *) _heap.allocate(size);
|
2018-11-01 01:08:37 -03:00
|
|
|
if (filename == nullptr) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
snprintf(filename, size, "%s/%s", dirname, de->d_name);
|
|
|
|
|
|
|
|
// we have something that looks like a lua file, attempt to load it
|
|
|
|
script_info * script = load_script(L, filename);
|
|
|
|
if (script == nullptr) {
|
2022-12-21 18:36:11 -04:00
|
|
|
_heap.deallocate(filename);
|
2018-11-01 01:08:37 -03:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
reschedule_script(script);
|
|
|
|
|
2021-12-06 00:11:29 -04:00
|
|
|
#if HAL_LOGGER_FILE_CONTENTS_ENABLED
|
2021-11-13 13:42:49 -04:00
|
|
|
if ((_debug_options.get() & uint8_t(DebugLevel::SUPPRESS_SCRIPT_LOG)) == 0) {
|
|
|
|
AP::logger().log_file_content(filename);
|
|
|
|
}
|
2021-12-06 00:11:29 -04:00
|
|
|
#endif
|
2018-11-01 01:08:37 -03:00
|
|
|
}
|
2019-08-01 07:55:03 -03:00
|
|
|
AP::FS().closedir(d);
|
2018-11-01 01:08:37 -03:00
|
|
|
}
|
|
|
|
|
2020-02-05 18:58:33 -04:00
|
|
|
void lua_scripts::reset_loop_overtime(lua_State *L) {
|
|
|
|
overtime = false;
|
|
|
|
// reset the hook to clear the counter
|
|
|
|
const int32_t vm_steps = MAX(_vm_steps, 1000);
|
|
|
|
lua_sethook(L, hook, LUA_MASKCOUNT, vm_steps);
|
|
|
|
}
|
|
|
|
|
2018-10-31 19:43:23 -03:00
|
|
|
void lua_scripts::run_next_script(lua_State *L) {
|
|
|
|
if (scripts == nullptr) {
|
2018-11-03 18:59:45 -03:00
|
|
|
#if defined(AP_SCRIPTING_CHECKS) && AP_SCRIPTING_CHECKS >= 1
|
2018-10-31 19:43:23 -03:00
|
|
|
AP_HAL::panic("Lua: Attempted to run a script without any scripts queued");
|
2018-11-03 18:59:45 -03:00
|
|
|
#endif // defined(AP_SCRIPTING_CHECKS) && AP_SCRIPTING_CHECKS >= 1
|
2018-10-31 19:43:23 -03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
AP_Scripting: Fix the lua scheduling rate to be referenced from the
start of the update
This allows specifying a return value like "return update, 10" to run
at a near perfect 100Hz, where as before it would be run 10 ms after the
script had completed it's loop, which can be highly variable as the
script experiences interupts from the system, as well as needing the
script author to take responsibility for calculating the desired update
rate at the end. This was always intended to be fixed, but I pushed it
back during the initial development, however people are begining to run
scripts that have enough processing, or are rate sensitive enough that
we are now needing to start correcting this, or scripts will have to do
their best to guess the time, which will be inferior to us providing it.
As a note if you exceeded the time expected we will be rescheduling the
script immediately, thus it will have a schedule time in the past and
will be slotted in. This can't indefinetly starve other scripts as they
will still be slotted in, but if you request an update in 1 ms, but took
100ms to run we will simply slide you back into the queue 1ms after when
you started running.
2020-11-14 06:07:07 -04:00
|
|
|
uint64_t start_time_ms = AP_HAL::millis64();
|
2018-10-31 19:43:23 -03:00
|
|
|
// strip the selected script out of the list
|
|
|
|
script_info *script = scripts;
|
|
|
|
scripts = script->next;
|
|
|
|
|
|
|
|
// reset the hook to clear the counter
|
2020-02-05 18:58:33 -04:00
|
|
|
reset_loop_overtime(L);
|
2018-10-31 19:43:23 -03:00
|
|
|
|
|
|
|
// store top of stack so we can calculate the number of return values
|
|
|
|
int stack_top = lua_gettop(L);
|
|
|
|
|
|
|
|
// pop the function to the top of the stack
|
2024-07-25 00:25:29 -03:00
|
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, script->run_ref);
|
2024-07-25 18:34:22 -03:00
|
|
|
// set current environment for other users
|
|
|
|
AP::scripting()->set_current_env_ref(script->env_ref);
|
2018-10-31 19:43:23 -03:00
|
|
|
|
|
|
|
if(lua_pcall(L, 0, LUA_MULTRET, 0)) {
|
|
|
|
if (overtime) {
|
|
|
|
// script has consumed an excessive amount of CPU time
|
2021-08-25 21:29:41 -03:00
|
|
|
set_and_print_new_error_message(MAV_SEVERITY_CRITICAL, "%s exceeded time limit", script->name);
|
2018-10-31 19:43:23 -03:00
|
|
|
} else {
|
2023-09-22 21:29:05 -03:00
|
|
|
set_and_print_new_error_message(MAV_SEVERITY_CRITICAL, "%s", lua_tostring(L, -1));
|
2018-10-31 19:43:23 -03:00
|
|
|
}
|
2021-08-25 21:29:41 -03:00
|
|
|
remove_script(L, script);
|
2018-10-31 19:43:23 -03:00
|
|
|
lua_pop(L, 1);
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
int returned = lua_gettop(L) - stack_top;
|
|
|
|
switch (returned) {
|
|
|
|
case 0:
|
|
|
|
// no time to reschedule so bail out
|
|
|
|
remove_script(L, script);
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
{
|
2021-08-25 21:29:41 -03:00
|
|
|
// sanity check the return types
|
|
|
|
if (lua_type(L, -1) != LUA_TNUMBER) {
|
|
|
|
set_and_print_new_error_message(MAV_SEVERITY_CRITICAL, "%s did not return a delay (0x%d)", script->name, lua_type(L, -1));
|
|
|
|
lua_pop(L, 2);
|
|
|
|
remove_script(L, script);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (lua_type(L, -2) != LUA_TFUNCTION) {
|
|
|
|
set_and_print_new_error_message(MAV_SEVERITY_CRITICAL, "%s did not return a function (0x%d)", script->name, lua_type(L, -2));
|
|
|
|
lua_pop(L, 2);
|
|
|
|
remove_script(L, script);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// types match the expectations, go ahead and reschedule
|
|
|
|
script->next_run_ms = start_time_ms + (uint64_t)luaL_checknumber(L, -1);
|
|
|
|
lua_pop(L, 1);
|
2024-07-25 00:25:29 -03:00
|
|
|
int old_ref = script->run_ref;
|
|
|
|
script->run_ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
2021-08-25 21:29:41 -03:00
|
|
|
luaL_unref(L, LUA_REGISTRYINDEX, old_ref);
|
|
|
|
reschedule_script(script);
|
|
|
|
break;
|
2018-10-31 19:43:23 -03:00
|
|
|
}
|
|
|
|
default:
|
|
|
|
{
|
2021-08-25 21:29:41 -03:00
|
|
|
set_and_print_new_error_message(MAV_SEVERITY_CRITICAL, "%s returned bad result count (%d)", script->name, returned);
|
2018-10-31 19:43:23 -03:00
|
|
|
remove_script(L, script);
|
|
|
|
// pop all the results we got that we didn't expect
|
|
|
|
lua_pop(L, returned);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void lua_scripts::remove_script(lua_State *L, script_info *script) {
|
2018-11-14 19:44:15 -04:00
|
|
|
if (script == nullptr) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-10-31 19:43:23 -03:00
|
|
|
// ensure that the script isn't in the loaded list for any reason
|
|
|
|
if (scripts == nullptr) {
|
|
|
|
// nothing to do, already not in the list
|
|
|
|
} else if (scripts == script) {
|
|
|
|
scripts = script->next;
|
|
|
|
} else {
|
|
|
|
for(script_info * current = scripts; current->next != nullptr; current = current->next) {
|
|
|
|
if (current->next == script) {
|
|
|
|
current->next = script->next;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-28 18:45:38 -04:00
|
|
|
{
|
|
|
|
// Remove from running checksum
|
|
|
|
WITH_SEMAPHORE(crc_sem);
|
|
|
|
running_checksum ^= script->crc;
|
|
|
|
}
|
|
|
|
|
2018-11-14 19:44:15 -04:00
|
|
|
if (L != nullptr) {
|
|
|
|
// state could be null if we are force killing all scripts
|
2024-07-25 18:34:22 -03:00
|
|
|
luaL_unref(L, LUA_REGISTRYINDEX, script->env_ref);
|
2024-07-25 00:25:29 -03:00
|
|
|
luaL_unref(L, LUA_REGISTRYINDEX, script->run_ref);
|
2018-11-14 19:44:15 -04:00
|
|
|
}
|
2022-12-21 18:36:11 -04:00
|
|
|
_heap.deallocate(script->name);
|
|
|
|
_heap.deallocate(script);
|
2018-10-31 19:43:23 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
void lua_scripts::reschedule_script(script_info *script) {
|
|
|
|
if (script == nullptr) {
|
2018-11-03 18:59:45 -03:00
|
|
|
#if defined(AP_SCRIPTING_CHECKS) && AP_SCRIPTING_CHECKS >= 1
|
2018-10-31 19:43:23 -03:00
|
|
|
AP_HAL::panic("Lua: Attempted to schedule a null pointer");
|
2018-11-03 18:59:45 -03:00
|
|
|
#endif // defined(AP_SCRIPTING_CHECKS) && AP_SCRIPTING_CHECKS >= 1
|
2018-10-31 19:43:23 -03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
script->next = nullptr;
|
|
|
|
if (scripts == nullptr) {
|
|
|
|
scripts = script;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t next_run_ms = script->next_run_ms;
|
|
|
|
|
|
|
|
if (scripts->next_run_ms > next_run_ms) {
|
|
|
|
script->next = scripts;
|
|
|
|
scripts = script;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
script_info *previous = scripts;
|
|
|
|
while (previous->next != nullptr) {
|
|
|
|
if (previous->next->next_run_ms > next_run_ms) {
|
|
|
|
script->next = previous->next;
|
|
|
|
previous->next = script;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
previous = previous->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
previous->next = script;
|
|
|
|
}
|
|
|
|
|
2022-12-21 18:36:11 -04:00
|
|
|
MultiHeap lua_scripts::_heap;
|
2018-12-08 22:35:08 -04:00
|
|
|
|
|
|
|
void *lua_scripts::alloc(void *ud, void *ptr, size_t osize, size_t nsize) {
|
2022-12-21 18:36:11 -04:00
|
|
|
(void)ud; /* not used */
|
|
|
|
return _heap.change_size(ptr, osize, nsize);
|
2018-12-08 22:35:08 -04:00
|
|
|
}
|
|
|
|
|
2018-10-31 19:43:23 -03:00
|
|
|
void lua_scripts::run(void) {
|
2019-12-05 02:17:06 -04:00
|
|
|
bool succeeded_initial_load = false;
|
|
|
|
|
2022-12-21 18:36:11 -04:00
|
|
|
if (!_heap.available()) {
|
2023-09-22 21:29:05 -03:00
|
|
|
GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "Lua: Unable to allocate a heap");
|
2018-12-08 22:35:08 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-11-14 19:44:15 -04:00
|
|
|
// panic should be hooked first
|
|
|
|
if (setjmp(panic_jmp)) {
|
2019-12-05 02:17:06 -04:00
|
|
|
if (!succeeded_initial_load) {
|
|
|
|
return;
|
|
|
|
}
|
2018-11-14 19:44:15 -04:00
|
|
|
if (lua_state != nullptr) {
|
|
|
|
lua_close(lua_state); // shutdown the old state
|
|
|
|
}
|
|
|
|
// remove all the old scheduled scripts
|
|
|
|
for (script_info *script = scripts; script != nullptr; script = scripts) {
|
|
|
|
remove_script(nullptr, script);
|
|
|
|
}
|
|
|
|
scripts = nullptr;
|
|
|
|
overtime = false;
|
|
|
|
}
|
|
|
|
|
2018-12-08 22:35:08 -04:00
|
|
|
lua_state = lua_newstate(alloc, NULL);
|
2018-11-14 19:44:15 -04:00
|
|
|
lua_State *L = lua_state;
|
|
|
|
if (L == nullptr) {
|
2023-09-21 22:24:30 -03:00
|
|
|
GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "Lua: Couldn't allocate a lua state");
|
2018-11-14 19:44:15 -04:00
|
|
|
return;
|
|
|
|
}
|
2022-10-30 21:18:16 -03:00
|
|
|
|
|
|
|
#ifndef HAL_CONSOLE_DISABLED
|
|
|
|
const int inital_mem = lua_gc(L, LUA_GCCOUNT, 0) * 1024 + lua_gc(L, LUA_GCCOUNTB, 0);
|
|
|
|
#endif
|
|
|
|
|
2018-11-14 19:44:15 -04:00
|
|
|
lua_atpanic(L, atpanic);
|
2020-02-13 16:50:17 -04:00
|
|
|
load_generated_bindings(L);
|
2018-10-31 19:43:23 -03:00
|
|
|
|
AP_Scripting: adjust string metatable setup to fix sandbox integrity
In Lua, strings are the only type that come with a default metatable.
The metatable must be shared by all string objects, and it is set to be
the `string` library table each time that library is opened. In
Ardupilot's scripting engine, the last script to load then has access to
the string metatable as the library is opened fresh for each script, as
its `string` library will have been set to the metatable.
Therefore, if two scripts are loaded, A first and B second, and script B
executes e.g. `string.byte = "haha"`, then `string.byte()` and
`s:byte()` for script B are broken. Because the metatable is shared,
this also breaks `s:byte()` for script A, which violates the integrity
of the sandbox.
Fix the issue by disabling the metatable setup functionality when the
string libary is opened, then manually opening an additional copy of the
library (which won't be given to any script) and setting it as the
string metatable during intialization.
This will break any script that modifies the string metatable for
constructive purposes, but such a script could have been broken if it
weren't the only script running anyway.
2024-07-26 15:39:44 -03:00
|
|
|
// set up string metatable. we set up one for all scripts that no script has
|
|
|
|
// access to, as it's impossible to set up one per-script and we don't want
|
|
|
|
// any script to be able to mess with it.
|
|
|
|
lua_pushliteral(L, ""); /* dummy string */
|
|
|
|
lua_createtable(L, 0, 1); /* table to be metatable for strings */
|
|
|
|
luaopen_string(L); /* get string library */
|
|
|
|
lua_setfield(L, -2, "__index"); /* metatable.__index = string */
|
|
|
|
lua_setmetatable(L, -2); /* set table as metatable for strings */
|
|
|
|
lua_pop(L, 1); /* pop dummy string */
|
|
|
|
|
2022-10-30 21:18:16 -03:00
|
|
|
#ifndef HAL_CONSOLE_DISABLED
|
|
|
|
const int loaded_mem = lua_gc(L, LUA_GCCOUNT, 0) * 1024 + lua_gc(L, LUA_GCCOUNTB, 0);
|
|
|
|
DEV_PRINTF("Lua: State memory usage: %i + %i\n", inital_mem, loaded_mem - inital_mem);
|
|
|
|
#endif
|
|
|
|
|
2018-11-01 01:08:37 -03:00
|
|
|
// Scan the filesystem in an appropriate manner and autostart scripts
|
2020-10-26 22:08:14 -03:00
|
|
|
// Skip those directores disabled with SCR_DIR_DISABLE param
|
|
|
|
uint16_t dir_disable = AP_Scripting::get_singleton()->get_disabled_dir();
|
|
|
|
bool loaded = false;
|
|
|
|
if ((dir_disable & uint16_t(AP_Scripting::SCR_DIR::SCRIPTS)) == 0) {
|
|
|
|
load_all_scripts_in_dir(L, SCRIPTING_DIRECTORY);
|
|
|
|
loaded = true;
|
|
|
|
}
|
2024-02-24 08:37:32 -04:00
|
|
|
#ifdef HAL_HAVE_AP_ROMFS_EMBEDDED_LUA
|
2020-10-26 22:08:14 -03:00
|
|
|
if ((dir_disable & uint16_t(AP_Scripting::SCR_DIR::ROMFS)) == 0) {
|
|
|
|
load_all_scripts_in_dir(L, "@ROMFS/scripts");
|
|
|
|
loaded = true;
|
|
|
|
}
|
2024-02-24 08:37:32 -04:00
|
|
|
#endif
|
2020-10-26 22:08:14 -03:00
|
|
|
if (!loaded) {
|
2023-09-21 22:24:30 -03:00
|
|
|
GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "Lua: All directory's disabled see SCR_DIR_DISABLE");
|
2020-10-26 22:08:14 -03:00
|
|
|
}
|
2018-10-31 19:43:23 -03:00
|
|
|
|
2020-03-28 00:12:06 -03:00
|
|
|
#ifndef __clang_analyzer__
|
2019-12-05 02:17:06 -04:00
|
|
|
succeeded_initial_load = true;
|
2020-03-28 00:12:06 -03:00
|
|
|
#endif // __clang_analyzer__
|
2019-12-05 02:17:06 -04:00
|
|
|
|
2021-08-04 13:49:17 -03:00
|
|
|
while (AP_Scripting::get_singleton()->should_run()) {
|
2018-11-14 19:44:15 -04:00
|
|
|
#if defined(AP_SCRIPTING_CHECKS) && AP_SCRIPTING_CHECKS >= 1
|
|
|
|
if (lua_gettop(L) != 0) {
|
|
|
|
AP_HAL::panic("Lua: Stack should be empty before running scripts");
|
|
|
|
}
|
|
|
|
#endif // defined(AP_SCRIPTING_CHECKS) && AP_SCRIPTING_CHECKS >= 1
|
|
|
|
|
2018-10-31 19:43:23 -03:00
|
|
|
if (scripts != nullptr) {
|
2018-11-03 18:59:45 -03:00
|
|
|
#if defined(AP_SCRIPTING_CHECKS) && AP_SCRIPTING_CHECKS >= 1
|
|
|
|
// Sanity check that the scripts list is ordered correctly
|
|
|
|
script_info *sanity = scripts;
|
|
|
|
while (sanity->next != nullptr) {
|
|
|
|
if (sanity->next_run_ms > sanity->next->next_run_ms) {
|
|
|
|
AP_HAL::panic("Lua: Script tasking order has been violated");
|
|
|
|
}
|
|
|
|
sanity = sanity->next;
|
|
|
|
}
|
|
|
|
#endif // defined(AP_SCRIPTING_CHECKS) && AP_SCRIPTING_CHECKS >= 1
|
|
|
|
|
2018-10-31 19:43:23 -03:00
|
|
|
// compute delay time
|
|
|
|
uint64_t now_ms = AP_HAL::millis64();
|
|
|
|
if (now_ms < scripts->next_run_ms) {
|
|
|
|
hal.scheduler->delay(scripts->next_run_ms - now_ms);
|
|
|
|
}
|
|
|
|
|
2021-11-13 13:09:09 -04:00
|
|
|
if ((_debug_options.get() & uint8_t(DebugLevel::RUNTIME_MSG)) != 0) {
|
2023-09-21 22:24:30 -03:00
|
|
|
GCS_SEND_TEXT(MAV_SEVERITY_DEBUG, "Lua: Running %s", scripts->name);
|
2019-03-22 12:19:59 -03:00
|
|
|
}
|
2024-06-22 23:14:53 -03:00
|
|
|
// take a copy of the script name for the purposes of
|
|
|
|
// logging statistics. "scripts" may become invalid
|
|
|
|
// during the "run_next_script" call, below.
|
|
|
|
char script_name[128+1] {};
|
|
|
|
strncpy_noterm(script_name, scripts->name, 128);
|
2018-10-31 19:43:23 -03:00
|
|
|
|
2022-01-31 19:37:04 -04:00
|
|
|
#if DISABLE_INTERRUPTS_FOR_SCRIPT_RUN
|
|
|
|
void *istate = hal.scheduler->disable_interrupts_save();
|
|
|
|
#endif
|
|
|
|
|
2019-07-26 23:10:26 -03:00
|
|
|
const int startMem = lua_gc(L, LUA_GCCOUNT, 0) * 1024 + lua_gc(L, LUA_GCCOUNTB, 0);
|
2018-10-31 19:43:23 -03:00
|
|
|
const uint32_t loadEnd = AP_HAL::micros();
|
|
|
|
|
2024-06-22 23:14:53 -03:00
|
|
|
// NOTE! the base pointer of our scripts linked list,
|
|
|
|
// *and all its contents* may become invalid as part of
|
|
|
|
// "run_next_script"! So do *NOT* attempt to access
|
|
|
|
// anything that was in *scripts after this call.
|
2018-10-31 19:43:23 -03:00
|
|
|
run_next_script(L);
|
|
|
|
|
|
|
|
const uint32_t runEnd = AP_HAL::micros();
|
2019-07-26 23:10:26 -03:00
|
|
|
const int endMem = lua_gc(L, LUA_GCCOUNT, 0) * 1024 + lua_gc(L, LUA_GCCOUNTB, 0);
|
2022-01-31 19:37:04 -04:00
|
|
|
|
|
|
|
#if DISABLE_INTERRUPTS_FOR_SCRIPT_RUN
|
|
|
|
hal.scheduler->restore_interrupts(istate);
|
|
|
|
#endif
|
|
|
|
|
2022-10-30 21:16:57 -03:00
|
|
|
update_stats(script_name, runEnd - loadEnd, endMem, endMem - startMem);
|
2021-11-13 13:42:49 -04:00
|
|
|
|
2018-10-31 19:43:23 -03:00
|
|
|
|
2019-08-26 21:25:23 -03:00
|
|
|
// garbage collect after each script, this shouldn't matter, but seems to resolve a memory leak
|
|
|
|
lua_gc(L, LUA_GCCOLLECT, 0);
|
|
|
|
|
2018-10-31 19:43:23 -03:00
|
|
|
} else {
|
2021-11-13 13:09:09 -04:00
|
|
|
if ((_debug_options.get() & uint8_t(DebugLevel::NO_SCRIPTS_TO_RUN)) != 0) {
|
2023-09-21 22:24:30 -03:00
|
|
|
GCS_SEND_TEXT(MAV_SEVERITY_DEBUG, "Lua: No scripts to run");
|
2020-02-18 21:10:24 -04:00
|
|
|
}
|
|
|
|
hal.scheduler->delay(1000);
|
2018-10-31 19:43:23 -03:00
|
|
|
}
|
|
|
|
|
2021-08-25 21:29:41 -03:00
|
|
|
// re-print the latest error message every 10 seconds 10 times
|
|
|
|
const uint8_t error_prints = 10;
|
|
|
|
if ((print_error_count < error_prints) && (AP_HAL::millis() - last_print_ms > 10000)) {
|
2022-08-28 16:24:21 -03:00
|
|
|
// note that we do not clear the buffer after we have finished printing, this allows it to be used for a pre-arm check
|
2021-08-25 21:29:41 -03:00
|
|
|
print_error(MAV_SEVERITY_DEBUG);
|
|
|
|
print_error_count++;
|
|
|
|
}
|
2018-10-31 19:43:23 -03:00
|
|
|
}
|
2022-04-19 20:34:23 -03:00
|
|
|
|
|
|
|
// make sure all scripts have been removed
|
|
|
|
while (scripts != nullptr) {
|
|
|
|
remove_script(lua_state, scripts);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lua_state != nullptr) {
|
|
|
|
lua_close(lua_state); // shutdown the old state
|
|
|
|
lua_state = nullptr;
|
|
|
|
}
|
|
|
|
|
2022-08-30 17:47:36 -03:00
|
|
|
error_msg_buf_sem.take_blocking();
|
2022-04-19 20:34:23 -03:00
|
|
|
if (error_msg_buf != nullptr) {
|
2022-12-21 18:36:11 -04:00
|
|
|
_heap.deallocate(error_msg_buf);
|
2022-04-19 20:34:23 -03:00
|
|
|
error_msg_buf = nullptr;
|
|
|
|
}
|
2022-08-30 17:47:36 -03:00
|
|
|
error_msg_buf_sem.give();
|
2018-10-31 19:43:23 -03:00
|
|
|
}
|
2023-06-07 02:56:50 -03:00
|
|
|
|
2023-11-25 13:18:37 -04:00
|
|
|
// Return the file checksums of running and loaded scripts
|
|
|
|
uint32_t lua_scripts::get_loaded_checksum()
|
|
|
|
{
|
|
|
|
WITH_SEMAPHORE(crc_sem);
|
|
|
|
return loaded_checksum;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t lua_scripts::get_running_checksum()
|
|
|
|
{
|
|
|
|
WITH_SEMAPHORE(crc_sem);
|
|
|
|
return running_checksum;
|
|
|
|
}
|
|
|
|
|
2023-06-07 02:56:50 -03:00
|
|
|
#endif // AP_SCRIPTING_ENABLED
|