diff options
Diffstat (limited to 'player/lua.c')
-rw-r--r-- | player/lua.c | 608 |
1 files changed, 309 insertions, 299 deletions
diff --git a/player/lua.c b/player/lua.c index 757f449f7b..63547694e2 100644 --- a/player/lua.c +++ b/player/lua.c @@ -36,6 +36,7 @@ #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" @@ -44,7 +45,6 @@ #include "osdep/subprocess.h" #include "osdep/timer.h" #include "osdep/threads.h" -#include "osdep/getpid.h" #include "stream/stream.h" #include "sub/osd.h" #include "core.h" @@ -56,22 +56,31 @@ // All these are generated from player/lua/*.lua static const char * const builtin_lua_scripts[][2] = { {"mp.defaults", -# include "player/lua/defaults.inc" +# include "player/lua/defaults.lua.inc" }, {"mp.assdraw", -# include "player/lua/assdraw.inc" +# include "player/lua/assdraw.lua.inc" + }, + {"mp.input", +# include "player/lua/input.lua.inc" }, {"mp.options", -# include "player/lua/options.inc" +# include "player/lua/options.lua.inc" }, {"@osc.lua", -# include "player/lua/osc.inc" +# include "player/lua/osc.lua.inc" }, {"@ytdl_hook.lua", -# include "player/lua/ytdl_hook.inc" +# include "player/lua/ytdl_hook.lua.inc" }, {"@stats.lua", -# include "player/lua/stats.inc" +# include "player/lua/stats.lua.inc" + }, + {"@console.lua", +# include "player/lua/console.lua.inc" + }, + {"@auto_profiles.lua", +# include "player/lua/auto_profiles.lua.inc" }, {0} }; @@ -80,10 +89,15 @@ static const char * const builtin_lua_scripts[][2] = { 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 @@ -110,43 +124,85 @@ static void mp_lua_optarg(lua_State *L, int arg) lua_pushnil(L); } -static int destroy_crap(lua_State *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) { - void **data = luaL_checkudata(L, 1, "ohthispain"); - talloc_free(data[0]); - data[0] = NULL; - return 0; + closedir(*(DIR**)p); } -// Creates a small userdata object and pushes it to the Lua stack. The function -// will (on the C level) return a talloc object that will be released by the -// userdata gc routine. -// This can be used to free temporary C data structures correctly if Lua errors -// happen. -// You can't free the talloc context directly; the Lua __gc handler does this. -// In my cases, talloc_free_children(returnval) will be used to free attached -// memory in advance when it's known not to be needed anymore (a minor -// optimization). Freeing it completely must be left to the Lua GC. -static void *mp_lua_PITA(lua_State *L) -{ - void **data = lua_newuserdata(L, sizeof(void *)); // u - if (luaL_newmetatable(L, "ohthispain")) { // u metatable - lua_pushvalue(L, -1); // u metatable metatable - lua_setfield(L, -2, "__index"); // u metatable - lua_pushcfunction(L, destroy_crap); // u metatable gc - lua_setfield(L, -2, "__gc"); // u metatable - } - lua_setmetatable(L, -2); // u - *data = talloc_new(NULL); - return *data; +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 auto_free_node(void *tmp, mpv_node *node) +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"); @@ -195,10 +251,16 @@ 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); - int r = luaL_loadfile(L, fname); - if (r) + 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, 0); + lua_call(L, 0, 1); + talloc_free(tmp); } static int load_builtin(lua_State *L) @@ -270,23 +332,38 @@ static int load_scripts(lua_State *L) return 0; } -static void set_path(lua_State *L) +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, "path"); // package path - const char *path = lua_tostring(L, -1); - - char *newpath = talloc_strdup(tmp, path ? path : ""); - char **luadir = mp_find_all_config_files(tmp, get_mpctx(L)->global, "scripts"); - for (int i = 0; luadir && luadir[i]; i++) { - newpath = talloc_asprintf_append(newpath, ";%s", - mp_path_join(tmp, luadir[i], "?.lua")); + 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")); } - lua_pushstring(L, newpath); // package path newpath - lua_setfield(L, -3, "path"); // package path + // 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); @@ -345,7 +422,8 @@ static int run_lua(lua_State *L) assert(lua_gettop(L) == 0); - set_path(L); + 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 @@ -359,20 +437,24 @@ static int run_lua(lua_State *L) return 0; } -static int load_lua(struct mpv_handle *client, const char *fname) +static int load_lua(struct mp_script_args *args) { - struct MPContext *mpctx = mp_client_get_core(client); int r = -1; struct script_ctx *ctx = talloc_ptrtype(NULL, ctx); *ctx = (struct script_ctx) { - .mpctx = mpctx, - .client = client, - .name = mpv_client_name(client), - .log = mp_client_get_log(client), - .filename = fname, + .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; @@ -384,6 +466,10 @@ static int load_lua(struct mpv_handle *client, const char *fname) 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 @@ -404,10 +490,9 @@ error_out: static int check_loglevel(lua_State *L, int arg) { const char *level = luaL_checkstring(L, arg); - for (int n = 0; n < MSGL_MAX; n++) { - if (mp_log_levels[n] && strcasecmp(mp_log_levels[n], level) == 0) - return n; - } + int n = mp_msg_find_level(level); + if (n >= 0) + return n; luaL_error(L, "Invalid log level '%s'", level); abort(); } @@ -427,7 +512,7 @@ static int script_log(lua_State *L) const char *s = lua_tostring(L, -1); if (s == NULL) return luaL_error(L, "Invalid argument"); - mp_msg(ctx->log, msgl, "%s%s", s, i > 0 ? " " : ""); + mp_msg(ctx->log, msgl, (i == 2 ? "%s" : " %s"), s); lua_pop(L, 1); // args... tostring } mp_msg(ctx->log, msgl, "\n"); @@ -449,127 +534,29 @@ static int script_find_config_file(lua_State *L) return 1; } -static int script_suspend(lua_State *L) +static int script_get_script_directory(lua_State *L) { struct script_ctx *ctx = get_ctx(L); - MP_ERR(ctx, "mp.suspend() is deprecated and does nothing.\n"); - return 0; -} - -static int script_resume(lua_State *L) -{ - return 0; -} - -static int script_resume_all(lua_State *L) -{ + if (ctx->path) { + lua_pushstring(L, ctx->path); + return 1; + } return 0; } static void pushnode(lua_State *L, mpv_node *node); -static int script_wait_event(lua_State *L) +static int script_raw_wait_event(lua_State *L, void *tmp) { struct script_ctx *ctx = get_ctx(L); mpv_event *event = mpv_wait_event(ctx->client, luaL_optnumber(L, 1, 1e20)); - lua_newtable(L); // event - lua_pushstring(L, mpv_event_name(event->event_id)); // event name - lua_setfield(L, -2, "event"); // event - - if (event->reply_userdata) { - lua_pushnumber(L, event->reply_userdata); - lua_setfield(L, -2, "id"); - } - - if (event->error < 0) { - lua_pushstring(L, mpv_error_string(event->error)); // event err - lua_setfield(L, -2, "error"); // event - } + struct mpv_node rn; + mpv_event_to_node(&rn, event); + steal_node_allocations(tmp, &rn); - switch (event->event_id) { - case MPV_EVENT_LOG_MESSAGE: { - mpv_event_log_message *msg = event->data; - - lua_pushstring(L, msg->prefix); // event s - lua_setfield(L, -2, "prefix"); // event - lua_pushstring(L, msg->level); // event s - lua_setfield(L, -2, "level"); // event - lua_pushstring(L, msg->text); // event s - lua_setfield(L, -2, "text"); // event - break; - } - case MPV_EVENT_CLIENT_MESSAGE: { - mpv_event_client_message *msg = event->data; - - lua_newtable(L); // event args - for (int n = 0; n < msg->num_args; n++) { - lua_pushinteger(L, n + 1); // event args N - lua_pushstring(L, msg->args[n]); // event args N val - lua_settable(L, -3); // event args - } - lua_setfield(L, -2, "args"); // event - break; - } - case MPV_EVENT_END_FILE: { - mpv_event_end_file *eef = event->data; - const char *reason; - switch (eef->reason) { - case MPV_END_FILE_REASON_EOF: reason = "eof"; break; - case MPV_END_FILE_REASON_STOP: reason = "stop"; break; - case MPV_END_FILE_REASON_QUIT: reason = "quit"; break; - case MPV_END_FILE_REASON_ERROR: reason = "error"; break; - case MPV_END_FILE_REASON_REDIRECT: reason = "redirect"; break; - default: - reason = "unknown"; - } - lua_pushstring(L, reason); // event reason - lua_setfield(L, -2, "reason"); // event - - if (eef->reason == MPV_END_FILE_REASON_ERROR) { - lua_pushstring(L, mpv_error_string(eef->error)); // event error - lua_setfield(L, -2, "error"); // event - } - break; - } - case MPV_EVENT_PROPERTY_CHANGE: { - mpv_event_property *prop = event->data; - lua_pushstring(L, prop->name); - lua_setfield(L, -2, "name"); - switch (prop->format) { - case MPV_FORMAT_NODE: - pushnode(L, prop->data); - break; - case MPV_FORMAT_DOUBLE: - lua_pushnumber(L, *(double *)prop->data); - break; - case MPV_FORMAT_FLAG: - lua_pushboolean(L, *(int *)prop->data); - break; - case MPV_FORMAT_STRING: - lua_pushstring(L, *(char **)prop->data); - break; - default: - lua_pushnil(L); - } - lua_setfield(L, -2, "data"); - break; - } - case MPV_EVENT_HOOK: { - mpv_event_hook *hook = event->data; - lua_pushinteger(L, hook->id); - lua_setfield(L, -2, "hook_id"); - break; - } - case MPV_EVENT_COMMAND_REPLY: { - mpv_event_command *cmd = event->data; - pushnode(L, &cmd->result); - lua_setfield(L, -2, "result"); - break; - } - default: ; - } + pushnode(L, &rn); // event // return event return 1; @@ -596,9 +583,11 @@ static int script_request_event(lua_State *L) static int script_enable_messages(lua_State *L) { struct script_ctx *ctx = get_ctx(L); - check_loglevel(L, 1); const char *level = luaL_checkstring(L, 1); - return check_error(L, mpv_request_log_messages(ctx->client, level)); + int r = mpv_request_log_messages(ctx->client, level); + if (r == MPV_ERROR_INVALID_PARAMETER) + luaL_error(L, "Invalid log level '%s'", level); + return check_error(L, r); } static int script_command(lua_State *L) @@ -626,6 +615,14 @@ static int script_commandv(lua_State *L) return check_error(L, mpv_command(ctx->client, args)); } +static int script_del_property(lua_State *L) +{ + struct script_ctx *ctx = get_ctx(L); + const char *p = luaL_checkstring(L, 1); + + return check_error(L, mpv_del_property(ctx->client, p)); +} + static int script_set_property(lua_State *L) { struct script_ctx *ctx = get_ctx(L); @@ -669,6 +666,8 @@ static int script_set_property_number(lua_State *L) static void makenode(void *tmp, mpv_node *dst, lua_State *L, int t) { + luaL_checkstack(L, 6, "makenode"); + if (t < 0) t = lua_gettop(L) + (t + 1); switch (lua_type(L, t)) { @@ -690,10 +689,21 @@ static void makenode(void *tmp, mpv_node *dst, lua_State *L, int t) dst->format = MPV_FORMAT_FLAG; dst->u.flag = !!lua_toboolean(L, t); break; - case LUA_TSTRING: - dst->format = MPV_FORMAT_STRING; - dst->u.string = talloc_strdup(tmp, lua_tostring(L, t)); + case LUA_TSTRING: { + size_t len = 0; + char *s = (char *)lua_tolstring(L, t, &len); + bool has_zeros = !!memchr(s, 0, len); + if (has_zeros) { + mpv_byte_array *ba = talloc_zero(tmp, mpv_byte_array); + *ba = (mpv_byte_array){talloc_memdup(tmp, s, len), len}; + dst->format = MPV_FORMAT_BYTE_ARRAY; + dst->u.ba = ba; + } else { + dst->format = MPV_FORMAT_STRING; + dst->u.string = talloc_strdup(tmp, s); + } break; + } case LUA_TTABLE: { // Lua uses the same type for arrays and maps, so guess the correct one. int format = MPV_FORMAT_NONE; @@ -776,31 +786,28 @@ static void makenode(void *tmp, mpv_node *dst, lua_State *L, int t) } } -static int script_set_property_native(lua_State *L) +static int script_set_property_native(lua_State *L, void *tmp) { struct script_ctx *ctx = get_ctx(L); const char *p = luaL_checkstring(L, 1); struct mpv_node node; - void *tmp = mp_lua_PITA(L); makenode(tmp, &node, L, 2); int res = mpv_set_property(ctx->client, p, MPV_FORMAT_NODE, &node); - talloc_free_children(tmp); return check_error(L, res); } -static int script_get_property(lua_State *L) +static int script_get_property_base(lua_State *L, void *tmp, int is_osd) { struct script_ctx *ctx = get_ctx(L); const char *name = luaL_checkstring(L, 1); - int type = lua_tointeger(L, lua_upvalueindex(1)) - ? MPV_FORMAT_OSD_STRING : MPV_FORMAT_STRING; + int type = is_osd ? MPV_FORMAT_OSD_STRING : MPV_FORMAT_STRING; char *result = NULL; int err = mpv_get_property(ctx->client, name, type, &result); if (err >= 0) { + add_af_mpv_alloc(tmp, result); lua_pushstring(L, result); - talloc_free(result); return 1; } else { if (lua_isnoneornil(L, 2) && type == MPV_FORMAT_OSD_STRING) { @@ -813,6 +820,16 @@ static int script_get_property(lua_State *L) } } +static int script_get_property(lua_State *L, void *tmp) +{ + return script_get_property_base(L, tmp, 0); +} + +static int script_get_property_osd(lua_State *L, void *tmp) +{ + return script_get_property_base(L, tmp, 1); +} + static int script_get_property_bool(lua_State *L) { struct script_ctx *ctx = get_ctx(L); @@ -850,7 +867,7 @@ static int script_get_property_number(lua_State *L) static void pushnode(lua_State *L, mpv_node *node) { - luaL_checkstack(L, 6, "stack overflow"); + luaL_checkstack(L, 6, "pushnode"); switch (node->format) { case MPV_FORMAT_STRING: @@ -900,19 +917,17 @@ static void pushnode(lua_State *L, mpv_node *node) } } -static int script_get_property_native(lua_State *L) +static int script_get_property_native(lua_State *L, void *tmp) { struct script_ctx *ctx = get_ctx(L); const char *name = luaL_checkstring(L, 1); mp_lua_optarg(L, 2); - void *tmp = mp_lua_PITA(L); mpv_node node; int err = mpv_get_property(ctx->client, name, MPV_FORMAT_NODE, &node); if (err >= 0) { - auto_free_node(tmp, &node); + steal_node_allocations(tmp, &node); pushnode(L, &node); - talloc_free_children(tmp); return 1; } lua_pushvalue(L, 2); @@ -953,19 +968,17 @@ static int script_raw_unobserve_property(lua_State *L) return 1; } -static int script_command_native(lua_State *L) +static int script_command_native(lua_State *L, void *tmp) { struct script_ctx *ctx = get_ctx(L); mp_lua_optarg(L, 2); struct mpv_node node; struct mpv_node result; - void *tmp = mp_lua_PITA(L); makenode(tmp, &node, L, 1); int err = mpv_command_node(ctx->client, &node, &result); if (err >= 0) { - auto_free_node(tmp, &result); + steal_node_allocations(tmp, &result); pushnode(L, &result); - talloc_free_children(tmp); return 1; } lua_pushvalue(L, 2); @@ -973,15 +986,13 @@ static int script_command_native(lua_State *L) return 2; } -static int script_raw_command_native_async(lua_State *L) +static int script_raw_command_native_async(lua_State *L, void *tmp) { struct script_ctx *ctx = get_ctx(L); uint64_t id = luaL_checknumber(L, 1); struct mpv_node node; - void *tmp = mp_lua_PITA(L); makenode(tmp, &node, L, 2); int res = mpv_command_node_async(ctx->client, id, &node); - talloc_free_children(tmp); return check_error(L, res); } @@ -993,52 +1004,6 @@ static int script_raw_abort_async_command(lua_State *L) return 0; } -static int script_set_osd_ass(lua_State *L) -{ - struct script_ctx *ctx = get_ctx(L); - int res_x = luaL_checkinteger(L, 1); - int res_y = luaL_checkinteger(L, 2); - const char *text = luaL_checkstring(L, 3); - if (!text[0]) - text = " "; // force external OSD initialization - osd_set_external(ctx->mpctx->osd, ctx->client, res_x, res_y, (char *)text); - mp_wakeup_core(ctx->mpctx); - return 0; -} - -static int script_get_osd_size(lua_State *L) -{ - struct MPContext *mpctx = get_mpctx(L); - struct mp_osd_res vo_res = osd_get_vo_res(mpctx->osd); - double aspect = 1.0 * vo_res.w / MPMAX(vo_res.h, 1) / - (vo_res.display_par ? vo_res.display_par : 1); - lua_pushnumber(L, vo_res.w); - lua_pushnumber(L, vo_res.h); - lua_pushnumber(L, aspect); - return 3; -} - -static int script_get_osd_margins(lua_State *L) -{ - struct MPContext *mpctx = get_mpctx(L); - struct mp_osd_res vo_res = osd_get_vo_res(mpctx->osd); - lua_pushnumber(L, vo_res.ml); - lua_pushnumber(L, vo_res.mt); - lua_pushnumber(L, vo_res.mr); - lua_pushnumber(L, vo_res.mb); - return 4; -} - -static int script_get_mouse_pos(lua_State *L) -{ - struct MPContext *mpctx = get_mpctx(L); - int px, py; - mp_input_get_mouse_pos(mpctx->input, &px, &py); - lua_pushnumber(L, px); - lua_pushnumber(L, py); - return 2; -} - static int script_get_time(lua_State *L) { struct script_ctx *ctx = get_ctx(L); @@ -1094,7 +1059,7 @@ static int script_raw_hook_continue(lua_State *L) return check_error(L, mpv_hook_continue(ctx->client, id)); } -static int script_readdir(lua_State *L) +static int script_readdir(lua_State *L, void *tmp) { // 0 1 2 3 const char *fmts[] = {"all", "files", "dirs", "normal", NULL}; @@ -1106,8 +1071,9 @@ static int script_readdir(lua_State *L) lua_pushstring(L, "error"); return 2; } + add_af_dir(tmp, dir); lua_newtable(L); // list - char *fullpath = NULL; + char *fullpath = talloc_strdup(tmp, ""); struct dirent *e; int n = 0; while ((e = readdir(dir))) { @@ -1129,8 +1095,6 @@ static int script_readdir(lua_State *L) lua_pushstring(L, name); // list index name lua_settable(L, -3); // list } - closedir(dir); - talloc_free(fullpath); return 1; } @@ -1151,7 +1115,7 @@ static int script_file_info(lua_State *L) "mode", "size", "atime", "mtime", "ctime", NULL }; - const unsigned int stat_values[] = { + const lua_Number stat_values[] = { statbuf.st_mode, statbuf.st_size, statbuf.st_atime, @@ -1161,7 +1125,7 @@ static int script_file_info(lua_State *L) // Add all fields for (int i = 0; stat_names[i]; i++) { - lua_pushinteger(L, stat_values[i]); + lua_pushnumber(L, stat_values[i]); lua_setfield(L, -2, stat_names[i]); } @@ -1185,31 +1149,23 @@ static int script_split_path(lua_State *L) return 2; } -static int script_join_path(lua_State *L) +static int script_join_path(lua_State *L, void *tmp) { const char *p1 = luaL_checkstring(L, 1); const char *p2 = luaL_checkstring(L, 2); - char *r = mp_path_join(NULL, p1, p2); + char *r = mp_path_join(tmp, p1, p2); lua_pushstring(L, r); - talloc_free(r); return 1; } -static int script_getpid(lua_State *L) -{ - lua_pushnumber(L, mp_getpid()); - return 1; -} - -static int script_parse_json(lua_State *L) +static int script_parse_json(lua_State *L, void *tmp) { mp_lua_optarg(L, 2); - void *tmp = mp_lua_PITA(L); char *text = talloc_strdup(tmp, luaL_checkstring(L, 1)); bool trail = lua_toboolean(L, 2); bool ok = false; struct mpv_node node; - if (json_parse(tmp, &node, &text, 32) >= 0) { + if (json_parse(tmp, &node, &text, MAX_JSON_DEPTH) >= 0) { json_skip_whitespace(&text); ok = !text[0] || trail; } @@ -1221,59 +1177,65 @@ static int script_parse_json(lua_State *L) lua_pushstring(L, "error"); } lua_pushstring(L, text); - talloc_free_children(tmp); return 3; } -static int script_format_json(lua_State *L) +static int script_format_json(lua_State *L, void *tmp) { - void *tmp = mp_lua_PITA(L); struct mpv_node node; makenode(tmp, &node, L, 1); char *dst = talloc_strdup(tmp, ""); if (json_write(&dst, &node) >= 0) { lua_pushstring(L, dst); - lua_pushnil(L); - } else { - lua_pushnil(L); - lua_pushstring(L, "error"); + return 1; } - talloc_free_children(tmp); + + lua_pushnil(L); + lua_pushstring(L, "error"); return 2; } -#define FN_ENTRY(name) {#name, script_ ## name} +static int script_get_env_list(lua_State *L) +{ + lua_newtable(L); // table + for (int n = 0; environ && environ[n]; n++) { + lua_pushstring(L, environ[n]); // table str + lua_rawseti(L, -2, n + 1); // table + } + return 1; +} + +#define FN_ENTRY(name) {#name, script_ ## name, 0} +#define AF_ENTRY(name) {#name, 0, script_ ## name} struct fn_entry { const char *name; - int (*fn)(lua_State *L); + int (*fn)(lua_State *L); // lua_CFunction + int (*af)(lua_State *L, void *); // af_CFunction }; static const struct fn_entry main_fns[] = { FN_ENTRY(log), - FN_ENTRY(suspend), - FN_ENTRY(resume), - FN_ENTRY(resume_all), - FN_ENTRY(wait_event), + AF_ENTRY(raw_wait_event), FN_ENTRY(request_event), FN_ENTRY(find_config_file), + FN_ENTRY(get_script_directory), FN_ENTRY(command), FN_ENTRY(commandv), - FN_ENTRY(command_native), - FN_ENTRY(raw_command_native_async), + AF_ENTRY(command_native), + AF_ENTRY(raw_command_native_async), FN_ENTRY(raw_abort_async_command), + AF_ENTRY(get_property), + AF_ENTRY(get_property_osd), FN_ENTRY(get_property_bool), FN_ENTRY(get_property_number), - FN_ENTRY(get_property_native), + AF_ENTRY(get_property_native), + FN_ENTRY(del_property), FN_ENTRY(set_property), FN_ENTRY(set_property_bool), FN_ENTRY(set_property_number), - FN_ENTRY(set_property_native), + AF_ENTRY(set_property_native), FN_ENTRY(raw_observe_property), FN_ENTRY(raw_unobserve_property), - FN_ENTRY(set_osd_ass), - FN_ENTRY(get_osd_size), - FN_ENTRY(get_osd_margins), - FN_ENTRY(get_mouse_pos), FN_ENTRY(get_time), FN_ENTRY(input_set_section_mouse_area), FN_ENTRY(format_time), @@ -1285,22 +1247,83 @@ static const struct fn_entry main_fns[] = { }; static const struct fn_entry utils_fns[] = { - FN_ENTRY(readdir), + AF_ENTRY(readdir), FN_ENTRY(file_info), FN_ENTRY(split_path), - FN_ENTRY(join_path), - FN_ENTRY(getpid), - FN_ENTRY(parse_json), - FN_ENTRY(format_json), + AF_ENTRY(join_path), + AF_ENTRY(parse_json), + AF_ENTRY(format_json), + FN_ENTRY(get_env_list), {0} }; +typedef struct autofree_data { + af_CFunction target; + void *ctx; +} autofree_data; + +/* runs the target autofree script_* function with the ctx argument */ +static int script_autofree_call(lua_State *L) +{ + // n*args &data + autofree_data *data = lua_touserdata(L, -1); + lua_pop(L, 1); // n*args + assert(data && data->target && data->ctx); + return data->target(L, data->ctx); +} + +static int script_autofree_trampoline(lua_State *L) +{ + // n*args + autofree_data data = { + .target = lua_touserdata(L, lua_upvalueindex(2)), // fn + .ctx = NULL, + }; + assert(data.target); + + lua_pushvalue(L, lua_upvalueindex(1)); // n*args autofree_call (closure) + lua_insert(L, 1); // autofree_call n*args + lua_pushlightuserdata(L, &data); // autofree_call n*args &data + + data.ctx = talloc_new(NULL); + int r = lua_pcall(L, lua_gettop(L) - 1, LUA_MULTRET, 0); // m*retvals + talloc_free(data.ctx); + + if (r) + lua_error(L); + + return lua_gettop(L); // m (retvals) +} + +static void af_pushcclosure(lua_State *L, af_CFunction fn, int n) +{ + // Instead of pushing a direct closure of fn with n upvalues, we push an + // autofree_trampoline closure with two upvalues: + // 1: autofree_call closure with the n upvalues given here. + // 2: fn + // + // when called the autofree_trampoline closure will pcall the autofree_call + // closure with the current lua call arguments and an additional argument + // which holds ctx and fn. the autofree_call closure (with the n upvalues + // given here) calls fn directly and provides it with the ctx C argument, + // so that fn sees the exact n upvalues and lua call arguments as intended, + // wrapped with ctx init/cleanup. + + lua_pushcclosure(L, script_autofree_call, n); + lua_pushlightuserdata(L, fn); + lua_pushcclosure(L, script_autofree_trampoline, 2); +} + static void register_package_fns(lua_State *L, char *module, const struct fn_entry *e) { push_module_table(L, module); // modtable for (int n = 0; e[n].name; n++) { - lua_pushcclosure(L, e[n].fn, 0); // modtable fn + if (e[n].af) { + af_pushcclosure(L, e[n].af, 0); // modtable fn + } else { + lua_pushcclosure(L, e[n].fn, 0); // modtable fn + } lua_setfield(L, -2, e[n].name); // modtable } lua_pop(L, 1); // - @@ -1311,24 +1334,11 @@ static void add_functions(struct script_ctx *ctx) lua_State *L = ctx->state; register_package_fns(L, "mp", main_fns); - - push_module_table(L, "mp"); // mp - - lua_pushinteger(L, 0); - lua_pushcclosure(L, script_get_property, 1); - lua_setfield(L, -2, "get_property"); - - lua_pushinteger(L, 1); - lua_pushcclosure(L, script_get_property, 1); - lua_setfield(L, -2, "get_property_osd"); - - lua_pop(L, 1); // - - register_package_fns(L, "mp.utils", utils_fns); } const struct mp_scripting mp_scripting_lua = { - .name = "lua script", + .name = "lua", .file_ext = "lua", .load = load_lua, }; |