1347 lines · 39.0 KB · C
RawDownload
/* * 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.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t