lua_scripts.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. /*
  2. This program is free software: you can redistribute it and/or modify
  3. it under the terms of the GNU General Public License as published by
  4. the Free Software Foundation, either version 3 of the License, or
  5. (at your option) any later version.
  6. This program is distributed in the hope that it will be useful,
  7. but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  9. GNU General Public License for more details.
  10. You should have received a copy of the GNU General Public License
  11. along with this program. If not, see <http://www.gnu.org/licenses/>.
  12. */
  13. #include "lua_scripts.h"
  14. #include <AP_HAL/AP_HAL.h>
  15. #include <GCS_MAVLink/GCS.h>
  16. #include <AP_ROMFS/AP_ROMFS.h>
  17. #include "lua_generated_bindings.h"
  18. #ifndef SCRIPTING_DIRECTORY
  19. #if HAL_OS_FATFS_IO
  20. #define SCRIPTING_DIRECTORY "/APM/scripts"
  21. #else
  22. #define SCRIPTING_DIRECTORY "./scripts"
  23. #endif //HAL_OS_FATFS_IO
  24. #endif // SCRIPTING_DIRECTORY
  25. extern const AP_HAL::HAL& hal;
  26. bool lua_scripts::overtime;
  27. jmp_buf lua_scripts::panic_jmp;
  28. lua_scripts::lua_scripts(const AP_Int32 &vm_steps, const AP_Int32 &heap_size, const AP_Int8 &debug_level)
  29. : _vm_steps(vm_steps),
  30. _debug_level(debug_level) {
  31. _heap = hal.util->allocate_heap_memory(heap_size);
  32. }
  33. void lua_scripts::hook(lua_State *L, lua_Debug *ar) {
  34. lua_scripts::overtime = true;
  35. // we need to aggressively bail out as we are over time
  36. // so we will aggressively trap errors until we clear out
  37. lua_sethook(L, hook, LUA_MASKCOUNT, 1);
  38. luaL_error(L, "Exceeded CPU time");
  39. }
  40. int lua_scripts::atpanic(lua_State *L) {
  41. gcs().send_text(MAV_SEVERITY_CRITICAL, "Lua: Panic: %s", lua_tostring(L, -1));
  42. hal.console->printf("Lua: Panic: %s\n", lua_tostring(L, -1));
  43. longjmp(panic_jmp, 1);
  44. return 0;
  45. }
  46. lua_scripts::script_info *lua_scripts::load_script(lua_State *L, char *filename) {
  47. if (int error = luaL_loadfile(L, filename)) {
  48. switch (error) {
  49. case LUA_ERRSYNTAX:
  50. gcs().send_text(MAV_SEVERITY_CRITICAL, "Lua: Syntax error in %s", filename);
  51. gcs().send_text(MAV_SEVERITY_CRITICAL, "Lua: Error: %s", lua_tostring(L, -1));
  52. lua_pop(L, lua_gettop(L));
  53. return nullptr;
  54. case LUA_ERRMEM:
  55. gcs().send_text(MAV_SEVERITY_CRITICAL, "Lua: Insufficent memory loading %s", filename);
  56. lua_pop(L, lua_gettop(L));
  57. return nullptr;
  58. case LUA_ERRFILE:
  59. gcs().send_text(MAV_SEVERITY_CRITICAL, "Lua: Unable to load the file: %s", lua_tostring(L, -1));
  60. hal.console->printf("Lua: File error: %s\n", lua_tostring(L, -1));
  61. lua_pop(L, lua_gettop(L));
  62. return nullptr;
  63. default:
  64. gcs().send_text(MAV_SEVERITY_CRITICAL, "Lua: Unknown error (%d) loading %s", error, filename);
  65. lua_pop(L, lua_gettop(L));
  66. return nullptr;
  67. }
  68. }
  69. script_info *new_script = (script_info *)hal.util->heap_realloc(_heap, nullptr, sizeof(script_info));
  70. if (new_script == nullptr) {
  71. // No memory, shouldn't happen, we even attempted to do a GC
  72. gcs().send_text(MAV_SEVERITY_CRITICAL, "Lua: Insufficent memory loading %s", filename);
  73. lua_pop(L, 1); // we can't use the function we just loaded, so ditch it
  74. return nullptr;
  75. }
  76. new_script->name = filename;
  77. new_script->next = nullptr;
  78. // find and create a sandbox for the new chunk
  79. lua_getglobal(L, "get_sandbox_env");
  80. if (lua_pcall(L, 0, LUA_MULTRET, 0)) {
  81. gcs().send_text(MAV_SEVERITY_CRITICAL, "Scripting: Could not create sandbox: %s", lua_tostring(L, -1));
  82. hal.console->printf("Lua: Scripting: Could not create sandbox: %s", lua_tostring(L, -1));
  83. return nullptr;
  84. }
  85. load_generated_sandbox(L);
  86. lua_setupvalue(L, -2, 1);
  87. new_script->lua_ref = luaL_ref(L, LUA_REGISTRYINDEX); // cache the reference
  88. new_script->next_run_ms = AP_HAL::millis64() - 1; // force the script to be stale
  89. return new_script;
  90. }
  91. void lua_scripts::load_all_scripts_in_dir(lua_State *L, const char *dirname) {
  92. if (dirname == nullptr) {
  93. return;
  94. }
  95. DIR *d = AP::FS().opendir(dirname);
  96. if (d == nullptr) {
  97. gcs().send_text(MAV_SEVERITY_INFO, "Lua: Could not find a scripts directory");
  98. return;
  99. }
  100. // load anything that ends in .lua
  101. for (struct dirent *de=AP::FS().readdir(d); de; de=AP::FS().readdir(d)) {
  102. uint8_t length = strlen(de->d_name);
  103. if (length < 5) {
  104. // not long enough
  105. continue;
  106. }
  107. if (strncmp(&de->d_name[length-4], ".lua", 4)) {
  108. // doesn't end in .lua
  109. continue;
  110. }
  111. // FIXME: because chunk name fetching is not working we are allocating and storing an extra string we shouldn't need to
  112. size_t size = strlen(dirname) + strlen(de->d_name) + 2;
  113. char * filename = (char *) hal.util->heap_realloc(_heap, nullptr, size);
  114. if (filename == nullptr) {
  115. continue;
  116. }
  117. snprintf(filename, size, "%s/%s", dirname, de->d_name);
  118. // we have something that looks like a lua file, attempt to load it
  119. script_info * script = load_script(L, filename);
  120. if (script == nullptr) {
  121. hal.util->heap_realloc(_heap, filename, 0);
  122. continue;
  123. }
  124. reschedule_script(script);
  125. }
  126. AP::FS().closedir(d);
  127. }
  128. void lua_scripts::run_next_script(lua_State *L) {
  129. if (scripts == nullptr) {
  130. #if defined(AP_SCRIPTING_CHECKS) && AP_SCRIPTING_CHECKS >= 1
  131. AP_HAL::panic("Lua: Attempted to run a script without any scripts queued");
  132. #endif // defined(AP_SCRIPTING_CHECKS) && AP_SCRIPTING_CHECKS >= 1
  133. return;
  134. }
  135. // reset the current script tracking information
  136. overtime = false;
  137. // strip the selected script out of the list
  138. script_info *script = scripts;
  139. scripts = script->next;
  140. // reset the hook to clear the counter
  141. const int32_t vm_steps = MAX(_vm_steps, 1000);
  142. lua_sethook(L, hook, LUA_MASKCOUNT, vm_steps);
  143. // store top of stack so we can calculate the number of return values
  144. int stack_top = lua_gettop(L);
  145. // pop the function to the top of the stack
  146. lua_rawgeti(L, LUA_REGISTRYINDEX, script->lua_ref);
  147. if(lua_pcall(L, 0, LUA_MULTRET, 0)) {
  148. if (overtime) {
  149. // script has consumed an excessive amount of CPU time
  150. gcs().send_text(MAV_SEVERITY_CRITICAL, "Lua: %s exceeded time limit (%d)", script->name, (int)vm_steps);
  151. remove_script(L, script);
  152. } else {
  153. gcs().send_text(MAV_SEVERITY_INFO, "Lua: %s", lua_tostring(L, -1));
  154. hal.console->printf("Lua: Error: %s\n", lua_tostring(L, -1));
  155. remove_script(L, script);
  156. }
  157. lua_pop(L, 1);
  158. return;
  159. } else {
  160. int returned = lua_gettop(L) - stack_top;
  161. switch (returned) {
  162. case 0:
  163. // no time to reschedule so bail out
  164. remove_script(L, script);
  165. break;
  166. case 2:
  167. {
  168. // sanity check the return types
  169. if (lua_type(L, -1) != LUA_TNUMBER) {
  170. gcs().send_text(MAV_SEVERITY_CRITICAL, "Lua: %s did not return a delay (0x%d)", script->name, lua_type(L, -1));
  171. lua_pop(L, 2);
  172. remove_script(L, script);
  173. return;
  174. }
  175. if (lua_type(L, -2) != LUA_TFUNCTION) {
  176. gcs().send_text(MAV_SEVERITY_CRITICAL, "Lua: %s did not return a function (0x%d)", script->name, lua_type(L, -2));
  177. lua_pop(L, 2);
  178. remove_script(L, script);
  179. return;
  180. }
  181. // types match the expectations, go ahead and reschedule
  182. script->next_run_ms = AP_HAL::millis64() + (uint64_t)luaL_checknumber(L, -1);
  183. lua_pop(L, 1);
  184. int old_ref = script->lua_ref;
  185. script->lua_ref = luaL_ref(L, LUA_REGISTRYINDEX);
  186. luaL_unref(L, LUA_REGISTRYINDEX, old_ref);
  187. reschedule_script(script);
  188. break;
  189. }
  190. default:
  191. {
  192. gcs().send_text(MAV_SEVERITY_CRITICAL, "Lua: %s returned bad result count (%d)", script->name, returned);
  193. remove_script(L, script);
  194. // pop all the results we got that we didn't expect
  195. lua_pop(L, returned);
  196. break;
  197. }
  198. }
  199. }
  200. }
  201. void lua_scripts::remove_script(lua_State *L, script_info *script) {
  202. if (script == nullptr) {
  203. return;
  204. }
  205. // ensure that the script isn't in the loaded list for any reason
  206. if (scripts == nullptr) {
  207. // nothing to do, already not in the list
  208. } else if (scripts == script) {
  209. scripts = script->next;
  210. } else {
  211. for(script_info * current = scripts; current->next != nullptr; current = current->next) {
  212. if (current->next == script) {
  213. current->next = script->next;
  214. break;
  215. }
  216. }
  217. }
  218. if (L != nullptr) {
  219. // state could be null if we are force killing all scripts
  220. luaL_unref(L, LUA_REGISTRYINDEX, script->lua_ref);
  221. }
  222. hal.util->heap_realloc(_heap, script->name, 0);
  223. hal.util->heap_realloc(_heap, script, 0);
  224. }
  225. void lua_scripts::reschedule_script(script_info *script) {
  226. if (script == nullptr) {
  227. #if defined(AP_SCRIPTING_CHECKS) && AP_SCRIPTING_CHECKS >= 1
  228. AP_HAL::panic("Lua: Attempted to schedule a null pointer");
  229. #endif // defined(AP_SCRIPTING_CHECKS) && AP_SCRIPTING_CHECKS >= 1
  230. return;
  231. }
  232. script->next = nullptr;
  233. if (scripts == nullptr) {
  234. scripts = script;
  235. return;
  236. }
  237. uint64_t next_run_ms = script->next_run_ms;
  238. if (scripts->next_run_ms > next_run_ms) {
  239. script->next = scripts;
  240. scripts = script;
  241. return;
  242. }
  243. script_info *previous = scripts;
  244. while (previous->next != nullptr) {
  245. if (previous->next->next_run_ms > next_run_ms) {
  246. script->next = previous->next;
  247. previous->next = script;
  248. return;
  249. }
  250. previous = previous->next;
  251. }
  252. previous->next = script;
  253. }
  254. void *lua_scripts::_heap;
  255. void *lua_scripts::alloc(void *ud, void *ptr, size_t osize, size_t nsize) {
  256. (void)ud; (void)osize; /* not used */
  257. return hal.util->heap_realloc(_heap, ptr, nsize);
  258. }
  259. void lua_scripts::run(void) {
  260. if (_heap == nullptr) {
  261. gcs().send_text(MAV_SEVERITY_INFO, "Lua: Unable to allocate a heap");
  262. return;
  263. }
  264. // panic should be hooked first
  265. if (setjmp(panic_jmp)) {
  266. if (lua_state != nullptr) {
  267. lua_close(lua_state); // shutdown the old state
  268. }
  269. // remove all the old scheduled scripts
  270. for (script_info *script = scripts; script != nullptr; script = scripts) {
  271. remove_script(nullptr, script);
  272. }
  273. scripts = nullptr;
  274. overtime = false;
  275. }
  276. lua_state = lua_newstate(alloc, NULL);
  277. lua_State *L = lua_state;
  278. if (L == nullptr) {
  279. gcs().send_text(MAV_SEVERITY_CRITICAL, "Lua: Couldn't allocate a lua state");
  280. return;
  281. }
  282. lua_atpanic(L, atpanic);
  283. luaL_openlibs(L);
  284. load_lua_bindings(L);
  285. // load the sandbox creation function
  286. uint32_t sandbox_size;
  287. char *sandbox_data = (char *)AP_ROMFS::find_decompress("sandbox.lua", sandbox_size);
  288. if (sandbox_data == nullptr) {
  289. gcs().send_text(MAV_SEVERITY_CRITICAL, "Scripting: Could not find sandbox");
  290. return;
  291. }
  292. if (luaL_dostring(L, sandbox_data)) {
  293. gcs().send_text(MAV_SEVERITY_CRITICAL, "Scripting: Loading sandbox: %s", lua_tostring(L, -1));
  294. return;
  295. }
  296. free(sandbox_data);
  297. // Scan the filesystem in an appropriate manner and autostart scripts
  298. load_all_scripts_in_dir(L, SCRIPTING_DIRECTORY);
  299. while (true) {
  300. #if defined(AP_SCRIPTING_CHECKS) && AP_SCRIPTING_CHECKS >= 1
  301. if (lua_gettop(L) != 0) {
  302. AP_HAL::panic("Lua: Stack should be empty before running scripts");
  303. }
  304. #endif // defined(AP_SCRIPTING_CHECKS) && AP_SCRIPTING_CHECKS >= 1
  305. if (scripts != nullptr) {
  306. #if defined(AP_SCRIPTING_CHECKS) && AP_SCRIPTING_CHECKS >= 1
  307. // Sanity check that the scripts list is ordered correctly
  308. script_info *sanity = scripts;
  309. while (sanity->next != nullptr) {
  310. if (sanity->next_run_ms > sanity->next->next_run_ms) {
  311. AP_HAL::panic("Lua: Script tasking order has been violated");
  312. }
  313. sanity = sanity->next;
  314. }
  315. #endif // defined(AP_SCRIPTING_CHECKS) && AP_SCRIPTING_CHECKS >= 1
  316. // compute delay time
  317. uint64_t now_ms = AP_HAL::millis64();
  318. if (now_ms < scripts->next_run_ms) {
  319. hal.scheduler->delay(scripts->next_run_ms - now_ms);
  320. }
  321. if (_debug_level > 1) {
  322. gcs().send_text(MAV_SEVERITY_DEBUG, "Lua: Running %s", scripts->name);
  323. }
  324. const int startMem = lua_gc(L, LUA_GCCOUNT, 0) * 1024 + lua_gc(L, LUA_GCCOUNTB, 0);
  325. const uint32_t loadEnd = AP_HAL::micros();
  326. run_next_script(L);
  327. const uint32_t runEnd = AP_HAL::micros();
  328. const int endMem = lua_gc(L, LUA_GCCOUNT, 0) * 1024 + lua_gc(L, LUA_GCCOUNTB, 0);
  329. if (_debug_level > 1) {
  330. gcs().send_text(MAV_SEVERITY_DEBUG, "Lua: Time: %u Mem: %d + %d",
  331. (unsigned int)(runEnd - loadEnd),
  332. (int)endMem,
  333. (int)(endMem - startMem));
  334. }
  335. // garbage collect after each script, this shouldn't matter, but seems to resolve a memory leak
  336. lua_gc(L, LUA_GCCOLLECT, 0);
  337. } else {
  338. gcs().send_text(MAV_SEVERITY_DEBUG, "Lua: No scripts to run");
  339. hal.scheduler->delay(10000);
  340. }
  341. }
  342. }