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:
Jo-Philipp Wich 2021-12-02 21:00:40 +01:00
parent 9c6ba38287
commit 4c532bfed2
4 changed files with 1210 additions and 2 deletions

View file

@ -9,6 +9,7 @@ INCLUDE_DIRECTORIES(include)
OPTION(FILE_SUPPORT "File plugin support" ON) OPTION(FILE_SUPPORT "File plugin support" ON)
OPTION(IWINFO_SUPPORT "libiwinfo plugin support" ON) OPTION(IWINFO_SUPPORT "libiwinfo plugin support" ON)
OPTION(RPCSYS_SUPPORT "rpc-sys 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 "") SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
@ -65,6 +66,14 @@ IF (IWINFO_SUPPORT)
SET_TARGET_PROPERTIES(iwinfo_plugin PROPERTIES OUTPUT_NAME iwinfo PREFIX "") SET_TARGET_PROPERTIES(iwinfo_plugin PROPERTIES OUTPUT_NAME iwinfo PREFIX "")
ENDIF() 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} INSTALL(TARGETS rpcd ${PLUGINS}
RUNTIME DESTINATION sbin RUNTIME DESTINATION sbin
LIBRARY DESTINATION lib/rpcd LIBRARY DESTINATION lib/rpcd

View 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 });
}
}
}
};

View file

@ -489,8 +489,12 @@ rpc_plugin_register_library(struct ubus_context *ctx, const char *path)
dlh = dlopen(path, RTLD_LAZY | RTLD_LOCAL); 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; return UBUS_STATUS_UNKNOWN_ERROR;
}
p = dlsym(dlh, "rpc_plugin"); p = dlsym(dlh, "rpc_plugin");

1044
ucode.c Normal file

File diff suppressed because it is too large Load diff