/* * This file is part of mpv. * * mpv is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * mpv 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */ #include <assert.h>#include <string.h>#include <sys/types.h>#include <sys/stat.h>#include <math.h> #include <lua.h>#include <lualib.h>#include <lauxlib.h> #include "osdep/io.h" #include "mpv_talloc.h" #include "common/common.h"#include "options/m_property.h"#include "common/msg.h"#include "common/msg_control.h"#include "common/stats.h"#include "options/m_option.h"#include "input/input.h"#include "options/path.h"#include "misc/bstr.h"#include "misc/json.h"#include "osdep/subprocess.h"#include "osdep/timer.h"#include "osdep/threads.h"#include "stream/stream.h"#include "sub/osd.h"#include "core.h"#include "command.h"#include "client.h"#include "libmpv/client.h" // List of builtin modules and their contents as strings.// All these are generated from player/lua/*.luastatic const char * const builtin_lua_scripts[][2] = { {"mp.defaults",# include "player/lua/defaults.lua.inc" }, {"mp.assdraw",# include "player/lua/assdraw.lua.inc" }, {"mp.fzy",# include "player/lua/fzy.lua.inc" }, {"mp.input",# include "player/lua/input.lua.inc" }, {"mp.options",# include "player/lua/options.lua.inc" }, {"@osc.lua",# include "player/lua/osc.lua.inc" }, {"@ytdl_hook.lua",# include "player/lua/ytdl_hook.lua.inc" }, {"@stats.lua",# include "player/lua/stats.lua.inc" }, {"@console.lua",# include "player/lua/console.lua.inc" }, {"@auto_profiles.lua",# include "player/lua/auto_profiles.lua.inc" }, {"@select.lua",# include "player/lua/select.lua.inc" }, {0}}; // Represents a loaded script. Each has its own Lua state.struct script_ctx { const char *name; const char *filename; const char *path; // NULL if single file lua_State *state; struct mp_log *log; struct mpv_handle *client; struct MPContext *mpctx; size_t lua_malloc_size; lua_Alloc lua_allocf; void *lua_alloc_ud; struct stats_ctx *stats;}; #if LUA_VERSION_NUM <= 501#define mp_cpcall lua_cpcall#else// Curse whoever had this stupid idea. Curse whoever thought it would be a good// idea not to include an emulated lua_cpcall() even more.static int mp_cpcall (lua_State *L, lua_CFunction func, void *ud){ lua_pushcfunction(L, func); // doesn't allocate in 5.2 (but does in 5.1) lua_pushlightuserdata(L, ud); return lua_pcall(L, 1, 0, 0);}#endif // Ensure that the given argument exists, even if it's nil. Can be used to// avoid confusing the last missing optional arg with the first temporary value// pushed to the stack.static void mp_lua_optarg(lua_State *L, int arg){ while (arg > lua_gettop(L)) lua_pushnil(L);} // autofree: avoid leaks if a lua-error occurs between talloc new/free.// If a lua c-function does a new allocation (not tied to an existing context),// and an uncaught lua-error occurs before "free" - the allocation is leaked. // autofree lua C function: same as lua_CFunction but with these differences:// - It accepts an additional void* argument - a pre-initialized talloc context// which it can use, and which is freed with its children once the function// completes - regardless if a lua error occurred or not. If a lua error did// occur then it's re-thrown after the ctx is freed.// The stack/arguments/upvalues/return are the same as with lua_CFunction.// - It's inserted into the lua VM using af_pushc{function,closure} instead of// lua_pushc{function,closure}, which takes care of wrapping it with the// automatic talloc allocation + lua-error-handling + talloc release.// This requires using AF_ENTRY instead of FN_ENTRY at struct fn_entry.// - The autofree overhead per call is roughly two additional plain lua calls.// Typically that's up to 20% slower than plain new+free without "auto",// and at most about twice slower - compared to bare new+free lua_CFunction.// - The overhead of af_push* is one additional lua-c-closure with two upvalues.typedef int (*af_CFunction)(lua_State *L, void *ctx); static void af_pushcclosure(lua_State *L, af_CFunction fn, int n);#define af_pushcfunction(L, fn) af_pushcclosure((L), (fn), 0) // add_af_dir, add_af_mpv_alloc take a valid DIR*/char* value respectively,// and closedir/mpv_free it when the parent is freed. static void destruct_af_dir(void *p){ closedir(*(DIR**)p);} static void add_af_dir(void *parent, DIR *d){ DIR **pd = talloc(parent, DIR*); *pd = d; talloc_set_destructor(pd, destruct_af_dir);} static void destruct_af_mpv_alloc(void *p){ mpv_free(*(char**)p);} static void add_af_mpv_alloc(void *parent, char *ma){ char **p = talloc(parent, char*); *p = ma; talloc_set_destructor(p, destruct_af_mpv_alloc);} // Perform the equivalent of mpv_free_node_contents(node) when tmp is freed.static void steal_node_allocations(void *tmp, mpv_node *node){ talloc_steal(tmp, node_get_alloc(node));} // lua_Alloc compatible. Serves only to track memory usage. This wraps the// existing allocator, partly because luajit requires the use of its internal// allocator on 64-bit platforms.static void *mp_lua_alloc(void *ud, void *ptr, size_t osize, size_t nsize){ struct script_ctx *ctx = ud; // Ah, what the fuck, screw whoever introduced this to Lua 5.2. if (!ptr) osize = 0; ptr = ctx->lua_allocf(ctx->lua_alloc_ud, ptr, osize, nsize); if (nsize && !ptr) return NULL; // allocation failed, so original memory left untouched ctx->lua_malloc_size = ctx->lua_malloc_size - osize + nsize; stats_size_value(ctx->stats, "mem", ctx->lua_malloc_size); return ptr;} static struct script_ctx *get_ctx(lua_State *L){ lua_getfield(L, LUA_REGISTRYINDEX, "ctx"); struct script_ctx *ctx = lua_touserdata(L, -1); lua_pop(L, 1); assert(ctx); return ctx;} static struct MPContext *get_mpctx(lua_State *L){ return get_ctx(L)->mpctx;} static int error_handler(lua_State *L){ struct script_ctx *ctx = get_ctx(L); if (luaL_loadstring(L, "return debug.traceback('', 3)") == 0) { // e fn|err lua_call(L, 0, 1); // e backtrace const char *tr = lua_tostring(L, -1); MP_WARN(ctx, "%s\n", tr ? tr : "(unknown)"); } lua_pop(L, 1); // e return 1;} // Check client API error code:// if err >= 0, push "true" to the stack, and return 1// if err < 0, push nil and then the error string to the stack, and return 2static int check_error(lua_State *L, int err){ if (err >= 0) { lua_pushboolean(L, 1); return 1; } lua_pushnil(L); lua_pushstring(L, mpv_error_string(err)); return 2;} static void add_functions(struct script_ctx *ctx); static void load_file(lua_State *L, const char *fname){ struct script_ctx *ctx = get_ctx(L); MP_DBG(ctx, "loading file %s\n", fname); void *tmp = talloc_new(ctx); // according to Lua manual chunkname should be '@' plus the filename char *dispname = talloc_asprintf(tmp, "@%s", fname); struct bstr s = stream_read_file(fname, tmp, ctx->mpctx->global, 100000000); if (!s.start) luaL_error(L, "Could not read file.\n"); if (luaL_loadbuffer(L, s.start, s.len, dispname)) lua_error(L); lua_call(L, 0, 1); talloc_free(tmp);} static int load_builtin(lua_State *L){ const char *name = luaL_checkstring(L, 1); char dispname[80]; snprintf(dispname, sizeof(dispname), "@%s", name); for (int n = 0; builtin_lua_scripts[n][0]; n++) { if (strcmp(name, builtin_lua_scripts[n][0]) == 0) { const char *script = builtin_lua_scripts[n][1]; if (luaL_loadbuffer(L, script, strlen(script), dispname)) lua_error(L); lua_call(L, 0, 1); return 1; } } luaL_error(L, "builtin module '%s' not found\n", name); return 0;} // Execute "require " .. namestatic void require(lua_State *L, const char *name){ struct script_ctx *ctx = get_ctx(L); MP_DBG(ctx, "loading %s\n", name); // Lazy, but better than calling the "require" function manually char buf[80]; snprintf(buf, sizeof(buf), "require '%s'", name); if (luaL_loadstring(L, buf)) lua_error(L); lua_call(L, 0, 0);} // Push the table of a module. If it doesn't exist, it's created.// The Lua script can call "require(module)" to "load" it.static void push_module_table(lua_State *L, const char *module){ lua_getglobal(L, "package"); // package lua_getfield(L, -1, "loaded"); // package loaded lua_remove(L, -2); // loaded lua_getfield(L, -1, module); // loaded module if (lua_isnil(L, -1)) { lua_pop(L, 1); // loaded lua_newtable(L); // loaded module lua_pushvalue(L, -1); // loaded module module lua_setfield(L, -3, module); // loaded module } lua_remove(L, -2); // module} static int load_scripts(lua_State *L){ struct script_ctx *ctx = get_ctx(L); const char *fname = ctx->filename; require(L, "mp.defaults"); if (fname[0] == '@') { require(L, fname); } else { load_file(L, fname); } lua_getglobal(L, "mp_event_loop"); // fn if (lua_isnil(L, -1)) luaL_error(L, "no event loop function\n"); lua_call(L, 0, 0); // - return 0;} static void fuck_lua(lua_State *L, const char *search_path, const char *extra){ void *tmp = talloc_new(NULL); lua_getglobal(L, "package"); // package lua_getfield(L, -1, search_path); // package search_path bstr path = bstr0(lua_tostring(L, -1)); char *newpath = talloc_strdup(tmp, ""); // Script-directory paths take priority. if (extra) { newpath = talloc_asprintf_append(newpath, "%s%s", newpath[0] ? ";" : "", mp_path_join(tmp, extra, "?.lua")); } // Unbelievable but true: Lua loads .lua files AND dynamic libraries from // the working directory. This is highly security relevant. // Lua scripts are still supposed to load globally installed libraries, so // try to get by by filtering out any relative paths. while (path.len) { bstr item; bstr_split_tok(path, ";", &item, &path); if (mp_path_is_absolute(item)) { newpath = talloc_asprintf_append(newpath, "%s%.*s", newpath[0] ? ";" : "", BSTR_P(item)); } } lua_pushstring(L, newpath); // package search_path newpath lua_setfield(L, -3, search_path); // package search_path lua_pop(L, 2); // - talloc_free(tmp);} static int run_lua(lua_State *L){ struct script_ctx *ctx = lua_touserdata(L, -1); lua_pop(L, 1); // - luaL_openlibs(L); // used by get_ctx() lua_pushlightuserdata(L, ctx); // ctx lua_setfield(L, LUA_REGISTRYINDEX, "ctx"); // - add_functions(ctx); // mp push_module_table(L, "mp"); // mp // "mp" is available by default, and no "require 'mp'" is needed lua_pushvalue(L, -1); // mp mp lua_setglobal(L, "mp"); // mp lua_pushstring(L, ctx->name); // mp name lua_setfield(L, -2, "script_name"); // mp // used by pushnode() lua_newtable(L); // mp table lua_pushvalue(L, -1); // mp table table lua_setfield(L, LUA_REGISTRYINDEX, "UNKNOWN_TYPE"); // mp table lua_setfield(L, -2, "UNKNOWN_TYPE"); // mp lua_newtable(L); // mp table lua_pushvalue(L, -1); // mp table table lua_setfield(L, LUA_REGISTRYINDEX, "MAP"); // mp table lua_setfield(L, -2, "MAP"); // mp lua_newtable(L); // mp table lua_pushvalue(L, -1); // mp table table lua_setfield(L, LUA_REGISTRYINDEX, "ARRAY"); // mp table lua_setfield(L, -2, "ARRAY"); // mp lua_pop(L, 1); // - assert(lua_gettop(L) == 0); // Add a preloader for each builtin Lua module lua_getglobal(L, "package"); // package assert(lua_type(L, -1) == LUA_TTABLE); lua_getfield(L, -1, "preload"); // package preload assert(lua_type(L, -1) == LUA_TTABLE); for (int n = 0; builtin_lua_scripts[n][0]; n++) { lua_pushcfunction(L, load_builtin); // package preload load_builtin lua_setfield(L, -2, builtin_lua_scripts[n][0]); } lua_pop(L, 2); // - assert(lua_gettop(L) == 0); fuck_lua(L, "path", ctx->path); fuck_lua(L, "cpath", NULL); assert(lua_gettop(L) == 0); // run this under an error handler that can do backtraces lua_pushcfunction(L, error_handler); // errf lua_pushcfunction(L, load_scripts); // errf fn if (lua_pcall(L, 0, 0, -2)) { // errf [error] const char *e = lua_tostring(L, -1); MP_FATAL(ctx, "Lua error: %s\n", e ? e : "(unknown)"); } return 0;} static int load_lua(struct mp_script_args *args){ int r = -1; struct script_ctx *ctx = talloc_ptrtype(NULL, ctx); *ctx = (struct script_ctx) { .mpctx = args->mpctx, .client = args->client, .name = mpv_client_name(args->client), .log = args->log, .filename = args->filename, .path = args->path, .stats = stats_ctx_create(ctx, args->mpctx->global, mp_tprintf(80, "script/%s", mpv_client_name(args->client))), }; stats_register_thread_cputime(ctx->stats, "cpu"); if (LUA_VERSION_NUM != 501 && LUA_VERSION_NUM != 502) { MP_FATAL(ctx, "Only Lua 5.1 and 5.2 are supported.\n"); goto error_out; } lua_State *L = ctx->state = luaL_newstate(); if (!L) { MP_FATAL(ctx, "Could not initialize Lua.\n"); goto error_out; } // Wrap the internal allocator with our version that does accounting ctx->lua_allocf = lua_getallocf(L, &ctx->lua_alloc_ud); lua_setallocf(L, mp_lua_alloc, ctx); if (mp_cpcall(L, run_lua, ctx)) { const char *err = "unknown error"; if (lua_type(L, -1) == LUA_TSTRING) // avoid allocation err = lua_tostring(L, -1); MP_FATAL(ctx, "Lua error: %s\n", err); goto error_out; } r = 0; error_out: if (ctx->lua_allocf) lua_setallocf(L, ctx->lua_allocf, ctx->lua_alloc_ud); if (ctx->state) lua_close(ctx->state); talloc_free(ctx); return r;} static int check_loglevel(lua_State *L, int arg){ const char *level = luaL_checkstring(L, arg); int n = mp_msg_find_level(level); if (n >= 0) return n; luaL_error(L, "Invalid log level '%s'", level); abort();Lines 1–500 of 1347. Load more
/* * This file is part of mpv. * * mpv is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * mpv 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */ #include <assert.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <math.h> #include <lua.h> #include <lualib.h> #include <lauxlib.h> #include "osdep/io.h" #include "mpv_talloc.h" #include "common/common.h" #include "options/m_property.h" #include "common/msg.h" #include "common/msg_control.h" #include "common/stats.h" #include "options/m_option.h" #include "input/input.h" #include "options/path.h" #include "misc/bstr.h" #include "misc/json.h" #include "osdep/subprocess.h" #include "osdep/timer.h" #include "osdep/threads.h" #include "stream/stream.h" #include "sub/osd.h" #include "core.h" #include "command.h" #include "client.h" #include "libmpv/client.h" // List of builtin modules and their contents as strings. // All these are generated from player/lua/*.lua static const char * const builtin_lua_scripts[][2] = { {"mp.defaults", # include "player/lua/defaults.lua.inc" }, {"mp.assdraw", # include "player/lua/assdraw.lua.inc" }, {"mp.fzy", # include "player/lua/fzy.lua.inc" }, {"mp.input", # include "player/lua/input.lua.inc" }, {"mp.options", # include "player/lua/options.lua.inc" }, {"@osc.lua", # include "player/lua/osc.lua.inc" }, {"@ytdl_hook.lua", # include "player/lua/ytdl_hook.lua.inc" }, {"@stats.lua", # include "player/lua/stats.lua.inc" }, {"@console.lua", # include "player/lua/console.lua.inc" }, {"@auto_profiles.lua", # include "player/lua/auto_profiles.lua.inc" }, {"@select.lua", # include "player/lua/select.lua.inc" }, {0} }; // Represents a loaded script. Each has its own Lua state. struct script_ctx { const char *name; const char *filename; const char *path; // NULL if single file lua_State *state; struct mp_log *log; struct mpv_handle *client; struct MPContext *mpctx; size_t lua_malloc_size; lua_Alloc lua_allocf; void *lua_alloc_ud; struct stats_ctx *stats; }; #if LUA_VERSION_NUM <= 501 #define mp_cpcall lua_cpcall #else // Curse whoever had this stupid idea. Curse whoever thought it would be a good // idea not to include an emulated lua_cpcall() even more. static int mp_cpcall (lua_State *L, lua_CFunction func, void *ud) { lua_pushcfunction(L, func); // doesn't allocate in 5.2 (but does in 5.1) lua_pushlightuserdata(L, ud); return lua_pcall(L, 1, 0, 0); } #endif // Ensure that the given argument exists, even if it's nil. Can be used to // avoid confusing the last missing optional arg with the first temporary value // pushed to the stack. static void mp_lua_optarg(lua_State *L, int arg) { while (arg > lua_gettop(L)) lua_pushnil(L); } // autofree: avoid leaks if a lua-error occurs between talloc new/free. // If a lua c-function does a new allocation (not tied to an existing context), // and an uncaught lua-error occurs before "free" - the allocation is leaked. // autofree lua C function: same as lua_CFunction but with these differences: // - It accepts an additional void* argument - a pre-initialized talloc context // which it can use, and which is freed with its children once the function // completes - regardless if a lua error occurred or not. If a lua error did // occur then it's re-thrown after the ctx is freed. // The stack/arguments/upvalues/return are the same as with lua_CFunction. // - It's inserted into the lua VM using af_pushc{function,closure} instead of // lua_pushc{function,closure}, which takes care of wrapping it with the // automatic talloc allocation + lua-error-handling + talloc release. // This requires using AF_ENTRY instead of FN_ENTRY at struct fn_entry. // - The autofree overhead per call is roughly two additional plain lua calls. // Typically that's up to 20% slower than plain new+free without "auto", // and at most about twice slower - compared to bare new+free lua_CFunction. // - The overhead of af_push* is one additional lua-c-closure with two upvalues. typedef int (*af_CFunction)(lua_State *L, void *ctx); static void af_pushcclosure(lua_State *L, af_CFunction fn, int n); #define af_pushcfunction(L, fn) af_pushcclosure((L), (fn), 0) // add_af_dir, add_af_mpv_alloc take a valid DIR*/char* value respectively, // and closedir/mpv_free it when the parent is freed. static void destruct_af_dir(void *p) { closedir(*(DIR**)p); } static void add_af_dir(void *parent, DIR *d) { DIR **pd = talloc(parent, DIR*); *pd = d; talloc_set_destructor(pd, destruct_af_dir); } static void destruct_af_mpv_alloc(void *p) { mpv_free(*(char**)p); } static void add_af_mpv_alloc(void *parent, char *ma) { char **p = talloc(parent, char*); *p = ma; talloc_set_destructor(p, destruct_af_mpv_alloc); } // Perform the equivalent of mpv_free_node_contents(node) when tmp is freed. static void steal_node_allocations(void *tmp, mpv_node *node) { talloc_steal(tmp, node_get_alloc(node)); } // lua_Alloc compatible. Serves only to track memory usage. This wraps the // existing allocator, partly because luajit requires the use of its internal // allocator on 64-bit platforms. static void *mp_lua_alloc(void *ud, void *ptr, size_t osize, size_t nsize) { struct script_ctx *ctx = ud; // Ah, what the fuck, screw whoever introduced this to Lua 5.2. if (!ptr) osize = 0; ptr = ctx->lua_allocf(ctx->lua_alloc_ud, ptr, osize, nsize); if (nsize && !ptr) return NULL; // allocation failed, so original memory left untouched ctx->lua_malloc_size = ctx->lua_malloc_size - osize + nsize; stats_size_value(ctx->stats, "mem", ctx->lua_malloc_size); return ptr; } static struct script_ctx *get_ctx(lua_State *L) { lua_getfield(L, LUA_REGISTRYINDEX, "ctx"); struct script_ctx *ctx = lua_touserdata(L, -1); lua_pop(L, 1); assert(ctx); return ctx; } static struct MPContext *get_mpctx(lua_State *L) { return get_ctx(L)->mpctx; } static int error_handler(lua_State *L) { struct script_ctx *ctx = get_ctx(L); if (luaL_loadstring(L, "return debug.traceback('', 3)") == 0) { // e fn|err lua_call(L, 0, 1); // e backtrace const char *tr = lua_tostring(L, -1); MP_WARN(ctx, "%s\n", tr ? tr : "(unknown)"); } lua_pop(L, 1); // e return 1; } // Check client API error code: // if err >= 0, push "true" to the stack, and return 1 // if err < 0, push nil and then the error string to the stack, and return 2 static int check_error(lua_State *L, int err) { if (err >= 0) { lua_pushboolean(L, 1); return 1; } lua_pushnil(L); lua_pushstring(L, mpv_error_string(err)); return 2; } static void add_functions(struct script_ctx *ctx); static void load_file(lua_State *L, const char *fname) { struct script_ctx *ctx = get_ctx(L); MP_DBG(ctx, "loading file %s\n", fname); void *tmp = talloc_new(ctx); // according to Lua manual chunkname should be '@' plus the filename char *dispname = talloc_asprintf(tmp, "@%s", fname); struct bstr s = stream_read_file(fname, tmp, ctx->mpctx->global, 100000000); if (!s.start) luaL_error(L, "Could not read file.\n"); if (luaL_loadbuffer(L, s.start, s.len, dispname)) lua_error(L); lua_call(L, 0, 1); talloc_free(tmp); } static int load_builtin(lua_State *L) { const char *name = luaL_checkstring(L, 1); char dispname[80]; snprintf(dispname, sizeof(dispname), "@%s", name); for (int n = 0; builtin_lua_scripts[n][0]; n++) { if (strcmp(name, builtin_lua_scripts[n][0]) == 0) { const char *script = builtin_lua_scripts[n][1]; if (luaL_loadbuffer(L, script, strlen(script), dispname)) lua_error(L); lua_call(L, 0, 1); return 1; } } luaL_error(L, "builtin module '%s' not found\n", name); return 0; } // Execute "require " .. name static void require(lua_State *L, const char *name) { struct script_ctx *ctx = get_ctx(L); MP_DBG(ctx, "loading %s\n", name); // Lazy, but better than calling the "require" function manually char buf[80]; snprintf(buf, sizeof(buf), "require '%s'", name); if (luaL_loadstring(L, buf)) lua_error(L); lua_call(L, 0, 0); } // Push the table of a module. If it doesn't exist, it's created. // The Lua script can call "require(module)" to "load" it. static void push_module_table(lua_State *L, const char *module) { lua_getglobal(L, "package"); // package lua_getfield(L, -1, "loaded"); // package loaded lua_remove(L, -2); // loaded lua_getfield(L, -1, module); // loaded module if (lua_isnil(L, -1)) { lua_pop(L, 1); // loaded lua_newtable(L); // loaded module lua_pushvalue(L, -1); // loaded module module lua_setfield(L, -3, module); // loaded module } lua_remove(L, -2); // module } static int load_scripts(lua_State *L) { struct script_ctx *ctx = get_ctx(L); const char *fname = ctx->filename; require(L, "mp.defaults"); if (fname[0] == '@') { require(L, fname); } else { load_file(L, fname); } lua_getglobal(L, "mp_event_loop"); // fn if (lua_isnil(L, -1)) luaL_error(L, "no event loop function\n"); lua_call(L, 0, 0); // - return 0; } static void fuck_lua(lua_State *L, const char *search_path, const char *extra) { void *tmp = talloc_new(NULL); lua_getglobal(L, "package"); // package lua_getfield(L, -1, search_path); // package search_path bstr path = bstr0(lua_tostring(L, -1)); char *newpath = talloc_strdup(tmp, ""); // Script-directory paths take priority. if (extra) { newpath = talloc_asprintf_append(newpath, "%s%s", newpath[0] ? ";" : "", mp_path_join(tmp, extra, "?.lua")); } // Unbelievable but true: Lua loads .lua files AND dynamic libraries from // the working directory. This is highly security relevant. // Lua scripts are still supposed to load globally installed libraries, so // try to get by by filtering out any relative paths. while (path.len) { bstr item; bstr_split_tok(path, ";", &item, &path); if (mp_path_is_absolute(item)) { newpath = talloc_asprintf_append(newpath, "%s%.*s", newpath[0] ? ";" : "", BSTR_P(item)); } } lua_pushstring(L, newpath); // package search_path newpath lua_setfield(L, -3, search_path); // package search_path lua_pop(L, 2); // - talloc_free(tmp); } static int run_lua(lua_State *L) { struct script_ctx *ctx = lua_touserdata(L, -1); lua_pop(L, 1); // - luaL_openlibs(L); // used by get_ctx() lua_pushlightuserdata(L, ctx); // ctx lua_setfield(L, LUA_REGISTRYINDEX, "ctx"); // - add_functions(ctx); // mp push_module_table(L, "mp"); // mp // "mp" is available by default, and no "require 'mp'" is needed lua_pushvalue(L, -1); // mp mp lua_setglobal(L, "mp"); // mp lua_pushstring(L, ctx->name); // mp name lua_setfield(L, -2, "script_name"); // mp // used by pushnode() lua_newtable(L); // mp table lua_pushvalue(L, -1); // mp table table lua_setfield(L, LUA_REGISTRYINDEX, "UNKNOWN_TYPE"); // mp table lua_setfield(L, -2, "UNKNOWN_TYPE"); // mp lua_newtable(L); // mp table lua_pushvalue(L, -1); // mp table table lua_setfield(L, LUA_REGISTRYINDEX, "MAP"); // mp table lua_setfield(L, -2, "MAP"); // mp lua_newtable(L); // mp table lua_pushvalue(L, -1); // mp table table lua_setfield(L, LUA_REGISTRYINDEX, "ARRAY"); // mp table lua_setfield(L, -2, "ARRAY"); // mp lua_pop(L, 1); // - assert(lua_gettop(L) == 0); // Add a preloader for each builtin Lua module lua_getglobal(L, "package"); // package assert(lua_type(L, -1) == LUA_TTABLE); lua_getfield(L, -1, "preload"); // package preload assert(lua_type(L, -1) == LUA_TTABLE); for (int n = 0; builtin_lua_scripts[n][0]; n++) { lua_pushcfunction(L, load_builtin); // package preload load_builtin lua_setfield(L, -2, builtin_lua_scripts[n][0]); } lua_pop(L, 2); // - assert(lua_gettop(L) == 0); fuck_lua(L, "path", ctx->path); fuck_lua(L, "cpath", NULL); assert(lua_gettop(L) == 0); // run this under an error handler that can do backtraces lua_pushcfunction(L, error_handler); // errf lua_pushcfunction(L, load_scripts); // errf fn if (lua_pcall(L, 0, 0, -2)) { // errf [error] const char *e = lua_tostring(L, -1); MP_FATAL(ctx, "Lua error: %s\n", e ? e : "(unknown)"); } return 0; } static int load_lua(struct mp_script_args *args) { int r = -1; struct script_ctx *ctx = talloc_ptrtype(NULL, ctx); *ctx = (struct script_ctx) { .mpctx = args->mpctx, .client = args->client, .name = mpv_client_name(args->client), .log = args->log, .filename = args->filename, .path = args->path, .stats = stats_ctx_create(ctx, args->mpctx->global, mp_tprintf(80, "script/%s", mpv_client_name(args->client))), }; stats_register_thread_cputime(ctx->stats, "cpu"); if (LUA_VERSION_NUM != 501 && LUA_VERSION_NUM != 502) { MP_FATAL(ctx, "Only Lua 5.1 and 5.2 are supported.\n"); goto error_out; } lua_State *L = ctx->state = luaL_newstate(); if (!L) { MP_FATAL(ctx, "Could not initialize Lua.\n"); goto error_out; } // Wrap the internal allocator with our version that does accounting ctx->lua_allocf = lua_getallocf(L, &ctx->lua_alloc_ud); lua_setallocf(L, mp_lua_alloc, ctx); if (mp_cpcall(L, run_lua, ctx)) { const char *err = "unknown error"; if (lua_type(L, -1) == LUA_TSTRING) // avoid allocation err = lua_tostring(L, -1); MP_FATAL(ctx, "Lua error: %s\n", err); goto error_out; } r = 0; error_out: if (ctx->lua_allocf) lua_setallocf(L, ctx->lua_allocf, ctx->lua_alloc_ud); if (ctx->state) lua_close(ctx->state); talloc_free(ctx); return r; } static int check_loglevel(lua_State *L, int arg) { const char *level = luaL_checkstring(L, arg); int n = mp_msg_find_level(level); if (n >= 0) return n; luaL_error(L, "Invalid log level '%s'", level); abort();