wplua: move under modules/module-lua-scripting

It's unlikely that wplua will ever be useful outside the context
of module-lua-scripting, so let's move it to keep all the code in one place
This commit is contained in:
George Kiagiadakis
2022-02-21 10:30:56 +02:00
parent 2f186c10e7
commit 17a257ddbe
24 changed files with 18 additions and 15 deletions

View File

@@ -0,0 +1,108 @@
/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include "wplua.h"
#include "private.h"
#include <wp/wp.h>
static lua_CFunction
find_method_in_luaL_Reg (luaL_Reg *reg, const gchar *method)
{
if (reg) {
while (reg->name) {
if (!g_strcmp0 (method, reg->name))
return reg->func;
reg++;
}
}
return NULL;
}
static int
_wplua_gboxed___index (lua_State *L)
{
luaL_argcheck (L, wplua_isboxed (L, 1, G_TYPE_BOXED), 1,
"expected userdata storing GValue<GBoxed>");
GValue *obj_v = lua_touserdata (L, 1);
const gchar *key = luaL_checkstring (L, 2);
lua_CFunction func = NULL;
GHashTable *vtables;
lua_pushliteral (L, "wplua_vtables");
lua_gettable (L, LUA_REGISTRYINDEX);
vtables = wplua_toboxed (L, -1);
lua_pop (L, 1);
/* search in registered vtables */
if (!func) {
GType type = G_VALUE_TYPE (obj_v);
while (!func && type) {
luaL_Reg *reg = g_hash_table_lookup (vtables, GUINT_TO_POINTER (type));
func = find_method_in_luaL_Reg (reg, key);
type = g_type_parent (type);
}
}
if (func) {
lua_pushcfunction (L, func);
return 1;
}
return 0;
}
void
_wplua_init_gboxed (lua_State *L)
{
static const luaL_Reg gboxed_meta[] = {
{ "__gc", _wplua_gvalue_userdata___gc },
{ "__eq", _wplua_gvalue_userdata___eq },
{ "__index", _wplua_gboxed___index },
{ NULL, NULL }
};
luaL_newmetatable (L, "GBoxed");
luaL_setfuncs (L, gboxed_meta, 0);
lua_pop (L, 1);
}
void
wplua_pushboxed (lua_State * L, GType type, gpointer object)
{
g_return_if_fail (G_TYPE_FUNDAMENTAL (type) == G_TYPE_BOXED);
GValue *v = _wplua_pushgvalue_userdata (L, type);
wp_trace_boxed (type, object, "pushing to Lua, v=%p", v);
g_value_take_boxed (v, object);
luaL_getmetatable (L, "GBoxed");
lua_setmetatable (L, -2);
}
gpointer
wplua_toboxed (lua_State *L, int idx)
{
g_return_val_if_fail (_wplua_isgvalue_userdata (L, idx, G_TYPE_BOXED), NULL);
return g_value_get_boxed ((GValue *) lua_touserdata (L, idx));
}
gpointer
wplua_checkboxed (lua_State *L, int idx, GType type)
{
if (G_UNLIKELY (!_wplua_isgvalue_userdata (L, idx, type))) {
wp_critical ("expected userdata storing GValue<%s>", g_type_name (type));
luaL_argerror (L, idx, "expected userdata storing GValue<GBoxed>");
}
return g_value_get_boxed ((GValue *) lua_touserdata (L, idx));
}
gboolean
wplua_isboxed (lua_State *L, int idx, GType type)
{
if (!g_type_is_a (type, G_TYPE_BOXED)) return FALSE;
return _wplua_isgvalue_userdata (L, idx, type);
}

View File

@@ -0,0 +1,181 @@
/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include "wplua.h"
#include "private.h"
#include <wp/wp.h>
/* This structure is added to a lua global and it's only referenced from there;
When the lua_State closes, it is unrefed and its finalize function below
invalidates all the closures so that nothing attempts to call into a lua
function after the state is closed */
typedef struct _WpLuaClosureStore WpLuaClosureStore;
struct _WpLuaClosureStore
{
GPtrArray *closures;
};
static WpLuaClosureStore *
_wplua_closure_store_new (void)
{
WpLuaClosureStore *self = g_rc_box_new (WpLuaClosureStore);
self->closures = g_ptr_array_new ();
return self;
}
static void
_wplua_closure_store_finalize (WpLuaClosureStore * self)
{
for (guint i = self->closures->len; i > 0; i--) {
GClosure *c = g_ptr_array_index (self->closures, i-1);
g_closure_ref (c);
g_closure_invalidate (c);
g_ptr_array_remove_index_fast (self->closures, i-1);
g_closure_unref (c);
}
g_ptr_array_unref (self->closures);
}
static WpLuaClosureStore *
_wplua_closure_store_ref (WpLuaClosureStore * self)
{
return g_rc_box_acquire (self);
}
static void
_wplua_closure_store_unref (WpLuaClosureStore * self)
{
g_rc_box_release_full (self, (GDestroyNotify) _wplua_closure_store_finalize);
}
G_DEFINE_BOXED_TYPE(WpLuaClosureStore, _wplua_closure_store,
_wplua_closure_store_ref, _wplua_closure_store_unref)
typedef struct _WpLuaClosure WpLuaClosure;
struct _WpLuaClosure
{
GClosure closure;
int func_ref;
GPtrArray *closures;
};
static void
_wplua_closure_marshal (GClosure *closure, GValue *return_value,
guint n_param_values, const GValue *param_values,
gpointer invocation_hint, gpointer marshal_data)
{
static int reentrant = 0;
lua_State *L = closure->data;
int func_ref = ((WpLuaClosure *) closure)->func_ref;
/* invalid closure, skip it */
if (func_ref == LUA_NOREF || func_ref == LUA_REFNIL)
return;
/* stop the garbage collector */
if (reentrant == 0)
lua_gc (L, LUA_GCSTOP, 0);
/* push the function */
lua_rawgeti (L, LUA_REGISTRYINDEX, func_ref);
/* push arguments */
for (guint i = 0; i < n_param_values; i++)
wplua_gvalue_to_lua (L, &param_values[i]);
/* call in protected mode */
reentrant++;
int res = _wplua_pcall (L, n_param_values, return_value ? 1 : 0);
reentrant--;
/* handle the result */
if (res == LUA_OK && return_value) {
wplua_lua_to_gvalue (L, -1, return_value);
lua_pop (L, 1);
}
/* clean up */
lua_gc (L, LUA_GCCOLLECT, 0);
if (reentrant == 0)
lua_gc (L, LUA_GCRESTART, 0);
}
static void
_wplua_closure_invalidate (lua_State *L, WpLuaClosure *c)
{
wp_trace_boxed (G_TYPE_CLOSURE, c, "invalidated");
luaL_unref (L, LUA_REGISTRYINDEX, c->func_ref);
c->func_ref = LUA_NOREF;
}
static void
_wplua_closure_finalize (lua_State *L, WpLuaClosure *c)
{
g_ptr_array_remove_fast (c->closures, c);
g_ptr_array_unref (c->closures);
}
GClosure *
wplua_checkclosure (lua_State *L, int idx)
{
luaL_checktype (L, idx, LUA_TFUNCTION);
return wplua_function_to_closure (L, idx);
}
/**
* wplua_function_to_closure:
*
* Make a GClosure out of a Lua function at index @em idx
*
* Returns: (transfer floating): the new closure
*/
GClosure *
wplua_function_to_closure (lua_State *L, int idx)
{
g_return_val_if_fail (lua_isfunction(L, idx), NULL);
GClosure *c = g_closure_new_simple (sizeof (WpLuaClosure), L);
WpLuaClosure *wlc = (WpLuaClosure *) c;
WpLuaClosureStore *store;
lua_pushvalue (L, idx);
wlc->func_ref = luaL_ref (L, LUA_REGISTRYINDEX);
wp_trace_boxed (G_TYPE_CLOSURE, c, "created, func_ref = %d", wlc->func_ref);
g_closure_set_marshal (c, _wplua_closure_marshal);
g_closure_add_invalidate_notifier (c, L,
(GClosureNotify) _wplua_closure_invalidate);
g_closure_add_finalize_notifier (c, L,
(GClosureNotify) _wplua_closure_finalize);
/* keep a weak ref of the closure in the store's array,
so that we can invalidate the closure when lua_State closes;
keep a strong ref of the array in the closure so that
_wplua_closure_finalize() works even after the state is closed */
lua_pushliteral (L, "wplua_closures");
lua_gettable (L, LUA_REGISTRYINDEX);
store = wplua_toboxed (L, -1);
lua_pop (L, 1);
g_ptr_array_add (store->closures, c);
wlc->closures = g_ptr_array_ref (store->closures);
return c;
}
void
_wplua_init_closure (lua_State *L)
{
lua_pushliteral (L, "wplua_closures");
wplua_pushboxed (L,
_wplua_closure_store_get_type (),
_wplua_closure_store_new ());
lua_settable (L, LUA_REGISTRYINDEX);
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/freedesktop/pipewire/wireplumber/wplua/">
<file compressed="true">sandbox.lua</file>
</gresource>
</gresources>

View File

@@ -0,0 +1,33 @@
wplua_lib_sources = [
'boxed.c',
'closure.c',
'object.c',
'userdata.c',
'value.c',
'wplua.c',
]
wplua_resources = gnome.compile_resources(
'wplua-resources',
'gresource.xml',
c_name: '_wplua',
extra_args: '--manual-register',
source_dir: meson.current_source_dir())
wplua_lib = static_library('wplua-' + wireplumber_api_version,
[ wplua_lib_sources, wplua_resources ],
c_args : [
'-D_GNU_SOURCE',
'-DG_LOG_USE_STRUCTURED',
'-DG_LOG_DOMAIN="wplua"',
],
install: false,
include_directories: wplua_include_dir,
dependencies : [wp_dep, lua_dep],
)
wplua_dep = declare_dependency(
link_with: wplua_lib,
include_directories: wplua_include_dir,
dependencies: [wp_dep, lua_dep],
)

View File

@@ -0,0 +1,246 @@
/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include "wplua.h"
#include "private.h"
#include <wp/wp.h>
static int
_wplua_gobject_call (lua_State *L)
{
GObject *obj = wplua_checkobject (L, 1, G_TYPE_OBJECT);
const char *sig_name = lua_tostring (L, 2);
guint n_params = lua_gettop (L) - 2;
GSignalQuery query;
guint sig_id = 0;
GQuark detail = 0;
if (G_UNLIKELY (!g_signal_parse_name (sig_name, G_TYPE_FROM_INSTANCE (obj),
&sig_id, &detail, FALSE)))
luaL_error (L, "unknown signal '%s::%s'", G_OBJECT_TYPE_NAME (obj),
sig_name);
g_signal_query (sig_id, &query);
if (G_UNLIKELY (!(query.signal_flags & G_SIGNAL_ACTION)))
luaL_error (L, "lua code is not allowed to emit non-action signal '%s::%s'",
G_OBJECT_TYPE_NAME (obj), sig_name);
if (G_UNLIKELY (query.n_params > n_params))
luaL_error (L, "not enough arguments for '%s::%s': expected %d, got %d",
G_OBJECT_TYPE_NAME (obj), sig_name, query.n_params, n_params);
GValue ret = G_VALUE_INIT;
GValue *vals = g_newa (GValue, n_params + 1);
memset (vals, 0, sizeof (GValue) * (n_params + 1));
if (query.return_type != G_TYPE_NONE)
g_value_init (&ret, query.return_type);
g_value_init_from_instance (&vals[0], obj);
for (guint i = 0; i < n_params; i++) {
g_value_init (&vals[i+1], query.param_types[i]);
wplua_lua_to_gvalue (L, i+3, &vals[i+1]);
}
g_signal_emitv (vals, sig_id, detail, &ret);
for (guint i = 0; i < n_params + 1; i++) {
g_value_unset (&vals[i]);
}
int n_ret = 0;
if (query.return_type != G_TYPE_NONE)
n_ret = wplua_gvalue_to_lua (L, &ret);
g_value_unset (&ret);
return n_ret;
}
static int
_wplua_gobject_connect (lua_State *L)
{
GObject *obj = wplua_checkobject (L, 1, G_TYPE_OBJECT);
const char *sig_name = luaL_checkstring (L, 2);
luaL_checktype (L, 3, LUA_TFUNCTION);
guint sig_id = 0;
GQuark detail = 0;
if (G_UNLIKELY (!g_signal_parse_name (sig_name, G_TYPE_FROM_INSTANCE (obj),
&sig_id, &detail, FALSE)))
luaL_error (L, "unknown signal '%s::%s'", G_OBJECT_TYPE_NAME (obj),
sig_name);
GClosure *closure = wplua_function_to_closure (L, 3);
gulong handler =
g_signal_connect_closure_by_id (obj, sig_id, detail, closure, FALSE);
lua_pushinteger (L, handler);
return 1;
}
static lua_CFunction
find_method_in_luaL_Reg (luaL_Reg *reg, const gchar *method)
{
if (reg) {
while (reg->name) {
if (!g_strcmp0 (method, reg->name))
return reg->func;
reg++;
}
}
return NULL;
}
static int
_wplua_gobject___index (lua_State *L)
{
GObject *obj = wplua_checkobject (L, 1, G_TYPE_OBJECT);
const gchar *key = luaL_checkstring (L, 2);
lua_CFunction func = NULL;
GHashTable *vtables;
lua_pushliteral (L, "wplua_vtables");
lua_gettable (L, LUA_REGISTRYINDEX);
vtables = wplua_toboxed (L, -1);
lua_pop (L, 1);
if (!g_strcmp0 (key, "call"))
func = _wplua_gobject_call;
else if (!g_strcmp0 (key, "connect"))
func = _wplua_gobject_connect;
/* search in registered vtables */
if (!func) {
GType type = G_TYPE_FROM_INSTANCE (obj);
while (!func && type) {
luaL_Reg *reg = g_hash_table_lookup (vtables, GUINT_TO_POINTER (type));
func = find_method_in_luaL_Reg (reg, key);
type = g_type_parent (type);
}
}
/* search in registered vtables of interfaces */
if (!func) {
g_autofree GType *interfaces =
g_type_interfaces (G_TYPE_FROM_INSTANCE (obj), NULL);
GType *type = interfaces;
while (!func && *type) {
luaL_Reg *reg = g_hash_table_lookup (vtables, GUINT_TO_POINTER (*type));
func = find_method_in_luaL_Reg (reg, key);
type++;
}
}
if (func) {
lua_pushcfunction (L, func);
return 1;
}
else {
/* search in properties */
GObjectClass *klass = G_OBJECT_GET_CLASS (obj);
GParamSpec *pspec = g_object_class_find_property (klass, key);
if (pspec && (pspec->flags & G_PARAM_READABLE)) {
g_auto (GValue) v = G_VALUE_INIT;
g_value_init (&v, pspec->value_type);
g_object_get_property (obj, key, &v);
return wplua_gvalue_to_lua (L, &v);
}
}
return 0;
}
static int
_wplua_gobject___newindex (lua_State *L)
{
GObject *obj = wplua_checkobject (L, 1, G_TYPE_OBJECT);
const gchar *key = luaL_checkstring (L, 2);
/* search in properties */
GObjectClass *klass = G_OBJECT_GET_CLASS (obj);
GParamSpec *pspec = g_object_class_find_property (klass, key);
if (pspec && (pspec->flags & G_PARAM_WRITABLE)) {
g_auto (GValue) v = G_VALUE_INIT;
g_value_init (&v, pspec->value_type);
wplua_lua_to_gvalue (L, 3, &v);
g_object_set_property (obj, key, &v);
} else {
luaL_error (L, "attempted to assign unknown or non-writable property '%s'",
key);
}
return 0;
}
static int
_wplua_gobject__tostring (lua_State *L)
{
GObject *obj;
gchar *str;
obj = wplua_checkobject (L, 1, G_TYPE_OBJECT);
str = g_strdup_printf (WP_OBJECT_FORMAT, WP_OBJECT_ARGS (obj));
lua_pushstring (L, str);
g_free (str);
return 1;
}
void
_wplua_init_gobject (lua_State *L)
{
static const luaL_Reg gobject_meta[] = {
{ "__gc", _wplua_gvalue_userdata___gc },
{ "__eq", _wplua_gvalue_userdata___eq },
{ "__index", _wplua_gobject___index },
{ "__newindex", _wplua_gobject___newindex },
{ "__tostring", _wplua_gobject__tostring },
{ NULL, NULL }
};
luaL_newmetatable (L, "GObject");
luaL_setfuncs (L, gobject_meta, 0);
lua_pop (L, 1);
}
void
wplua_pushobject (lua_State * L, gpointer object)
{
g_return_if_fail (G_IS_OBJECT (object));
GValue *v = _wplua_pushgvalue_userdata (L, G_TYPE_FROM_INSTANCE (object));
wp_trace_object (object, "pushing to Lua, v=%p", v);
g_value_take_object (v, object);
luaL_getmetatable (L, "GObject");
lua_setmetatable (L, -2);
}
gpointer
wplua_toobject (lua_State *L, int idx)
{
g_return_val_if_fail (_wplua_isgvalue_userdata (L, idx, G_TYPE_OBJECT), NULL);
return g_value_get_object ((GValue *) lua_touserdata (L, idx));
}
gpointer
wplua_checkobject (lua_State *L, int idx, GType type)
{
if (G_UNLIKELY (!_wplua_isgvalue_userdata (L, idx, type))) {
wp_critical ("expected userdata storing GValue<%s>", g_type_name (type));
luaL_argerror (L, idx, "expected userdata storing GValue<GObject>");
}
return g_value_get_object ((GValue *) lua_touserdata (L, idx));
}
gboolean
wplua_isobject (lua_State *L, int idx, GType type)
{
if (!g_type_is_a (type, G_TYPE_OBJECT)) return FALSE;
return _wplua_isgvalue_userdata (L, idx, type);
}

View File

@@ -0,0 +1,37 @@
/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#ifndef __WPLUA_PRIVATE_H__
#define __WPLUA_PRIVATE_H__
#include "wplua.h"
G_BEGIN_DECLS
/* boxed.c */
void _wplua_init_gboxed (lua_State *L);
/* closure.c */
void _wplua_init_closure (lua_State *L);
/* object.c */
void _wplua_init_gobject (lua_State *L);
/* userdata.c */
GValue * _wplua_pushgvalue_userdata (lua_State * L, GType type);
gboolean _wplua_isgvalue_userdata (lua_State *L, int idx, GType type);
int _wplua_gvalue_userdata___gc (lua_State *L);
int _wplua_gvalue_userdata___eq (lua_State *L);
/* wplua.c */
int _wplua_pcall (lua_State *L, int nargs, int nret);
G_END_DECLS
#endif

View File

@@ -0,0 +1,111 @@
-- WirePlumber
--
-- Copyright © 2020 Collabora Ltd.
-- @author George Kiagiadakis <george.kiagiadakis@collabora.com>
--
-- Based on https://github.com/kikito/sandbox.lua
-- Copyright © 2013 Enrique García Cota
--
-- SPDX-License-Identifier: MIT
local SANDBOX_CONFIG = ...
local SANDBOX_ENV = {}
local function populate_env(id)
local module, method = id:match('([^%.]+)%.([^%.]+)')
if module then
SANDBOX_ENV[module] = SANDBOX_ENV[module] or {}
SANDBOX_ENV[module][method] = _G[module][method]
else
SANDBOX_ENV[id] = _G[id]
end
end
-- List of safe functions and packages
if SANDBOX_CONFIG["minimal_std"] then
-- minimal list, used for config files
([[
_VERSION ipairs pairs select tonumber tostring type
table
string.byte string.char string.find string.format string.gmatch
string.gsub string.len string.lower string.match string.reverse
string.sub string.upper
]]):gsub('%S+', populate_env)
else
-- full list, used for scripts
([[
_VERSION assert error ipairs next pairs print
pcall select tonumber tostring type xpcall
table utf8
math.abs math.acos math.asin math.atan math.ceil
math.cos math.deg math.exp math.tointeger math.floor math.fmod
math.huge math.ult math.log math.maxinteger math.mininteger math.max
math.min math.modf math.pi math.rad math.random
math.sin math.sqrt math.tan math.type
string.byte string.char string.find string.format string.gmatch
string.gsub string.len string.lower string.match string.reverse
string.sub string.upper
os.clock os.difftime os.time os.date os.getenv
]]):gsub('%S+', populate_env)
end
-- Additionally export everything in SANDBOX_EXPORT
if type(SANDBOX_EXPORT) == "table" then
for k, v in pairs(SANDBOX_EXPORT) do
SANDBOX_ENV[k] = v
end
end
-- Additionally protect packages from malicious scripts trying to override methods
for k, v in pairs(SANDBOX_ENV) do
if type(v) == "table" then
SANDBOX_ENV[k] = setmetatable({}, {
__index = v,
__newindex = function(_, attr_name, _)
error('Can not modify ' .. k .. '.' .. attr_name .. '. Protected by the sandbox.')
end
})
end
end
if SANDBOX_CONFIG["isolate_env"] then
-- in isolate_env mode, use a separate enviornment for each loaded chunk and
-- store all of them in a global table so that they are not garbage collected
SANDBOX_ENV_LIST = {}
function sandbox(chunk, ...)
-- chunk's environment will be an empty table with __index
-- to access our SANDBOX_ENV (without being able to write it)
local env = setmetatable({}, {
__index = SANDBOX_ENV,
})
-- store the chunk's environment so that it is not garbage collected
table.insert(SANDBOX_ENV_LIST, env)
-- set it as the chunk's 1st upvalue (__ENV)
debug.setupvalue(chunk, 1, env)
-- execute the chunk
chunk(...)
end
else
-- in common_env mode, use the same environment for all loaded chunks
-- chunk's environment will be an empty table with __index
-- to access our SANDBOX_ENV (without being able to write it)
SANDBOX_COMMON_ENV = setmetatable({}, {
__index = SANDBOX_ENV,
})
function sandbox(chunk, ...)
-- set it as the chunk's 1st upvalue (__ENV)
debug.setupvalue(chunk, 1, SANDBOX_COMMON_ENV)
-- execute the chunk
chunk(...)
end
end

View File

@@ -0,0 +1,78 @@
/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include "wplua.h"
#include "private.h"
#include <wp/wp.h>
GValue *
_wplua_pushgvalue_userdata (lua_State * L, GType type)
{
GValue *v = lua_newuserdata (L, sizeof (GValue));
memset (v, 0, sizeof (GValue));
g_value_init (v, type);
return v;
}
gboolean
_wplua_isgvalue_userdata (lua_State *L, int idx, GType type)
{
GValue *v;
if (!lua_isuserdata (L, idx))
return FALSE;
if (lua_rawlen (L, idx) != sizeof (GValue))
return FALSE;
if (!(v = lua_touserdata (L, idx)))
return FALSE;
if (type != G_TYPE_NONE && !g_type_is_a (G_VALUE_TYPE (v), type))
return FALSE;
return TRUE;
}
GType
wplua_gvalue_userdata_type (lua_State *L, int idx)
{
GValue *v;
if (!lua_isuserdata (L, idx))
return G_TYPE_INVALID;
if (lua_rawlen (L, idx) != sizeof (GValue))
return G_TYPE_INVALID;
if (!(v = lua_touserdata (L, idx)))
return G_TYPE_INVALID;
return G_VALUE_TYPE (v);
}
int
_wplua_gvalue_userdata___gc (lua_State *L)
{
GValue *v = lua_touserdata (L, 1);
wp_trace_boxed (G_VALUE_TYPE (v), g_value_peek_pointer (v),
"collected, v=%p", v);
g_value_unset (v);
return 0;
}
int
_wplua_gvalue_userdata___eq (lua_State *L)
{
if (_wplua_isgvalue_userdata (L, 1, G_TYPE_NONE) &&
_wplua_isgvalue_userdata (L, 2, G_TYPE_NONE)) {
GValue *v1 = lua_touserdata (L, 1);
GValue *v2 = lua_touserdata (L, 2);
gpointer p1 = g_value_peek_pointer (v1);
gpointer p2 = g_value_peek_pointer (v2);
lua_pushboolean (L, (p1 == p2));
} else {
lua_pushboolean (L, FALSE);
}
return 1;
}

View File

@@ -0,0 +1,342 @@
/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include "wplua.h"
#include "private.h"
#include <wp/wp.h>
WpProperties *
wplua_table_to_properties (lua_State *L, int idx)
{
WpProperties *p = wp_properties_new_empty ();
const gchar *key, *value;
int table = lua_absindex (L, idx);
lua_pushnil(L);
while (lua_next (L, table) != 0) {
/* copy key & value to convert them to string */
key = luaL_tolstring (L, -2, NULL);
value = luaL_tolstring (L, -2, NULL);
wp_properties_set (p, key, value);
lua_pop (L, 3);
}
/* sort, because the lua table has a random order and it's too messy to read */
wp_properties_sort (p);
return p;
}
void
wplua_properties_to_table (lua_State *L, WpProperties *p)
{
lua_newtable (L);
if (p) {
g_autoptr (WpIterator) it = wp_properties_new_iterator (p);
GValue v = G_VALUE_INIT;
const gchar *key, *value;
while (wp_iterator_next (it, &v)) {
WpPropertiesItem *pi = g_value_get_boxed (&v);
key = wp_properties_item_get_key (pi);
value = wp_properties_item_get_value (pi);
lua_pushstring (L, key);
lua_pushstring (L, value);
lua_settable (L, -3);
g_value_unset (&v);
}
}
}
GVariant *
wplua_lua_to_gvariant (lua_State *L, int idx)
{
switch (lua_type (L, idx)) {
case LUA_TNIL:
return g_variant_new ("()");
case LUA_TBOOLEAN:
return g_variant_new_boolean (lua_toboolean (L, idx));
case LUA_TNUMBER:
if (lua_isinteger (L, idx))
return g_variant_new_int64 (lua_tointeger (L, idx));
else
return g_variant_new_double (lua_tonumber (L, idx));
case LUA_TSTRING:
return g_variant_new_string (lua_tostring (L, idx));
case LUA_TTABLE: {
GVariantBuilder b = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
const gchar *key;
int table = lua_absindex (L, idx);
lua_pushnil (L);
while (lua_next (L, table) != 0) {
/* copy key to convert it to string */
lua_pushvalue (L, -2);
key = lua_tostring (L, -1);
g_variant_builder_add (&b, "{sv}", key, wplua_lua_to_gvariant (L, -2));
lua_pop (L, 2);
}
return g_variant_builder_end (&b);
}
default:
wp_warning ("skipping bad value (its type cannot be represented in GVariant)");
return NULL;
}
}
void
wplua_gvariant_to_lua (lua_State *L, GVariant *variant)
{
if (variant == NULL || g_variant_is_of_type (variant, G_VARIANT_TYPE_UNIT)) {
lua_pushnil (L);
}
else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_INT16)) {
lua_pushinteger (L, g_variant_get_int16 (variant));
}
else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_INT32)) {
lua_pushinteger (L, g_variant_get_int32 (variant));
}
else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_INT64)) {
lua_pushinteger (L, g_variant_get_int64 (variant));
}
else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_UINT16)) {
lua_pushinteger (L, g_variant_get_uint16 (variant));
}
else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_UINT32)) {
lua_pushinteger (L, g_variant_get_uint32 (variant));
}
else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_UINT64)) {
lua_pushinteger (L, g_variant_get_uint64 (variant));
}
else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_DOUBLE)) {
lua_pushnumber (L, g_variant_get_double (variant));
}
else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_BOOLEAN)) {
lua_pushboolean (L, g_variant_get_boolean (variant));
}
else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING)) {
lua_pushstring (L, g_variant_get_string (variant, NULL));
}
else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARIANT)) {
g_autoptr (GVariant) v = g_variant_get_variant (variant);
wplua_gvariant_to_lua (L, v);
}
else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_DICTIONARY)) {
gsize n_children, i;
n_children = g_variant_n_children (variant);
lua_createtable (L, 0, n_children);
for (i = 0; i < n_children; i++) {
g_autoptr (GVariant) key, value;
g_variant_get_child (variant, i, "{@?@*}", &key, &value);
wplua_gvariant_to_lua (L, key);
/* if the key is a string convertible to integer, convert it */
if (lua_type (L, -1) == LUA_TSTRING) {
int isnum = 0;
lua_Integer num = lua_tointegerx (L, -1, &isnum);
if (isnum) {
lua_pop (L, 1);
lua_pushinteger (L, num);
}
}
wplua_gvariant_to_lua (L, value);
lua_settable (L, -3);
}
}
else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_ARRAY)) {
gsize n_children, i;
n_children = g_variant_n_children (variant);
lua_createtable (L, n_children, 0);
for (i = 0; i < n_children; i++) {
g_autoptr (GVariant) value;
value = g_variant_get_child_value (variant, i);
wplua_gvariant_to_lua (L, value);
lua_seti (L, -2, i + 1);
}
}
else {
g_autofree gchar *type_name =
g_variant_type_dup_string (g_variant_get_type (variant));
wp_warning ("Unhandled GVariant type %s", type_name);
lua_pushnil (L);
}
}
gint
wplua_lua_to_enum (lua_State *L, int idx, GType enum_type)
{
if (lua_type (L, idx) == LUA_TSTRING) {
g_autoptr (GEnumClass) klass = g_type_class_ref (enum_type);
GEnumValue *value = g_enum_get_value_by_nick (klass, lua_tostring (L, idx));
if (value)
return value->value;
else
luaL_error (L, "Invalid enum value '%s'", lua_tostring (L, idx));
}
return lua_tointeger (L, idx);
}
void
wplua_enum_to_lua (lua_State *L, gint enum_val, GType enum_type)
{
g_autoptr (GEnumClass) klass = g_type_class_ref (enum_type);
GEnumValue *value = g_enum_get_value (klass, enum_val);
if (value)
lua_pushstring (L, value->value_nick);
else
lua_pushinteger (L, enum_val);
}
void
wplua_lua_to_gvalue (lua_State *L, int idx, GValue *v)
{
switch (g_type_fundamental (G_VALUE_TYPE (v))) {
case G_TYPE_CHAR:
if (lua_type (L, idx) == LUA_TSTRING)
g_value_set_schar (v, *lua_tostring (L, idx));
else
g_value_set_schar (v, lua_tointeger (L, idx));
break;
case G_TYPE_UCHAR:
g_value_set_uchar (v, lua_tointeger (L, idx));
break;
case G_TYPE_INT:
g_value_set_int (v, lua_tointeger (L, idx));
break;
case G_TYPE_UINT:
g_value_set_uint (v, lua_tointeger (L, idx));
break;
case G_TYPE_LONG:
g_value_set_long (v, lua_tointeger (L, idx));
break;
case G_TYPE_ULONG:
g_value_set_ulong (v, lua_tointeger (L, idx));
break;
case G_TYPE_INT64:
g_value_set_int64 (v, lua_tointeger (L, idx));
break;
case G_TYPE_UINT64:
g_value_set_uint64 (v, lua_tonumber (L, idx));
break;
case G_TYPE_FLOAT:
g_value_set_float (v, lua_tonumber (L, idx));
break;
case G_TYPE_DOUBLE:
g_value_set_double (v, lua_tonumber (L, idx));
break;
case G_TYPE_BOOLEAN:
g_value_set_boolean (v, lua_toboolean (L, idx));
break;
case G_TYPE_STRING:
g_value_set_string (v, lua_tostring (L, idx));
break;
case G_TYPE_POINTER:
if (lua_type (L, idx) == LUA_TLIGHTUSERDATA)
g_value_set_pointer (v, lua_touserdata (L, idx));
break;
case G_TYPE_BOXED:
if (_wplua_isgvalue_userdata (L, idx, G_VALUE_TYPE (v)))
g_value_set_boxed (v, wplua_toboxed (L, idx));
/* table -> WpProperties */
else if (lua_istable (L, idx) && G_VALUE_TYPE (v) == WP_TYPE_PROPERTIES)
g_value_take_boxed (v, wplua_table_to_properties (L, idx));
break;
case G_TYPE_OBJECT:
case G_TYPE_INTERFACE:
if (_wplua_isgvalue_userdata (L, idx, G_VALUE_TYPE (v)))
g_value_set_object (v, wplua_toobject (L, idx));
break;
case G_TYPE_ENUM:
g_value_set_enum (v, wplua_lua_to_enum (L, idx, G_VALUE_TYPE (v)));
break;
case G_TYPE_FLAGS:
g_value_set_flags (v, lua_tointeger (L, idx));
break;
case G_TYPE_VARIANT:
g_value_set_variant (v, wplua_lua_to_gvariant (L, idx));
break;
default:
break;
}
}
int
wplua_gvalue_to_lua (lua_State *L, const GValue *v)
{
switch (g_type_fundamental (G_VALUE_TYPE (v))) {
case G_TYPE_CHAR:
lua_pushinteger (L, g_value_get_schar (v));
break;
case G_TYPE_UCHAR:
lua_pushinteger (L, g_value_get_uchar (v));
break;
case G_TYPE_INT:
lua_pushinteger (L, g_value_get_int (v));
break;
case G_TYPE_UINT:
lua_pushinteger (L, g_value_get_uint (v));
break;
case G_TYPE_LONG:
lua_pushinteger (L, g_value_get_long (v));
break;
case G_TYPE_ULONG:
lua_pushinteger (L, g_value_get_ulong (v));
break;
case G_TYPE_INT64:
lua_pushinteger (L, g_value_get_int64 (v));
break;
case G_TYPE_UINT64:
lua_pushnumber (L, g_value_get_uint64 (v));
break;
case G_TYPE_FLOAT:
lua_pushnumber (L, g_value_get_float (v));
break;
case G_TYPE_DOUBLE:
lua_pushnumber (L, g_value_get_double (v));
break;
case G_TYPE_BOOLEAN:
lua_pushboolean (L, g_value_get_boolean (v));
break;
case G_TYPE_STRING:
lua_pushstring (L, g_value_get_string (v));
break;
case G_TYPE_POINTER:
lua_pushlightuserdata (L, g_value_get_pointer (v));
break;
case G_TYPE_BOXED:
if (G_VALUE_TYPE (v) == WP_TYPE_PROPERTIES)
wplua_properties_to_table (L, g_value_get_boxed (v));
else
wplua_pushboxed (L, G_VALUE_TYPE (v), g_value_dup_boxed (v));
break;
case G_TYPE_OBJECT:
case G_TYPE_INTERFACE:
wplua_pushobject (L, g_value_dup_object (v));
break;
case G_TYPE_ENUM:
wplua_enum_to_lua (L, g_value_get_enum (v), G_VALUE_TYPE (v));
break;
case G_TYPE_FLAGS:
/* FIXME: push as userdata with methods */
lua_pushinteger (L, g_value_get_flags (v));
break;
case G_TYPE_PARAM: {
GParamSpec *pspec = g_value_get_param (v);
lua_pushstring (L, pspec->name);
break;
}
case G_TYPE_VARIANT: {
GVariant *variant = g_value_get_variant (v);
wplua_gvariant_to_lua (L, variant);
break;
}
default:
lua_pushnil (L);
break;
}
return 1;
}

View File

@@ -0,0 +1,278 @@
/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include "wplua.h"
#include "private.h"
#include <wp/wp.h>
#define URI_SANDBOX "resource:///org/freedesktop/pipewire/wireplumber/wplua/sandbox.lua"
extern void _wplua_register_resource (void);
G_DEFINE_QUARK (wplua, wp_domain_lua);
static void
_wplua_openlibs (lua_State *L)
{
/* http://www.lua.org/manual/5.3/manual.html#luaL_requiref
* http://www.lua.org/source/5.3/linit.c.html */
static const luaL_Reg loadedlibs[] = {
{"_G", luaopen_base},
/* {LUA_LOADLIBNAME, luaopen_package}, */
/* {LUA_COLIBNAME, luaopen_coroutine}, */
{LUA_TABLIBNAME, luaopen_table},
/* {LUA_IOLIBNAME, luaopen_io}, */
{LUA_OSLIBNAME, luaopen_os},
{LUA_STRLIBNAME, luaopen_string},
{LUA_MATHLIBNAME, luaopen_math},
{LUA_UTF8LIBNAME, luaopen_utf8},
{LUA_DBLIBNAME, luaopen_debug},
{NULL, NULL}
};
const luaL_Reg *lib;
for (lib = loadedlibs; lib->func; lib++) {
luaL_requiref (L, lib->name, lib->func, 1);
lua_pop (L, 1);
}
}
static int
_wplua_errhandler (lua_State *L)
{
luaL_traceback (L, L, NULL, 1);
wp_warning ("%s\n%s", lua_tostring (L, -2), lua_tostring (L, -1));
lua_pop (L, 2);
return 0;
}
int
_wplua_pcall (lua_State *L, int nargs, int nret)
{
int hpos = lua_gettop (L) - nargs;
int ret = LUA_OK;
lua_pushcfunction (L, _wplua_errhandler);
lua_insert (L, hpos);
ret = lua_pcall (L, nargs, nret, hpos);
switch (ret) {
case LUA_ERRMEM:
wp_critical ("not enough memory");
break;
case LUA_ERRERR:
wp_critical ("error running the message handler");
break;
default:
break;
}
lua_remove (L, hpos);
return ret;
}
lua_State *
wplua_new (void)
{
static gboolean resource_registered = FALSE;
lua_State *L = luaL_newstate ();
wp_debug ("initializing lua_State %p", L);
if (!resource_registered) {
_wplua_register_resource ();
resource_registered = TRUE;
}
_wplua_openlibs (L);
_wplua_init_gboxed (L);
_wplua_init_gobject (L);
_wplua_init_closure (L);
{
GHashTable *t = g_hash_table_new (g_direct_hash, g_direct_equal);
lua_pushliteral (L, "wplua_vtables");
wplua_pushboxed (L, G_TYPE_HASH_TABLE, t);
lua_settable (L, LUA_REGISTRYINDEX);
}
return L;
}
void
wplua_free (lua_State * L)
{
wp_debug ("closing lua_State %p", L);
lua_close (L);
}
void
wplua_enable_sandbox (lua_State * L, WpLuaSandboxFlags flags)
{
g_autoptr (GError) error = NULL;
wp_debug ("enabling Lua sandbox");
lua_newtable (L);
lua_pushliteral (L, "minimal_std");
lua_pushboolean (L, (flags & WP_LUA_SANDBOX_MINIMAL_STD));
lua_settable (L, -3);
lua_pushliteral (L, "isolate_env");
lua_pushboolean (L, (flags & WP_LUA_SANDBOX_ISOLATE_ENV));
lua_settable (L, -3);
if (!wplua_load_uri (L, URI_SANDBOX, 1, 0, &error)) {
wp_critical ("Failed to load sandbox: %s", error->message);
}
}
void
wplua_register_type_methods (lua_State * L, GType type,
lua_CFunction constructor, const luaL_Reg * methods)
{
g_return_if_fail (L != NULL);
g_return_if_fail (G_TYPE_FUNDAMENTAL (type) == G_TYPE_OBJECT ||
G_TYPE_FUNDAMENTAL (type) == G_TYPE_BOXED ||
G_TYPE_FUNDAMENTAL (type) == G_TYPE_INTERFACE);
/* register methods */
if (methods) {
GHashTable *vtables;
lua_pushliteral (L, "wplua_vtables");
lua_gettable (L, LUA_REGISTRYINDEX);
vtables = wplua_toboxed (L, -1);
lua_pop (L, 1);
wp_debug ("Registering methods for '%s'", g_type_name (type));
if (G_UNLIKELY (g_hash_table_contains (vtables, GUINT_TO_POINTER (type)))) {
wp_critical ("type '%s' was already registered", g_type_name (type));
return;
}
g_hash_table_insert (vtables, GUINT_TO_POINTER (type), (gpointer) methods);
}
/* register constructor */
if (constructor) {
luaL_Buffer b;
wp_debug ("Registering class for '%s'", g_type_name (type));
luaL_buffinit (L, &b);
luaL_addstring (&b, g_type_name (type));
luaL_addchar (&b, '_');
luaL_addstring (&b, "new");
luaL_pushresult (&b);
lua_pushcfunction (L, constructor);
lua_setglobal (L, lua_tostring (L, -2));
lua_pop (L, 1);
}
}
static gboolean
_wplua_load_buffer (lua_State * L, const gchar *buf, gsize size,
const gchar * name, int nargs, int nres, GError **error)
{
int ret;
int sandbox = 0;
int args_top = lua_gettop (L);
/* wrap with sandbox() if it's loaded */
if (lua_getglobal (L, "sandbox") == LUA_TFUNCTION)
sandbox = 1;
else
lua_pop (L, 1);
/* skip shebang, if present */
if (g_str_has_prefix (buf, "#!/")) {
const char *tmp = strchr (buf, '\n');
size -= (tmp - buf);
buf = tmp;
}
ret = luaL_loadbuffer (L, buf, size, name);
if (ret != LUA_OK) {
g_set_error (error, WP_DOMAIN_LUA, WP_LUA_ERROR_COMPILATION,
"Failed to compile: %s", lua_tostring (L, -1));
lua_pop (L, nargs + sandbox + 1);
return FALSE;
}
/* push sandbox() and the chunk below the arguments */
lua_rotate (L, args_top, -nargs);
ret = _wplua_pcall (L, nargs + sandbox, nres);
if (ret != LUA_OK) {
g_set_error (error, WP_DOMAIN_LUA, WP_LUA_ERROR_RUNTIME,
"Runtime error while loading '%s'", name);
return FALSE;
}
return TRUE;
}
gboolean
wplua_load_buffer (lua_State * L, const gchar *buf, gsize size,
int nargs, int nres, GError **error)
{
g_return_val_if_fail (L != NULL, FALSE);
g_return_val_if_fail (buf != NULL, FALSE);
g_return_val_if_fail (size != 0, FALSE);
g_autofree gchar *name =
g_strdup_printf ("buffer@%p;size=%" G_GSIZE_FORMAT, buf, size);
return _wplua_load_buffer (L, buf, size, name, nargs, nres, error);
}
gboolean
wplua_load_uri (lua_State * L, const gchar *uri, int nargs, int nres,
GError **error)
{
g_autoptr (GFile) file = NULL;
g_autoptr (GBytes) bytes = NULL;
g_autoptr (GError) err = NULL;
g_autofree gchar *name = NULL;
gconstpointer data;
gsize size;
g_return_val_if_fail (L != NULL, FALSE);
g_return_val_if_fail (uri != NULL, FALSE);
file = g_file_new_for_uri (uri);
if (!(bytes = g_file_load_bytes (file, NULL, NULL, &err))) {
g_propagate_prefixed_error (error, err, "Failed to load '%s':", uri);
err = NULL;
return FALSE;
}
name = g_path_get_basename (uri);
data = g_bytes_get_data (bytes, &size);
return _wplua_load_buffer (L, data, size, name, nargs, nres, error);
}
gboolean
wplua_load_path (lua_State * L, const gchar *path, int nargs, int nres,
GError **error)
{
g_autofree gchar *abs_path = NULL;
g_autofree gchar *uri = NULL;
g_return_val_if_fail (L != NULL, FALSE);
g_return_val_if_fail (path != NULL, FALSE);
if (!g_path_is_absolute (path)) {
g_autofree gchar *cwd = g_get_current_dir ();
abs_path = g_build_filename (cwd, path, NULL);
}
if (!(uri = g_filename_to_uri (abs_path ? abs_path : path, NULL, error)))
return FALSE;
return wplua_load_uri (L, uri, nargs, nres, error);
}

View File

@@ -0,0 +1,100 @@
/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#ifndef __WPLUA_H__
#define __WPLUA_H__
#include <wp/wp.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
G_BEGIN_DECLS
/**
* WP_DOMAIN_LUA:
*
* @brief A <a href="https://developer.gnome.org/glib/stable/glib-Error-Reporting.html#GError">
* GError</a> domain for errors that occurred within the context of the
* WirePlumber lua library.
*/
#define WP_DOMAIN_LUA (wp_domain_lua_quark ())
GQuark wp_domain_lua_quark (void);
/**
* WpLuaError:
*
* @brief
* @em WP_LUA_ERROR_COMPILATION: a compilation error, i.e. invalid Lua code
* @em WP_LUA_ERROR_RUNTIME: a runtime error, i.e. misbehaving Lua code
*
* Error codes that can appear in a
* <a href="https://developer.gnome.org/glib/stable/glib-Error-Reporting.html#GError">
* GError</a> when the error domain is %WP_DOMAIN_LUA
*/
typedef enum {
WP_LUA_ERROR_COMPILATION,
WP_LUA_ERROR_RUNTIME,
} WpLuaError;
typedef enum {
WP_LUA_SANDBOX_MINIMAL_STD,
WP_LUA_SANDBOX_ISOLATE_ENV,
} WpLuaSandboxFlags;
lua_State * wplua_new (void);
void wplua_free (lua_State * L);
void wplua_enable_sandbox (lua_State * L, WpLuaSandboxFlags flags);
void wplua_register_type_methods (lua_State * L, GType type,
lua_CFunction constructor, const luaL_Reg * methods);
GType wplua_gvalue_userdata_type (lua_State *L, int idx);
/* push -> transfer full; get -> transfer none */
void wplua_pushobject (lua_State * L, gpointer object);
gpointer wplua_toobject (lua_State *L, int idx);
gpointer wplua_checkobject (lua_State *L, int idx, GType type);
gboolean wplua_isobject (lua_State *L, int idx, GType type);
/* push -> transfer full; get -> transfer none */
void wplua_pushboxed (lua_State * L, GType type, gpointer object);
gpointer wplua_toboxed (lua_State *L, int idx);
gpointer wplua_checkboxed (lua_State *L, int idx, GType type);
gboolean wplua_isboxed (lua_State *L, int idx, GType type);
/* transfer floating */
GClosure * wplua_checkclosure (lua_State *L, int idx);
GClosure * wplua_function_to_closure (lua_State *L, int idx);
void wplua_enum_to_lua (lua_State *L, gint enum_val, GType enum_type);
gint wplua_lua_to_enum (lua_State *L, int idx, GType enum_type);
void wplua_lua_to_gvalue (lua_State *L, int idx, GValue *v);
int wplua_gvalue_to_lua (lua_State *L, const GValue *v);
GVariant * wplua_lua_to_gvariant (lua_State *L, int idx);
void wplua_gvariant_to_lua (lua_State *L, GVariant *p);
WpProperties * wplua_table_to_properties (lua_State *L, int idx);
void wplua_properties_to_table (lua_State *L, WpProperties *p);
gboolean wplua_load_buffer (lua_State * L, const gchar *buf, gsize size,
int nargs, int nres, GError **error);
gboolean wplua_load_uri (lua_State * L, const gchar *uri, int nargs, int nres,
GError **error);
gboolean wplua_load_path (lua_State * L, const gchar *path, int nargs, int nres,
GError **error);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(lua_State, wplua_free)
G_END_DECLS
#endif