From 25b9b4b827f74482743689386b195dca3b81a0df Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Sat, 5 May 2012 22:37:46 +0200 Subject: [PATCH] implement Lua binding --- CMakeLists.txt | 4 + lua/CMakeLists.txt | 51 +++++++ lua/ubus.c | 395 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 450 insertions(+) create mode 100644 lua/CMakeLists.txt create mode 100644 lua/ubus.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 29809cc..9057b3c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,8 @@ cmake_minimum_required(VERSION 2.6) PROJECT(ubus C) ADD_DEFINITIONS(-Os -Wall -Werror --std=gnu99 -g3) +OPTION(BUILD_LUA "build Lua plugin" ON) + SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") IF(APPLE) @@ -25,6 +27,8 @@ TARGET_LINK_LIBRARIES(ubus-example ubus ubox) SET(CMAKE_INSTALL_PREFIX /usr) +ADD_SUBDIRECTORY(lua) + INSTALL(TARGETS ubus cli LIBRARY DESTINATION lib RUNTIME DESTINATION bin diff --git a/lua/CMakeLists.txt b/lua/CMakeLists.txt new file mode 100644 index 0000000..6c22bfc --- /dev/null +++ b/lua/CMakeLists.txt @@ -0,0 +1,51 @@ +cmake_minimum_required(VERSION 2.6) + +PROJECT(ubus C) + +SET(CMAKE_INSTALL_PREFIX /) + +IF(NOT LUA_CFLAGS) + FIND_PROGRAM(PKG_CONFIG pkg-config) + IF(PKG_CONFIG) + EXECUTE_PROCESS( + COMMAND pkg-config --silence-errors --cflags lua5.1 + OUTPUT_VARIABLE LUA_CFLAGS + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + ENDIF() +ENDIF() + +ADD_DEFINITIONS(-Os -Wall -Werror --std=gnu99 -g3 -I.. ${LUA_CFLAGS}) +LINK_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/..) + +IF(APPLE) + SET(CMAKE_SHARED_MODULE_CREATE_C_FLAGS "${CMAKE_SHARED_MODULE_CREATE_C_FLAGS} -undefined dynamic_lookup") +ENDIF(APPLE) + +IF(NOT LUAPATH) + EXECUTE_PROCESS( + COMMAND lua -e "for k in string.gmatch(package.cpath .. \";\", \"([^;]+)/..so;\") do if k:sub(1,1) == \"/\" then print(k) break end end" + OUTPUT_VARIABLE LUAPATH + RESULT_VARIABLE LUA_CHECK_RES + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + IF(BUILD_LUA) + IF(NOT ${LUA_CHECK_RES} EQUAL 0 OR ${LUAPATH} EQUAL "") + MESSAGE(SEND_ERROR "Lua was not found on your system") + ENDIF() + ENDIF() +ENDIF() + +IF(BUILD_LUA) + ADD_LIBRARY(ubus_lua MODULE ubus.c) + SET_TARGET_PROPERTIES(ubus_lua PROPERTIES + OUTPUT_NAME ubus + PREFIX "" + ) + TARGET_LINK_LIBRARIES(ubus_lua ubus) + + INSTALL(TARGETS ubus_lua + LIBRARY DESTINATION ${LUAPATH} + ) +ENDIF() diff --git a/lua/ubus.c b/lua/ubus.c new file mode 100644 index 0000000..85c1266 --- /dev/null +++ b/lua/ubus.c @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2012 Jo-Philipp Wich + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include + + +#define MODNAME "ubus" +#define METANAME MODNAME ".meta" + + +struct ubus_lua_connection { + int timeout; + struct blob_buf buf; + struct ubus_context *ctx; +}; + + +static int +ubus_lua_parse_blob(lua_State *L, struct blob_attr *attr, bool table); + +static int +ubus_lua_parse_blob_array(lua_State *L, struct blob_attr *attr, int len, bool table) +{ + int rv; + int idx = 1; + int rem = len; + struct blob_attr *pos; + + lua_newtable(L); + + __blob_for_each_attr(pos, attr, rem) + { + rv = ubus_lua_parse_blob(L, pos, table); + + if (rv > 1) + lua_rawset(L, -3); + else if (rv > 0) + lua_rawseti(L, -2, idx++); + } + + return 1; +} + +static int +ubus_lua_parse_blob(lua_State *L, struct blob_attr *attr, bool table) +{ + int len; + int off = 0; + void *data; + char buf[32]; + + if (!blobmsg_check_attr(attr, false)) + return 0; + + if (table && blobmsg_name(attr)[0]) + { + lua_pushstring(L, blobmsg_name(attr)); + off++; + } + + data = blobmsg_data(attr); + len = blobmsg_data_len(attr); + + switch (blob_id(attr)) + { + case BLOBMSG_TYPE_BOOL: + lua_pushboolean(L, *(uint8_t *)data); + break; + + case BLOBMSG_TYPE_INT16: + lua_pushinteger(L, be16_to_cpu(*(uint16_t *)data)); + break; + + case BLOBMSG_TYPE_INT32: + lua_pushinteger(L, be32_to_cpu(*(uint32_t *)data)); + break; + + case BLOBMSG_TYPE_INT64: + /* NB: Lua cannot handle 64bit, format value as string and push that */ + sprintf(buf, "%lld", (long long int) be64_to_cpu(*(uint64_t *)data)); + lua_pushstring(L, buf); + break; + + case BLOBMSG_TYPE_STRING: + lua_pushstring(L, data); + break; + + case BLOBMSG_TYPE_ARRAY: + ubus_lua_parse_blob_array(L, data, len, false); + break; + + case BLOBMSG_TYPE_TABLE: + ubus_lua_parse_blob_array(L, data, len, true); + break; + + default: + lua_pushnil(L); + break; + } + + return off + 1; +} + + +static bool +ubus_lua_format_blob_is_array(lua_State *L) +{ + lua_Integer prv = 0; + lua_Integer cur = 0; + + /* Find out whether table is array-like */ + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) + { + if (lua_type(L, -2) != LUA_TNUMBER && lua_type(L, -2) != LUA_TINT) + { + lua_pop(L, 1); + return false; + } + + cur = lua_tointeger(L, -2); + + if ((cur - 1) != prv) + { + lua_pop(L, 1); + return false; + } + + prv = cur; + } + + return true; +} + +static int +ubus_lua_format_blob_array(lua_State *L, struct blob_buf *b, bool table); + +static int +ubus_lua_format_blob(lua_State *L, struct blob_buf *b, bool table) +{ + void *c; + bool rv = true; + const char *key = table ? lua_tostring(L, -2) : NULL; + + switch (lua_type(L, -1)) + { + case LUA_TBOOLEAN: + blobmsg_add_u8(b, key, (uint8_t)lua_toboolean(L, -1)); + break; + + case LUA_TINT: + case LUA_TNUMBER: + blobmsg_add_u32(b, key, (uint32_t)lua_tointeger(L, -1)); + break; + + case LUA_TSTRING: + case LUA_TUSERDATA: + case LUA_TLIGHTUSERDATA: + blobmsg_add_string(b, key, lua_tostring(L, -1)); + break; + + case LUA_TTABLE: + if (ubus_lua_format_blob_is_array(L)) + { + c = blobmsg_open_array(b, key); + rv = ubus_lua_format_blob_array(L, b, false); + blobmsg_close_array(b, c); + } + else + { + c = blobmsg_open_table(b, key); + rv = ubus_lua_format_blob_array(L, b, true); + blobmsg_close_table(b, c); + } + break; + + default: + rv = false; + break; + } + + return rv; +} + +static int +ubus_lua_format_blob_array(lua_State *L, struct blob_buf *b, bool table) +{ + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) + { + if (!ubus_lua_format_blob(L, b, table)) + { + lua_pop(L, 1); + return false; + } + } + + return true; +} + + +static int +ubus_lua_connect(lua_State *L) +{ + struct ubus_lua_connection *c; + const char *sockpath = luaL_optstring(L, 1, NULL); + int timeout = luaL_optint(L, 2, 30); + + if ((c = lua_newuserdata(L, sizeof(*c))) != NULL && + (c->ctx = ubus_connect(sockpath)) != NULL) + { + c->timeout = timeout; + memset(&c->buf, 0, sizeof(c->buf)); + luaL_getmetatable(L, METANAME); + lua_setmetatable(L, -2); + return 1; + } + + /* NB: no errors from ubus_connect() yet */ + lua_pushnil(L); + lua_pushinteger(L, UBUS_STATUS_UNKNOWN_ERROR); + return 2; +} + + +static void +ubus_lua_objects_cb(struct ubus_context *c, struct ubus_object_data *o, void *p) +{ + lua_State *L = (lua_State *)p; + + lua_pushstring(L, o->path); + lua_rawseti(L, -2, lua_objlen(L, -2) + 1); +} + +static int +ubus_lua_objects(lua_State *L) +{ + int rv; + struct ubus_lua_connection *c = luaL_checkudata(L, 1, METANAME); + + lua_newtable(L); + rv = ubus_lookup(c->ctx, NULL, ubus_lua_objects_cb, L); + + if (rv != UBUS_STATUS_OK) + { + lua_pop(L, 1); + lua_pushnil(L); + lua_pushinteger(L, rv); + return 2; + } + + return 1; +} + + +static void +ubus_lua_signatures_cb(struct ubus_context *c, struct ubus_object_data *o, void *p) +{ + lua_State *L = (lua_State *)p; + + if (!o->signature) + return; + + ubus_lua_parse_blob_array(L, blob_data(o->signature), blob_len(o->signature), true); +} + +static int +ubus_lua_signatures(lua_State *L) +{ + int rv; + struct ubus_lua_connection *c = luaL_checkudata(L, 1, METANAME); + const char *path = luaL_checkstring(L, 2); + + rv = ubus_lookup(c->ctx, path, ubus_lua_signatures_cb, L); + + if (rv != UBUS_STATUS_OK) + { + lua_pop(L, 1); + lua_pushnil(L); + lua_pushinteger(L, rv); + return 2; + } + + return 1; +} + + +static void +ubus_lua_call_cb(struct ubus_request *req, int type, struct blob_attr *msg) +{ + lua_State *L = (lua_State *)req->priv; + + if (!msg) + lua_pushnil(L); + + ubus_lua_parse_blob_array(L, blob_data(msg), blob_len(msg), true); +} + +static int +ubus_lua_call(lua_State *L) +{ + int rv; + uint32_t id; + struct ubus_lua_connection *c = luaL_checkudata(L, 1, METANAME); + const char *path = luaL_checkstring(L, 2); + const char *func = luaL_checkstring(L, 3); + + luaL_checktype(L, 4, LUA_TTABLE); + blob_buf_init(&c->buf, 0); + + if (!ubus_lua_format_blob_array(L, &c->buf, true)) + { + lua_pushnil(L); + lua_pushinteger(L, UBUS_STATUS_INVALID_ARGUMENT); + return 2; + } + + rv = ubus_lookup_id(c->ctx, path, &id); + + if (rv) + { + lua_pushnil(L); + lua_pushinteger(L, rv); + return 2; + } + + rv = ubus_invoke(c->ctx, id, func, c->buf.head, ubus_lua_call_cb, L, c->timeout * 1000); + + if (rv != UBUS_STATUS_OK) + { + lua_pop(L, 1); + lua_pushnil(L); + lua_pushinteger(L, rv); + return 2; + } + + return 1; +} + + +static int +ubus_lua__gc(lua_State *L) +{ + struct ubus_lua_connection *c = luaL_checkudata(L, 1, METANAME); + + if (c->ctx != NULL) + { + ubus_free(c->ctx); + memset(c, 0, sizeof(*c)); + } + + return 0; +} + +static const luaL_Reg ubus[] = { + { "connect", ubus_lua_connect }, + { "objects", ubus_lua_objects }, + { "signatures", ubus_lua_signatures }, + { "call", ubus_lua_call }, + { "close", ubus_lua__gc }, + { "__gc", ubus_lua__gc }, + { NULL, NULL }, +}; + + +int +luaopen_ubus(lua_State *L) +{ + /* create metatable */ + luaL_newmetatable(L, METANAME); + + /* metatable.__index = metatable */ + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + + /* fill metatable */ + luaL_register(L, NULL, ubus); + lua_pop(L, 1); + + /* create module */ + luaL_register(L, MODNAME, ubus); + + return 0; +} -- 2.11.0