ucode: add ucode interpreter plugin
The rpcd ucode plugin allows utilizing ucode scripts to register ubus objects and to implement the objects method callbacks. Upon startup, rpcd will compile and execute each ucode script in `$INSTALL_PREFIX/share/ucode/` and register ubus proxy objects and methods definitions according to the signature returned by the script. Refer to examples/ucode/example-plugin.uc for details of the signature format. Signed-off-by: Jo-Philipp Wich <jo@mein.io>
This commit is contained in:
parent
9c6ba38287
commit
4c532bfed2
4 changed files with 1210 additions and 2 deletions
|
@ -9,6 +9,7 @@ INCLUDE_DIRECTORIES(include)
|
|||
OPTION(FILE_SUPPORT "File plugin support" ON)
|
||||
OPTION(IWINFO_SUPPORT "libiwinfo plugin support" ON)
|
||||
OPTION(RPCSYS_SUPPORT "rpc-sys plugin support" ON)
|
||||
OPTION(UCODE_SUPPORT "ucode plugin support" ON)
|
||||
|
||||
SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
|
||||
|
||||
|
@ -57,7 +58,7 @@ IF(RPCSYS_SUPPORT)
|
|||
SET_TARGET_PROPERTIES(rpcsys_plugin PROPERTIES OUTPUT_NAME rpcsys PREFIX "")
|
||||
ENDIF()
|
||||
|
||||
IF (IWINFO_SUPPORT)
|
||||
IF(IWINFO_SUPPORT)
|
||||
FIND_LIBRARY(iwinfo NAMES iwinfo)
|
||||
SET(PLUGINS ${PLUGINS} iwinfo_plugin)
|
||||
ADD_LIBRARY(iwinfo_plugin MODULE iwinfo.c)
|
||||
|
@ -65,6 +66,14 @@ IF (IWINFO_SUPPORT)
|
|||
SET_TARGET_PROPERTIES(iwinfo_plugin PROPERTIES OUTPUT_NAME iwinfo PREFIX "")
|
||||
ENDIF()
|
||||
|
||||
IF(UCODE_SUPPORT)
|
||||
FIND_LIBRARY(ucode NAMES ucode)
|
||||
SET(PLUGINS ${PLUGINS} ucode_plugin)
|
||||
ADD_LIBRARY(ucode_plugin MODULE ucode.c)
|
||||
TARGET_LINK_LIBRARIES(ucode_plugin ${ucode})
|
||||
SET_TARGET_PROPERTIES(ucode_plugin PROPERTIES OUTPUT_NAME ucode PREFIX "")
|
||||
ENDIF()
|
||||
|
||||
INSTALL(TARGETS rpcd ${PLUGINS}
|
||||
RUNTIME DESTINATION sbin
|
||||
LIBRARY DESTINATION lib/rpcd
|
||||
|
|
151
examples/ucode/example-plugin.uc
Normal file
151
examples/ucode/example-plugin.uc
Normal file
|
@ -0,0 +1,151 @@
|
|||
{%
|
||||
|
||||
'use strict';
|
||||
|
||||
let ubus = require('ubus').connect();
|
||||
|
||||
/*
|
||||
* An ucode plugin script is supposed to return a signature object on
|
||||
* invocation which describes the ubus objects the plugin script wants to
|
||||
* register, as well as the related methods and their argument signatures.
|
||||
*/
|
||||
return {
|
||||
/*
|
||||
* Each toplevel key of the returned signature object corresponds to the
|
||||
* name of an ubus object to register.
|
||||
*
|
||||
* The value of each key must be a nested dictionary describing the object
|
||||
* methods.
|
||||
*/
|
||||
example_object_1: {
|
||||
/*
|
||||
* Each key within the nested dictionary refers to the name of a method
|
||||
* to define for the parent object.
|
||||
*
|
||||
* The value of each method name key must be another nested dictionary
|
||||
* describing the method.
|
||||
*/
|
||||
method_1: {
|
||||
/*
|
||||
* At the very least, each method description dictionary *must*
|
||||
* contain a key "call" with the ucode callback function to call
|
||||
* when the corresponding ubus object method is invoked.
|
||||
*
|
||||
* The callback function is supposed to return a dictionary which
|
||||
* is automatically translated into a nested blobmsg structure and
|
||||
* sent back as ubus reply.
|
||||
*
|
||||
* Non-dictionary return values are discarded and the ubus method
|
||||
* call will fail with UBUS_STATUS_NO_DATA.
|
||||
*/
|
||||
call: function() {
|
||||
return { hello: "world" };
|
||||
}
|
||||
},
|
||||
|
||||
method_2: {
|
||||
/*
|
||||
* Additionally, method descriptions *may* supply an "args" key
|
||||
* referring to a dictionary describing the ubus method call
|
||||
* arguments accepted.
|
||||
*
|
||||
* The resulting method argument policy is also published to ubus
|
||||
* and visible with "ubus -v list".
|
||||
*
|
||||
* The callback function will receive a dictionary containing the
|
||||
* received named arguments as first argument. Only named arguments
|
||||
* present in the "args" dictionary are accepted. Attempts to invoke
|
||||
* ubus methods with arguments not mentioned here or with argument
|
||||
* values not matching the given type hints are rejected with
|
||||
* UBUS_STATUS_INVALID_ARGUMENT.
|
||||
*
|
||||
* The expected data type of each named argument is inferred from
|
||||
* the ucode value within the "args" dictionary:
|
||||
*
|
||||
* ucode type | ucode value | blob type
|
||||
* -----------+-------------+--------------------
|
||||
* integer | 8 | BLOBMSG_TYPE_INT8
|
||||
* integer | 16 | BLOBMSG_TYPE_INT16
|
||||
* integer | 64 | BLOBMSG_TYPE_INT64
|
||||
* integer | any integer | BLOBMSG_TYPE_INT32
|
||||
* boolean | true, false | BLOBMSG_TYPE_INT8
|
||||
* string | any string | BLOBMSG_TYPE_STRING
|
||||
* double | any double | BLOBMSG_TYPE_DOUBLE
|
||||
* array | any array | BLOBMSG_TYPE_ARRAY
|
||||
* object | any object | BLOBMSG_TYPE_TABLE
|
||||
*
|
||||
* The ucode callback function will also receive auxillary status
|
||||
* information about the ubus request within a dictionary passed as
|
||||
* second argument to it. The dictionary will contain details about
|
||||
* the invoked object, the invoked method name (useful in case
|
||||
* multiple methods share the same callback) and the effective ubusd
|
||||
* ACL for this request.
|
||||
*/
|
||||
args: {
|
||||
foo: 32,
|
||||
bar: 64,
|
||||
baz: true,
|
||||
qrx: "example"
|
||||
},
|
||||
|
||||
call: function(request) {
|
||||
return {
|
||||
got_args: request.args,
|
||||
got_info: request.info
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
method_3: {
|
||||
call: function(request) {
|
||||
/*
|
||||
* Process exit codes are automatically translated to ubus
|
||||
* error status codes. Exit code values outside of the
|
||||
* representable status range are converted to
|
||||
* UBUS_STATUS_UNKNOWN_ERROR.
|
||||
*/
|
||||
if (request.info.acl.user != "root")
|
||||
exit(UBUS_STATUS_PERMISSION_DENIED);
|
||||
|
||||
/*
|
||||
* To invoke nested ubus requests without potentially blocking
|
||||
* rpcd's main loop, use the ubus.defer() method to start an
|
||||
* asynchronous request and issue request.reply() from within
|
||||
* the completion callback. It is important to return the deferred
|
||||
* request value produced by ubus.call_async() to instruct rpcd to
|
||||
* await the completion of the nested request.
|
||||
*/
|
||||
return ubus.defer('example_object_2', 'method_a', { number: 5 },
|
||||
function(code, reply) {
|
||||
request.reply({
|
||||
res: reply,
|
||||
req: request.info
|
||||
}, UBUS_STATUS_OK);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
method_4: {
|
||||
call: function() {
|
||||
/*
|
||||
* Runtime exceptions are catched by rpcd, the corresponding
|
||||
* request is replied to with UBUS_STATUS_UNKNOWN_ERROR.
|
||||
*/
|
||||
die("An error occurred");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
example_object_2: {
|
||||
method_a: {
|
||||
args: { number: 123 },
|
||||
call: function(request) {
|
||||
/*
|
||||
* Instead of returning the reply, we can also use the reply
|
||||
* method of the request object.
|
||||
*/
|
||||
request.reply({ got_number: request.args.number });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
6
plugin.c
6
plugin.c
|
@ -489,8 +489,12 @@ rpc_plugin_register_library(struct ubus_context *ctx, const char *path)
|
|||
|
||||
dlh = dlopen(path, RTLD_LAZY | RTLD_LOCAL);
|
||||
|
||||
if (!dlh)
|
||||
if (!dlh) {
|
||||
fprintf(stderr, "Failed to load plugin %s: %s\n",
|
||||
path, dlerror());
|
||||
|
||||
return UBUS_STATUS_UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
p = dlsym(dlh, "rpc_plugin");
|
||||
|
||||
|
|
Loading…
Reference in a new issue