/* * Copyright 2016, CZ.NIC z.s.p.o. (http://www.nic.cz/) * * This file is part of the turris updater. * * Updater 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. * * Updater 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 Updater. If not, see . */ #include "interpreter.h" #include "util.h" #include "events.h" #include "journal.h" #include "md5.h" #include "sha256.h" #include "locks.h" #include "arguments.h" #include "picosat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include // The name used in lua registry to store stuff #define REGISTRY_NAME "libupdater" static const char *crash_file = "/tmp/updater_crash.log"; // From the embed file, lua things that are auto-loaded extern struct file_index_element lautoload[]; struct interpreter { lua_State *state; struct events *events; }; static int err_handler(lua_State *L) { /* * Call stacktraceplus.stacktrace(msg). But in a safe way, * if it doesn't work, just return msg. This may happen * before the stacktraceplus library is loaded. */ int top = lua_gettop(L); /* * Make sure we have enough space for: * • stacktraceplus * • stacktrace function * • its parameter * • another copy of the error message. * * The manual isn't clear if the old stack is reused for * the error handler, or a new one is provided. So just * expect the worst. */ if (!lua_checkstack(L, 4)) return 1; // Reuse the provided param as a result lua_getfield(L, LUA_GLOBALSINDEX, "c_pcall_error_handler"); if (!lua_isfunction(L, -1)) goto FAIL; lua_pushvalue(L, top); int result = lua_pcall(L, 1, 1, 0); if (result) goto FAIL; // The result is on the top. Just return it. return 1; FAIL: // In case we fail to provide the error message // Copy the original message lua_pushvalue(L, top); return 1; } static int push_err_handler(lua_State *L) { luaL_checkstack(L, 1, "Not enough space to push error handler"); lua_pushcfunction(L, err_handler); return lua_gettop(L); } static const char *interpreter_error_result(lua_State *L) { // There's an error on top of the stack if (lua_istable(L, -1)) { lua_getfield(L, -1, "trace"); const char *trace = lua_tostring(L, -1); if (trace) { DBG("%s", trace); if (!dump2file(crash_file, trace)) WARN("Crash report of stack trace dump failed."); } // Else just print message, we are probably missing trace lua_pop(L, 1); lua_getfield(L, -1, "msg"); } const char *errmsg = lua_tostring(L, -1); return errmsg; } static int lua_log(lua_State *L) { int nargs = lua_gettop(L); ASSERT_MSG(nargs >= 1, "Not enough arguments passed to log()"); enum log_level level = log_level_get(lua_tostring(L, 1)); int depth = luaL_checkinteger(L, 2); // Depth to ignore and report upper location if (depth < 0) return luaL_error(L, "Second argument mustn't be less then zero"); struct lua_Debug ldebug; lua_getstack(L, depth + 1, &ldebug); // get informations about caller lua_getinfo(L, "Sln", &ldebug); size_t sum = 1; size_t sizes[nargs - 2]; const char *strs[nargs - 2]; for (int i = 3; i <= nargs; i ++) { if (lua_isnil(L, i)) strs[i - 3] = ""; else if((strs[i - 3] = lua_tostring(L, i)) == NULL) // If it is not nil nor string or number, it is function or table so too complex just for simple log function strs[i - 3] = ""; sizes[i - 3] = strlen(strs[i - 3]); sum += sizes[i - 3]; } char *message = alloca(sum); size_t pos = 0; for (size_t i = 0; i < (unsigned)nargs - 2; i ++) { memcpy(message + pos, strs[i], sizes[i]); pos += sizes[i]; } message[pos] = '\0'; char *file = aprintf("%s.lua", ldebug.source); log_internal(level, file, ldebug.currentline, ldebug.name ? ldebug.name : "Globals", "%s", message); return 0; } static int lua_state_dump(lua_State *L) { const char *state = luaL_checkstring(L, 1); state_dump(state); return 0; } /* * Put a value from the stack (at index) into our own table in the registry. * Return the index under which it is stored in there. The returned value allocated * on the heap, you must free it yourself. */ static char *register_value(lua_State *L, int index) { // Make sure we don't skew the index by placing other stuff onto the stack lua_pushvalue(L, index); lua_getfield(L, LUA_REGISTRYINDEX, REGISTRY_NAME); // We expect this won't wrap around for the lifetime of the program static uint64_t id = 0; const size_t max_len = 26; // 21 characters is the max length of uint64_t in decimal, val- is 4, 1 for '\0' char *result = malloc(max_len); snprintf(result, max_len, "val-%" PRIu64, id ++); lua_pushvalue(L, -2); lua_setfield(L, -2, result); // Pop the table and the original copy of the value lua_pop(L, 2); return result; } /* * Extract named value from registry table onto the top of stack. * Remove it from the registry table. Free the name. */ static void extract_registry_value(lua_State *L, char *name) { // Get the registry table lua_getfield(L, LUA_REGISTRYINDEX, REGISTRY_NAME); // Get the value lua_getfield(L, -1, name); // Delete the value the from registry table lua_pushnil(L); // The table is now at -3, because we added the result and the nil on top of stack lua_setfield(L, -3, name); // Remove the registry table lua_remove(L, -2); free(name); } struct lua_command_data { struct lua_State *L; char *terminated_callback; char *postfork_callback; }; // Extract pointer of userdata from the lua registry static void *extract_registry(lua_State *L, const char *name) { lua_getfield(L, LUA_REGISTRYINDEX, REGISTRY_NAME); lua_getfield(L, -1, name); ASSERT(lua_islightuserdata(L, -1)); void *result = lua_touserdata(L, -1); lua_pop(L, 2); return result; } // Name of the wait_id meta table #define WAIT_ID_META "WAIT_ID_META" static void command_terminated(struct wait_id id __attribute__((unused)), void *data, int status, enum command_kill_status killed, size_t out_size, const char *out, size_t err_size, const char *err) { struct lua_command_data *lcd = data; struct lua_State *L = lcd->L; ASSERT(L); // This may be called from C code with a dirty stack luaL_checkstack(L, 6, "Not enough stack space to call command callback"); int handler = push_err_handler(L); if (lcd->postfork_callback) { /* * This already happened in the child. But we need to free * resources ‒ remove it from the registry table. */ extract_registry_value(L, lcd->postfork_callback); lua_pop(L, 1); } // Get the lua function. ASSERT(lcd->terminated_callback); extract_registry_value(L, lcd->terminated_callback); /* * We terminated the command, we won't need it any more. * Make sure we don't leak even if the lua throws or whatever. */ free(lcd); // Push the rest of parameters here lua_pushinteger(L, WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status)); const char *ks = NULL; switch (killed) { #define KS(NAME) case CK_##NAME: ks = #NAME; break KS(TERMINATED); KS(TERMED); KS(KILLED); KS(SIGNAL_OTHER); #undef KS } ASSERT(ks); lua_pushstring(L, ks); lua_pushlstring(L, out, out_size); lua_pushlstring(L, err, err_size); int result = lua_pcall(L, 4, 0, handler); ASSERT_MSG(!result, "%s", interpreter_error_result(L)); } static void command_postfork(void *data) { struct lua_command_data *lcd = data; struct lua_State *L = lcd->L; ASSERT(L); // This would be called from within the lua_run_command, no need to allocate more stack if (lcd->postfork_callback) { int handler = push_err_handler(L); extract_registry_value(L, lcd->postfork_callback); int result = lua_pcall(L, 0, 0, handler); ASSERT_MSG(!result, "%s", interpreter_error_result(L)); } // We don't worry about freeing memory here. We're going to exec just in a while. } static void do_flush(lua_State *L, const char *handle) { lua_getfield(L, LUA_GLOBALSINDEX, "io"); lua_getfield(L, -1, handle); lua_getfield(L, -1, "flush"); lua_pushvalue(L, -2); lua_call(L, 1, 0); lua_pop(L, 2); } // Push the provided wait ID onto the top of the lua stack static void push_wid(lua_State *L, const struct wait_id *id) { struct wait_id *lid = lua_newuserdata(L, sizeof *lid); *lid = *id; // Set meta table. Empty one, but make sure we can recognize our data. luaL_newmetatable(L, WAIT_ID_META); lua_setmetatable(L, -2); } static int lua_run_generic(lua_State *L, bool utils) { // Flush the lua output (it seems to buffered separately) do_flush(L, "stdout"); do_flush(L, "stderr"); // Extract the parameters. There's a lot of them. luaL_checktype(L, 1, LUA_TFUNCTION); int pf_cback_type = lua_type(L, 2); if (pf_cback_type != LUA_TNIL && pf_cback_type != LUA_TFUNCTION) return luaL_error(L, "The 2nd argument of run_command must be either function or nil"); if (!lua_isnil(L, 3) && !lua_isstring(L, 3)) return luaL_error(L, "The 3rd argument of run_command is a string input or nil"); int term_timeout = luaL_checkinteger(L, 4); int kill_timeout = luaL_checkinteger(L, 5); const char *command = luaL_checkstring(L, 6); if (utils) { DBG("Util command %s", command); } else DBG("Command %s", command); // The rest of the args are args for the command ‒ get them into an array const size_t arg_count = lua_gettop(L) - 6; const char *args[arg_count + 1]; for (int i = 6; i < lua_gettop(L); i ++) DBG("Arg %s", args[i - 6] = luaL_checkstring(L, i + 1)); args[arg_count] = NULL; // Data for the callbacks. It will get freed there. struct lua_command_data *data = malloc(sizeof *data); data->L = L; data->terminated_callback = register_value(L, 1); data->postfork_callback = lua_isnil(L, 2) ? NULL : register_value(L, 2); struct events *events = extract_registry(L, "events"); ASSERT(events); size_t input_size = 0; const char *input = NULL; if (lua_isstring(L, 3)) input = lua_tolstring(L, 3, &input_size); struct wait_id id; if (utils) id = run_util_a(events, command_terminated, command_postfork, data, input_size, input, term_timeout, kill_timeout, command, args); else id = run_command_a(events, command_terminated, command_postfork, data, input_size, input, term_timeout, kill_timeout, command, args); push_wid(L, &id); // Return 1 value ‒ the wait_id return 1; } static int lua_run_command(lua_State *L) { return lua_run_generic(L, false); } static int lua_run_util(lua_State *L) { return lua_run_generic(L, true); } struct lua_download_data { lua_State *L; char *callback; }; static void download_callback(struct wait_id id __attribute__((unused)), void *data, int status, size_t out_size, const char *out) { struct lua_download_data *d = data; struct lua_State *L = d->L; ASSERT(L); // This may be called from C code with a dirty stack luaL_checkstack(L, 4, "Not enough stack space to call download callback"); int handler = push_err_handler(L); // Get the lua function. ASSERT(d->callback); extract_registry_value(L, d->callback); /* * We terminated the command, we won't need it any more. * Make sure we don't leak even if the lua throws or whatever. */ free(d); lua_pushinteger(L, status); lua_pushlstring(L, out, out_size); int result = lua_pcall(L, 2, 0, handler); ASSERT_MSG(!result, "%s", interpreter_error_result(L)); } static int lua_download(lua_State *L) { // Flush the lua output (it seems to buffered separately) do_flush(L, "stdout"); do_flush(L, "stderr"); // Extract params luaL_checktype(L, 1, LUA_TFUNCTION); int pcount = lua_gettop(L); const char *url = luaL_checkstring(L, 2); const char *cacert = NULL; if (pcount >= 3 && !lua_isnil(L, 3)) cacert = luaL_checkstring(L, 3); const char *crl = NULL; if (pcount >= 4 && !lua_isnil(L, 4)) crl = luaL_checkstring(L, 4); bool ssl = lua_toboolean(L, 5); // Handle the callback struct lua_download_data *data = malloc(sizeof *data); data->L = L; data->callback = register_value(L, 1); // Run the download struct events *events = extract_registry(L, "events"); ASSERT(events); struct wait_id id = download(events, download_callback, data, url, cacert, crl, ssl); // Return the ID push_wid(L, &id); return 1; } static int lua_events_wait(lua_State *L) { // All the parameters here are the wait_id userdata. We need to put them into an array. size_t event_count = lua_gettop(L); struct wait_id ids[event_count]; for (size_t i = 1; i <= event_count; i ++) // Check each one is the wait_id we provided memcpy(&ids[i - 1], luaL_checkudata(L, i, WAIT_ID_META), sizeof ids[i - 1]); struct events *events = extract_registry(L, "events"); events_wait(events, event_count, ids); // Nothing returned return 0; } static int lua_mkdtemp(lua_State *L) { int param_count = lua_gettop(L); if (param_count > 1) return luaL_error(L, "Too many parameters to mkdtemp: %d", param_count); const char *base_dir = getenv("TMPDIR"); if (!base_dir) base_dir = "/tmp"; if (param_count && !lua_isnil(L, 1)) base_dir = luaL_checkstring(L, 1); char *template = aprintf("%s/updater-XXXXXX", base_dir); char *result = mkdtemp(template); if (result) { lua_pushstring(L, result); return 1; } else { lua_pushnil(L); lua_pushstring(L, strerror(errno)); return 2; } } static int lua_chdir(lua_State *L) { int param_count = lua_gettop(L); if (param_count != 1) return luaL_error(L, "chdir expects 1 parameter"); const char *path = luaL_checkstring(L, 1); int result = chdir(path); if (result == -1) return luaL_error(L, "chdir to %s: %s", path, strerror(errno)); return 0; } static int lua_getcwd(lua_State *L) { const char *result = NULL; // An arbitrary length. size_t s = 16; while (!result) { s *= 2; char *buf = alloca(s); result = getcwd(buf, s); if (!result && errno != ERANGE) return luaL_error(L, "getcwd: %s", strerror(errno)); } lua_pushstring(L, result); return 1; } static int lua_mkdir(lua_State *L) { const char *dir = luaL_checkstring(L, 1); // TODO: Make the mask configurable int result = mkdir(dir, 0777); if (result == -1) return luaL_error(L, "mkdir '%s' failed: %s", dir, strerror(errno)); // No results if it was successfull return 0; } struct mv_result_data { char *err; int status; }; static void mv_result(struct wait_id id __attribute__((unused)), void *data, int status, enum command_kill_status killed __attribute__((unused)), size_t out_size __attribute__((unused)), const char *output __attribute__((unused)), size_t err_size __attribute__((unused)), const char *err) { struct mv_result_data *mv_result_data = data; mv_result_data->status = WTERMSIG(status); if (status) mv_result_data->err = strdup(err); } static int lua_move(lua_State *L) { const char *old = luaL_checkstring(L, 1); const char *new = luaL_checkstring(L, 2); /* * TODO: * We need to support cross-device move. But that one is a hell * to implement (because it might be a symlink, block or character * device, we need to support file permissions, etc. We use * external mv for now instead, we may want to reconsider later. * * Also, musl seems to have a bug of not overwriting one symlink by * another, which can cause strange errors, including not booting * up the kernel. */ struct events *events = extract_registry(L, "events"); ASSERT(events); struct mv_result_data mv_result_data = { .err = NULL }; struct wait_id id = run_util(events, mv_result, NULL, &mv_result_data, 0, NULL, -1, -1, "mv", "-f", old, new, (const char *)NULL); events_wait(events, 1, &id); if (mv_result_data.status) { lua_pushfstring(L, "Failed to move '%s' to '%s': %s (ecode %d)", old, new, mv_result_data.err, mv_result_data.status); free(mv_result_data.err); return lua_error(L); } return 0; } static const char *stat2str(const struct stat *buf) { switch (buf->st_mode & S_IFMT) { case S_IFSOCK: return "s"; case S_IFLNK: return "l"; case S_IFREG: return "r"; case S_IFBLK: return "b"; case S_IFDIR: return "d"; case S_IFCHR: return "c"; case S_IFIFO: return "f"; } return "?"; } // Get the type of file refered by the dirent. static const char *get_dirent_type(DIR *d, struct dirent *ent) { switch (ent->d_type) { case DT_BLK: return "b"; case DT_CHR: return "c"; case DT_DIR: return "d"; case DT_FIFO: return "f"; case DT_LNK: return "l"; case DT_REG: return "r"; case DT_SOCK: return "s"; default: // DT_UNKNOWN // The file system might not have this info in dir, try again with stat break; } // OK, we didn't find out here, try again with stat struct stat buf; int result = fstatat(dirfd(d), ent->d_name, &buf, AT_SYMLINK_NOFOLLOW); if (result == -1) { ERROR("fstatat failed on %s: %s", ent->d_name, strerror(errno)); return "?"; } return stat2str(&buf); } static int lua_ls(lua_State *L) { const char *dir = luaL_checkstring(L, 1); DIR *d = opendir(dir); if (!d) return luaL_error(L, "Could not read directory %s: %s", dir, strerror(errno)); struct dirent *ent; lua_newtable(L); errno = 0; while ((ent = readdir(d))) { // Skip the . and .. directories if (strcmp(ent->d_name, "..") && strcmp(ent->d_name, ".")) { lua_pushstring(L, get_dirent_type(d, ent)); lua_setfield(L, -2, ent->d_name); } errno = 0; } int old_errno = errno; int result = closedir(d); if (old_errno) return luaL_error(L, "Could not read directory entity of %s: %s", dir, strerror(old_errno)); if (result == -1) return luaL_error(L, "Failed to close directory %s: %s", dir, strerror(errno)); return 1; } struct perm_def { mode_t mask; size_t pos; char letter; }; /* * Note that some of these are on the same position. They are * ordered so that the last matching wins, producing the desired * result. */ static const struct perm_def perm_defs[] = { { S_IRUSR, 0, 'r' }, { S_IWUSR, 1, 'w' }, { S_IXUSR, 2, 'x' }, { S_IRGRP, 3, 'r' }, { S_IWGRP, 4, 'w' }, { S_IXGRP, 5, 'x' }, { S_IROTH, 6, 'r' }, { S_IWOTH, 7, 'w' }, { S_IXOTH, 8, 'x' }, { S_ISVTX, 8, 't' }, { S_ISVTX | S_IXOTH, 8, 'T' }, { S_ISGID, 5, 'S' }, { S_ISGID | S_IXGRP, 5, 's' }, { S_ISUID, 2, 'S' }, { S_ISUID | S_IXUSR, 2, 's' } }; static const char *perm2str(struct stat *buf) { static char perm[9]; memset(perm, '-', sizeof perm); for (size_t i = 0; i < sizeof perm_defs / sizeof *perm_defs; i ++) { if ((buf->st_mode & perm_defs[i].mask) == perm_defs[i].mask) // All the bits are set according to the mask perm[perm_defs[i].pos] = perm_defs[i].letter; } return perm; } static int stat_lstat(lua_State *L, bool use_lstat) { const char *fname = luaL_checkstring(L, 1); struct stat buf; int result; if (use_lstat) result = lstat(fname, &buf); else result = stat(fname, &buf); if (result == -1) { if (errno == ENOENT) // No result, because the file does not exist return 0; else return luaL_error(L, "Failed to stat '%s': %s", fname, strerror(errno)); } lua_pushstring(L, stat2str(&buf)); lua_pushstring(L, perm2str(&buf)); return 2; } static int lua_stat(lua_State *L) { return stat_lstat(L, false); } static int lua_lstat(lua_State *L) { return stat_lstat(L, true); } static int lua_sync(lua_State *L __attribute__((unused))) { DBG("Sync"); sync(); return 0; } static int lua_setenv(lua_State *L) { const char *name = luaL_checkstring(L, 1); const char *value = luaL_checkstring(L, 2); int result = setenv(name, value, 1); if (result) { return luaL_error(L, "Failed to set env %s = %s", name, value, strerror(errno)); } return 0; } static void push_hex(lua_State *L, const uint8_t *buffer, size_t size) { char result[2 * size]; for (size_t i = 0; i < size; i ++) sprintf(result + 2 * i, "%02hhx", buffer[i]); lua_pushlstring(L, result, 2 * size); } static int lua_md5(lua_State *L) { size_t len; const char *buffer = luaL_checklstring(L, 1, &len); uint8_t result[MD5_DIGEST_SIZE]; md5_buffer(buffer, len, result); push_hex(L, result, sizeof result); return 1; } static int lua_sha256(lua_State *L) { size_t len; const char *buffer = luaL_checklstring(L, 1, &len); uint8_t result[SHA256_DIGEST_SIZE]; sha256_buffer(buffer, len, result); push_hex(L, result, sizeof result); return 1; } static int lua_reexec(lua_State *L) { size_t args_c = lua_gettop(L); const char *args[args_c]; size_t i; for (i = 0; i < args_c; i++) { args[i] = luaL_checkstring(L, i + 1); } reexec(args_c, (char**) args); return 0; } // Stores pointer to internal files used as uri. static const struct file_index_element *uriinternal; static int lua_uri_internal_get(lua_State *L) { int param_count = lua_gettop(L); if (param_count > 1) return luaL_error(L, "Too many parameters to uri_internal_get: %d", param_count); const char *name = luaL_checkstring(L, 1); if (!uriinternal) return luaL_error(L, "Internal uri is not supported.", name); const struct file_index_element *file = index_element_find(uriinternal, name); if (!file) return luaL_error(L, "No internal with name: %s", name); lua_pushlstring(L, (const char *)file->data, file->size); return 1; } static int lua_system_reboot(lua_State *L) { bool stick = lua_toboolean(L, 1); system_reboot(stick); return 0; } static int lua_get_updater_version(lua_State *L) { lua_pushstring(L, UPDATER_VERSION); return 1; } extern bool state_log_enabled; // defined in util.c static int lua_state_log_enabled(lua_State *L) { lua_pushboolean(L, state_log_enabled); return 1; } struct injected_func { int (*func)(lua_State *); const char *name; }; static const struct injected_func injected_funcs[] = { { lua_log, "log" }, { lua_state_log_enabled, "state_log_enabled" }, { lua_state_dump, "state_dump" }, { lua_run_command, "run_command" }, { lua_run_util, "run_util" }, { lua_download, "download" }, { lua_events_wait, "events_wait" }, /* * Note: watch_cancel is not provided, because it would be hell to * manage the dynamically allocated memory correctly and there doesn't * seem to be a need for them at this moment. */ { lua_mkdtemp, "mkdtemp" }, { lua_chdir, "chdir" }, { lua_getcwd, "getcwd" }, { lua_mkdir, "mkdir" }, { lua_move, "move" }, { lua_ls, "ls" }, { lua_stat, "stat" }, { lua_lstat, "lstat" }, { lua_sync, "sync" }, { lua_setenv, "setenv" }, { lua_md5, "md5" }, { lua_sha256, "sha256" }, { lua_reexec, "reexec" }, { lua_uri_internal_get, "uri_internal_get" }, { lua_system_reboot, "system_reboot" }, { lua_get_updater_version, "get_updater_version" } }; #ifdef COVERAGE // From the embed file. Coverage lua code. extern struct file_index_element lcoverage[]; static int interpreter_coverage_dump(lua_State *L) { char *out_dir = getenv("COVERAGEDIR"); if (!out_dir) { WARN("COVERAGEDIR variable not specified. Skipping coverage dump"); return 0; } DBG("Executing coverage data dump."); int handler = push_err_handler(L); lua_getfield(L, LUA_GLOBALSINDEX, "coverage"); lua_getfield(L, -1, "dump"); // called function lua_pushstring(L, out_dir); // argument if (lua_pcall(L, 1, 0, handler)) ERROR("Coverage data dump failed: %s", interpreter_error_result(L)); lua_pop(L, 1); // pop coverage module from stack lua_remove(L, handler); return 0; } static void interpreter_load_coverage(struct interpreter *interpreter) { lua_State *L = interpreter->state; DBG("Initializing Lua code coverage"); if (!interpreter_include(interpreter, (const char *) lcoverage->data, lcoverage->size, "coverage")) { lua_getfield(L, LUA_GLOBALSINDEX, "coverage"); // get this module lua_newuserdata(L, 1); // push to stack dummy user data. They are freed by Lua it self not our code. lua_newtable(L); // new meta table for user data lua_pushcfunction(L, interpreter_coverage_dump); lua_setfield(L, -2, "__gc"); // set function to to new table lua_setmetatable(L, -2); // set new table as user data meta table lua_setfield(L, -2, "gc_udata"); // Set dummy user data to coverage.gc_udata lua_pop(L, 1); // Pop coverage module from stack } else WARN("Loading of Lua coverage code failed."); } #endif struct interpreter *interpreter_create(struct events *events, const struct file_index_element *uriinter) { uriinternal = uriinter; struct interpreter *result = malloc(sizeof *result); lua_State *L = luaL_newstate(); *result = (struct interpreter) { .state = L, .events = events }; luaL_openlibs(L); // Create registry for our needs and fill it with some data lua_newtable(L); lua_pushlightuserdata(L, result); lua_setfield(L, -2, "interpreter"); lua_pushlightuserdata(L, events); lua_setfield(L, -2, "events"); lua_setfield(L, LUA_REGISTRYINDEX, REGISTRY_NAME); // Insert bunch of functions for (size_t i = 0; i < sizeof injected_funcs / sizeof *injected_funcs; i ++) { DBG("Injecting function no %zu %s/%p", i, injected_funcs[i].name, injected_funcs[i].name); lua_pushcfunction(L, injected_funcs[i].func); lua_setglobal(L, injected_funcs[i].name); } // Some binary embedded modules journal_mod_init(L); locks_mod_init(L); picosat_mod_init(L); #ifdef COVERAGE interpreter_load_coverage(result); #endif return result; } struct reader_data { const char *chunk; size_t length; bool used; }; const char *reader(lua_State *L __attribute__((unused)), void *data_raw, size_t *size) { struct reader_data *data = data_raw; if (data->used) { *size = 0; return NULL; } else { *size = data->length; data->used = true; return data->chunk; } } const char *interpreter_include(struct interpreter *interpreter, const char *code, size_t length, const char *src) { lua_State *L = interpreter->state; ASSERT(L); // We don't know how dirty stack we get here luaL_checkstack(L, 4, "Can't create space for interpreter_include"); if (!length) // It is a null-terminated string, compute its length length = strlen(code); push_err_handler(L); int result = lua_load(L, reader, &(struct reader_data) { .chunk = code, .length = length }, src); if (result) // There's been an error. Extract it (top of the stack). return interpreter_error_result(L); /* * The stack: * • … (unknown stuff from before) * • The error handler (-2) * • The chunk to call (-1) */ result = lua_pcall(L, 0, 1, -2); // Remove the error handler lua_remove(L, -2); if (result) return interpreter_error_result(L); bool has_result = true; if (lua_isnil(L, -1)) { /* * In case the module returned nil, use true instead, to properly * imitate require in what is put into package.loaded. */ lua_pop(L, 1); lua_pushboolean(L, 1); has_result = false; } // Store it into package.loaded lua_getfield(L, LUA_GLOBALSINDEX, "package"); lua_getfield(L, -1, "loaded"); /* * The stack: * • ̣… (unknown stuff from before) * • The result of load (-3) * • package (-2) * • package.loaded (-1) */ /* * Check if the table is already there and don't override it if so. * This is the case of module() in the loaded stuff. */ lua_getfield(L, -1, src); bool is_table = lua_istable(L, -1); lua_pop(L, 1); if (!is_table) { // Get a copy of the result on top lua_pushvalue(L, -3); // Move the top into the table lua_setfield(L, -2, src); } // Drop the two tables from top of the stack, leave the result there lua_pop(L, 2); if (has_result) // Store the result (pops it from the stack) lua_setfield(L, LUA_GLOBALSINDEX, src); else lua_pop(L, 1); return NULL; } const char *interpreter_autoload(struct interpreter *interpreter) { for (struct file_index_element *el = lautoload; el->name; el ++) { const char *underscore = rindex(el->name, '_'); // Use the part after the last underscore as the name const char *name = underscore ? underscore + 1 : el->name; DBG("Including module %s", name); const char *err = interpreter_include(interpreter, (const char *) el->data, el->size, name); if (err) return err; } return NULL; } static void lookup(lua_State *L, char **name, char **end) { /* * Look-up a value in the table on the top of the stack * and move in the string being parsed. Also remove the * old table. */ // Terminate the string (replace the separator) **end = '\0'; // Do the lookup lua_getfield(L, -1, *name); // Move in the string *name = *end + 1; // And get rid of the old one lua_remove(L, -2); } const char *interpreter_call(struct interpreter *interpreter, const char *function, size_t *result_count, const char *param_spec, ...) { // Get a read-write version of the function string. size_t flen = strlen(function); char *f = alloca(flen + 1); strcpy(f, function); lua_State *L = interpreter->state; // Clear the stack lua_pop(L, lua_gettop(L)); int handler = push_err_handler(L); /* * Make sure the index 1 always contains the * table we want to look up in. We start at the global * scope. */ lua_pushvalue(L, LUA_GLOBALSINDEX); char *pos; while ((pos = strchr(f, '.'))) { // Look up the new table in the current one, moving the position lookup(L, &f, &pos); } size_t nparams = 0; if ((pos = strchr(f, ':'))) { // It is a method. Look up the table first lookup(L, &f, &pos); // Look up the function in the table lua_getfield(L, -1, f); // set „self“ to the table we looked up in lua_pushvalue(L, -2); nparams = 1; } else lua_getfield(L, -1, f); // Drop the table we looked up the function inside lua_remove(L, -2 - nparams); // Reserve space for the parameters. One letter in param_spec is one param. size_t spec_len = strlen(param_spec); luaL_checkstack(L, spec_len, "Couldn't grow the LUA stack for parameters"); nparams += spec_len; va_list args; va_start(args, param_spec); for (; *param_spec; param_spec ++) { switch (*param_spec) { #define CASE(TYPE, SIGN, FUN) \ case SIGN: { \ TYPE x = va_arg(args, TYPE); \ lua_push##FUN(L, x); \ break; \ } // Represent bool as int, because of C type promotions // cppcheck-suppress va_end_missing (false positive: look just below the for cycle) CASE(int, 'b', boolean); case 'n': // No param here lua_pushnil(L); break; CASE(int, 'i', integer); CASE(const char *, 's', string); case 'S': { // binary string, it has 2 parameters const char *s = va_arg(args, const char *); size_t len = va_arg(args, size_t); lua_pushlstring(L, s, len); break; } CASE(double, 'f', number); case 'r': { const char *s = va_arg(args, const char *); // Get the registry table lua_getfield(L, LUA_REGISTRYINDEX, REGISTRY_NAME); // Get the value lua_getfield(L, -1, s); // Remove the registry table. This way we added only the extracted value. lua_remove(L, -2); break; } default: DIE("Unknown type specifier '%c' passed", *param_spec); #undef CASE } } va_end(args); int result = lua_pcall(L, nparams, LUA_MULTRET, handler); lua_remove(L, handler); if (result) { return interpreter_error_result(L); } if (result_count) *result_count = lua_gettop(L); return NULL; } int interpreter_collect_results(struct interpreter *interpreter, const char *spec, ...) { lua_State *L = interpreter->state; size_t top = lua_gettop(L); size_t pos = 0; va_list args; va_start(args, spec); for (; *spec; spec ++) { if (pos >= top) { va_end(args); return pos; } switch (*spec) { case 'b': { bool *b = va_arg(args, bool *); *b = lua_toboolean(L, pos + 1); break; } case 'i': if (lua_isnumber(L, pos + 1)) { int *i = va_arg(args, int *); *i = lua_tointeger(L, pos + 1); } else return pos; break; case 'n': if (!lua_isnil(L, pos + 1)) return pos; // Fall through to skipping the the '-' case case '-': // Just skipping the position break; case 's': if (lua_isstring(L, pos + 1)) { const char **s = va_arg(args, const char **); *s = lua_tostring(L, pos + 1); } else return pos; break; case 'S': if (lua_isstring(L, pos + 1)) { const char **s = va_arg(args, const char **); size_t *l = va_arg(args, size_t *); *s = lua_tolstring(L, pos + 1, l); } else return pos; break; case 'f': if (lua_isnumber(L, pos + 1)) { double *d = va_arg(args, double *); *d = lua_tonumber(L, pos + 1); } else return pos; break; case 'r': { char **name = va_arg(args, char **); *name = register_value(L, pos + 1); break; } default: DIE("Invalid type specifier '%c' passed", *spec); } pos ++; } va_end(args); return -1; } void interpreter_registry_release(struct interpreter *interpreter, char *name) { extract_registry_value(interpreter->state, name); // Get rid of the value on top of the stack as well lua_pop(interpreter->state, 1); } void interpreter_destroy(struct interpreter *interpreter) { ASSERT(interpreter->state); lua_close(interpreter->state); interpreter->state = NULL; free(interpreter); }