rpcd/plugin.c
Jo-Philipp Wich 4c532bfed2 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>
2021-12-08 20:09:24 +01:00

550 lines
10 KiB
C

/*
* rpcd - UBUS RPC server
*
* Copyright (C) 2013-2014 Jo-Philipp Wich <jow@openwrt.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <rpcd/plugin.h>
static struct blob_buf buf;
struct rpc_plugin_lookup_context {
uint32_t id;
char *name;
bool found;
};
static void
rpc_plugin_lookup_plugin_cb(struct ubus_context *ctx,
struct ubus_object_data *obj, void *priv)
{
struct rpc_plugin_lookup_context *c = priv;
if (c->id == obj->id)
{
c->found = true;
sprintf(c->name, "%s", obj->path);
}
}
static bool
rpc_plugin_lookup_plugin(struct ubus_context *ctx, struct ubus_object *obj,
char *strptr)
{
struct rpc_plugin_lookup_context c = { .id = obj->id, .name = strptr };
if (ubus_lookup(ctx, NULL, rpc_plugin_lookup_plugin_cb, &c))
return false;
return c.found;
}
static void
rpc_plugin_json_array_to_blob(struct array_list *a, struct blob_buf *blob);
static void
rpc_plugin_json_object_to_blob(json_object *o, struct blob_buf *blob);
static void
rpc_plugin_json_element_to_blob(const char *name, json_object *val,
struct blob_buf *blob)
{
void *c;
int64_t n;
switch (json_object_get_type(val)) {
case json_type_object:
c = blobmsg_open_table(blob, name);
rpc_plugin_json_object_to_blob(val, blob);
blobmsg_close_table(blob, c);
break;
case json_type_array:
c = blobmsg_open_array(blob, name);
rpc_plugin_json_array_to_blob(json_object_get_array(val), blob);
blobmsg_close_array(blob, c);
break;
case json_type_string:
blobmsg_add_string(blob, name, json_object_get_string(val));
break;
case json_type_boolean:
blobmsg_add_u8(blob, name, json_object_get_boolean(val));
break;
case json_type_int:
n = json_object_get_int64(val);
if (n >= INT32_MIN && n <= INT32_MAX)
blobmsg_add_u32(blob, name, n);
else
blobmsg_add_u64(blob, name, n);
break;
case json_type_double:
blobmsg_add_double(blob, name, json_object_get_double(val));
break;
case json_type_null:
blobmsg_add_field(blob, BLOBMSG_TYPE_UNSPEC, name, NULL, 0);
break;
}
}
static void
rpc_plugin_json_array_to_blob(struct array_list *a, struct blob_buf *blob)
{
int i, len;
for (i = 0, len = array_list_length(a); i < len; i++)
rpc_plugin_json_element_to_blob(NULL, array_list_get_idx(a, i), blob);
}
static void
rpc_plugin_json_object_to_blob(json_object *o, struct blob_buf *blob)
{
json_object_object_foreach(o, key, val)
rpc_plugin_json_element_to_blob(key, val, blob);
}
struct call_context {
char path[PATH_MAX];
const char *argv[4];
char *method;
char *input;
json_tokener *tok;
json_object *obj;
bool input_done;
bool output_done;
};
static int
rpc_plugin_call_stdin_cb(struct ustream *s, void *priv)
{
struct call_context *c = priv;
if (!c->input_done)
{
ustream_write(s, c->input, strlen(c->input), false);
c->input_done = true;
}
return 0;
}
static int
rpc_plugin_call_stdout_cb(struct blob_buf *blob, char *buf, int len, void *priv)
{
struct call_context *c = priv;
if (!c->output_done)
{
c->obj = json_tokener_parse_ex(c->tok, buf, len);
if (json_tokener_get_error(c->tok) != json_tokener_continue)
c->output_done = true;
}
return len;
}
static int
rpc_plugin_call_stderr_cb(struct blob_buf *blob, char *buf, int len, void *priv)
{
return len;
}
static int
rpc_plugin_call_finish_cb(struct blob_buf *blob, int stat, void *priv)
{
struct call_context *c = priv;
int rv = UBUS_STATUS_INVALID_ARGUMENT;
if (json_tokener_get_error(c->tok) == json_tokener_success)
{
if (c->obj)
{
if (json_object_get_type(c->obj) == json_type_object)
{
rpc_plugin_json_object_to_blob(c->obj, blob);
rv = UBUS_STATUS_OK;
}
json_object_put(c->obj);
}
else
{
rv = UBUS_STATUS_NO_DATA;
}
}
json_tokener_free(c->tok);
free(c->input);
return rv;
}
static int
rpc_plugin_call(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
int rv = UBUS_STATUS_UNKNOWN_ERROR;
struct call_context *c;
char *plugin, *mptr;
c = calloc_a(sizeof(*c), &mptr, strlen(method) + 1);
if (!c)
goto fail;
c->method = strcpy(mptr, method);
c->input = blobmsg_format_json(msg, true);
c->tok = json_tokener_new();
if (!c->input || !c->tok)
goto fail;
plugin = c->path + sprintf(c->path, "%s/", RPC_PLUGIN_DIRECTORY);
if (!rpc_plugin_lookup_plugin(ctx, obj, plugin))
{
rv = UBUS_STATUS_NOT_FOUND;
goto fail;
}
c->argv[0] = c->path;
c->argv[1] = "call";
c->argv[2] = c->method;
rv = rpc_exec(c->argv, rpc_plugin_call_stdin_cb,
rpc_plugin_call_stdout_cb, rpc_plugin_call_stderr_cb,
rpc_plugin_call_finish_cb, c, ctx, req);
if (rv == UBUS_STATUS_OK)
return rv;
fail:
if (c)
{
if (c->input)
free(c->input);
if (c->tok)
json_tokener_free(c->tok);
free(c);
}
return rv;
}
static bool
rpc_plugin_parse_signature(struct blob_attr *sig, struct ubus_method *method)
{
int rem, n_attr;
enum blobmsg_type type;
struct blob_attr *attr;
struct blobmsg_policy *policy = NULL;
if (!sig || blobmsg_type(sig) != BLOBMSG_TYPE_TABLE)
return false;
n_attr = 0;
blobmsg_for_each_attr(attr, sig, rem)
n_attr++;
if (n_attr)
{
policy = calloc(n_attr, sizeof(*policy));
if (!policy)
return false;
n_attr = 0;
blobmsg_for_each_attr(attr, sig, rem)
{
type = blobmsg_type(attr);
if (type == BLOBMSG_TYPE_INT32)
{
switch (blobmsg_get_u32(attr))
{
case 8:
type = BLOBMSG_TYPE_INT8;
break;
case 16:
type = BLOBMSG_TYPE_INT16;
break;
case 64:
type = BLOBMSG_TYPE_INT64;
break;
default:
type = BLOBMSG_TYPE_INT32;
break;
}
}
policy[n_attr].name = strdup(blobmsg_name(attr));
policy[n_attr].type = type;
n_attr++;
}
}
method->name = strdup(blobmsg_name(sig));
method->handler = rpc_plugin_call;
method->policy = policy;
method->n_policy = n_attr;
return true;
}
static struct ubus_object *
rpc_plugin_parse_exec(const char *name, int fd)
{
int len, rem, n_method;
struct blob_attr *cur;
struct ubus_method *methods;
struct ubus_object_type *obj_type;
struct ubus_object *obj;
char outbuf[1024];
json_tokener *tok;
json_object *jsobj;
blob_buf_init(&buf, 0);
tok = json_tokener_new();
if (!tok)
return NULL;
while ((len = read(fd, outbuf, sizeof(outbuf))) > 0)
{
jsobj = json_tokener_parse_ex(tok, outbuf, len);
if (json_tokener_get_error(tok) == json_tokener_continue)
continue;
if (json_tokener_get_error(tok) != json_tokener_success)
break;
if (jsobj)
{
if (json_object_get_type(jsobj) == json_type_object)
blobmsg_add_object(&buf, jsobj);
json_object_put(jsobj);
break;
}
}
json_tokener_free(tok);
n_method = 0;
blob_for_each_attr(cur, buf.head, rem)
n_method++;
if (!n_method)
return NULL;
methods = calloc(n_method, sizeof(*methods));
if (!methods)
return NULL;
n_method = 0;
blob_for_each_attr(cur, buf.head, rem)
{
if (!rpc_plugin_parse_signature(cur, &methods[n_method]))
continue;
n_method++;
}
obj = calloc(1, sizeof(*obj));
if (!obj)
return NULL;
obj_type = calloc(1, sizeof(*obj_type));
if (!obj_type) {
free(obj);
return NULL;
}
if (asprintf((char **)&obj_type->name, "rpcd-plugin-exec-%s", name) < 0) {
free(obj);
free(obj_type);
return NULL;
}
obj_type->methods = methods;
obj_type->n_methods = n_method;
obj->name = strdup(name);
obj->type = obj_type;
obj->methods = methods;
obj->n_methods = n_method;
return obj;
}
static int
rpc_plugin_register_exec(struct ubus_context *ctx, const char *path)
{
pid_t pid;
int rv = UBUS_STATUS_NO_DATA, fd, fds[2];
const char *name;
struct ubus_object *plugin;
name = strrchr(path, '/');
if (!name)
return UBUS_STATUS_INVALID_ARGUMENT;
if (pipe(fds))
return UBUS_STATUS_UNKNOWN_ERROR;
switch ((pid = fork()))
{
case -1:
return UBUS_STATUS_UNKNOWN_ERROR;
case 0:
fd = open("/dev/null", O_RDWR);
if (fd > -1)
{
dup2(fd, 0);
dup2(fd, 2);
if (fd > 2)
close(fd);
}
dup2(fds[1], 1);
close(fds[0]);
close(fds[1]);
if (execl(path, path, "list", NULL))
return UBUS_STATUS_UNKNOWN_ERROR;
default:
plugin = rpc_plugin_parse_exec(name + 1, fds[0]);
if (!plugin)
goto out;
rv = ubus_add_object(ctx, plugin);
out:
close(fds[0]);
close(fds[1]);
waitpid(pid, NULL, 0);
return rv;
}
}
static LIST_HEAD(plugins);
static const struct rpc_daemon_ops ops = {
.session_access = rpc_session_access,
.session_create_cb = rpc_session_create_cb,
.session_destroy_cb = rpc_session_destroy_cb,
.exec = rpc_exec,
.exec_timeout = &rpc_exec_timeout,
};
static int
rpc_plugin_register_library(struct ubus_context *ctx, const char *path)
{
struct rpc_plugin *p;
void *dlh;
dlh = dlopen(path, RTLD_LAZY | RTLD_LOCAL);
if (!dlh) {
fprintf(stderr, "Failed to load plugin %s: %s\n",
path, dlerror());
return UBUS_STATUS_UNKNOWN_ERROR;
}
p = dlsym(dlh, "rpc_plugin");
if (!p)
return UBUS_STATUS_NOT_FOUND;
list_add(&p->list, &plugins);
return p->init(&ops, ctx);
}
int rpc_plugin_api_init(struct ubus_context *ctx)
{
DIR *d;
int rv = 0;
struct stat s;
struct dirent *e;
char path[PATH_MAX];
if ((d = opendir(RPC_PLUGIN_DIRECTORY)) != NULL)
{
while ((e = readdir(d)) != NULL)
{
snprintf(path, sizeof(path) - 1,
RPC_PLUGIN_DIRECTORY "/%s", e->d_name);
if (stat(path, &s) || !S_ISREG(s.st_mode) || !(s.st_mode & S_IXUSR))
continue;
rv |= rpc_plugin_register_exec(ctx, path);
}
closedir(d);
}
if ((d = opendir(RPC_LIBRARY_DIRECTORY)) != NULL)
{
while ((e = readdir(d)) != NULL)
{
snprintf(path, sizeof(path) - 1,
RPC_LIBRARY_DIRECTORY "/%s", e->d_name);
if (stat(path, &s) || !S_ISREG(s.st_mode))
continue;
rv |= rpc_plugin_register_library(ctx, path);
}
closedir(d);
}
return rv;
}