diff options
Diffstat (limited to 'lib/ubus.c')
-rw-r--r-- | lib/ubus.c | 333 |
1 files changed, 333 insertions, 0 deletions
diff --git a/lib/ubus.c b/lib/ubus.c new file mode 100644 index 0000000..5c56787 --- /dev/null +++ b/lib/ubus.c @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2020 Jo-Philipp Wich <jo@mein.io> + * + * 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 "../module.h" + +#include <unistd.h> +#include <libubus.h> +#include <libubox/blobmsg.h> +#include <libubox/blobmsg_json.h> + +#define err_return(err) do { last_error = err; return NULL; } while(0) + +static const struct ut_ops *ops; + +static struct json_object *conn_proto; + +static enum ubus_msg_status last_error = 0; + +struct ubus_connection { + int timeout; + struct blob_buf buf; + struct ubus_context *ctx; +}; + +static struct json_object * +ut_ubus_error(struct ut_state *s, uint32_t off, struct json_object *args) +{ + struct json_object *errmsg; + + if (last_error == 0) + return NULL; + + errmsg = json_object_new_string(ubus_strerror(last_error)); + last_error = 0; + + return errmsg; +} + +static struct json_object * +ut_blob_to_json(struct blob_attr *attr, bool table, const char **name); + +static struct json_object * +ut_blob_array_to_json(struct blob_attr *attr, size_t len, bool table) +{ + struct json_object *o = table ? json_object_new_object() : json_object_new_array(); + struct json_object *v; + struct blob_attr *pos; + size_t rem = len; + const char *name; + + if (!o) + return NULL; + + __blob_for_each_attr(pos, attr, rem) { + name = NULL; + v = ut_blob_to_json(pos, table, &name); + + if (table && name) + json_object_object_add(o, name, v); + else if (!table) + json_object_array_add(o, v); + else + json_object_put(v); + } + + return o; +} + +static struct json_object * +ut_blob_to_json(struct blob_attr *attr, bool table, const char **name) +{ + void *data; + int len; + + if (!blobmsg_check_attr(attr, false)) + return NULL; + + if (table && blobmsg_name(attr)[0]) + *name = blobmsg_name(attr); + + data = blobmsg_data(attr); + len = blobmsg_data_len(attr); + + switch (blob_id(attr)) { + case BLOBMSG_TYPE_BOOL: + return json_object_new_boolean(*(uint8_t *)data); + + case BLOBMSG_TYPE_INT16: + return json_object_new_int64((int64_t)be16_to_cpu(*(uint16_t *)data)); + + case BLOBMSG_TYPE_INT32: + return json_object_new_int64((int64_t)be32_to_cpu(*(uint32_t *)data)); + + case BLOBMSG_TYPE_INT64: + return json_object_new_uint64(be64_to_cpu(*(uint64_t *)data)); + + case BLOBMSG_TYPE_DOUBLE: + ; + union { + double d; + uint64_t u64; + } v; + + v.u64 = be64_to_cpu(*(uint64_t *)data); + + return json_object_new_double(v.d); + + case BLOBMSG_TYPE_STRING: + return json_object_new_string(data); + + case BLOBMSG_TYPE_ARRAY: + return ut_blob_array_to_json(data, len, false); + + case BLOBMSG_TYPE_TABLE: + return ut_blob_array_to_json(data, len, true); + + default: + return NULL; + } +} + + +static struct json_object * +ut_ubus_connect(struct ut_state *s, uint32_t off, struct json_object *args) +{ + struct json_object *socket = json_object_array_get_idx(args, 0); + struct json_object *timeout = json_object_array_get_idx(args, 1); + struct json_object *co; + struct ubus_connection *c; + + if ((socket && !json_object_is_type(socket, json_type_string)) || + (timeout && !json_object_is_type(timeout, json_type_int))) + err_return(UBUS_STATUS_INVALID_ARGUMENT); + + c = calloc(1, sizeof(*c)); + + if (!c) + err_return(UBUS_STATUS_UNKNOWN_ERROR); + + c->ctx = ubus_connect(socket ? json_object_get_string(socket) : NULL); + c->timeout = timeout ? json_object_get_int(timeout) : 30; + + if (!c->ctx) { + free(c); + err_return(UBUS_STATUS_UNKNOWN_ERROR); + } + + if (c->timeout < 0) + c->timeout = 30; + + co = json_object_new_object(); + + if (!co) { + ubus_free(c->ctx); + free(c); + err_return(ENOMEM); + } + + ubus_add_uloop(c->ctx); + + return ops->set_type(s, co, conn_proto, "ubus.connection", c); +} + +static void +ut_ubus_signatures_cb(struct ubus_context *c, struct ubus_object_data *o, void *p) +{ + struct json_object *arr = p; + struct json_object *sig; + + if (!o->signature) + return; + + sig = ut_blob_array_to_json(blob_data(o->signature), blob_len(o->signature), true); + + if (sig) + json_object_array_add(arr, sig); +} + +static void +ut_ubus_objects_cb(struct ubus_context *c, struct ubus_object_data *o, void *p) +{ + struct json_object *arr = p; + struct json_object *obj; + + obj = json_object_new_string(o->path); + + if (obj) + json_object_array_add(arr, obj); +} + +static struct json_object * +ut_ubus_list(struct ut_state *s, uint32_t off, struct json_object *args) +{ + struct ubus_connection **c = (struct ubus_connection **)ops->get_type(s->ctx, "ubus.connection"); + struct json_object *objname = json_object_array_get_idx(args, 0); + struct json_object *res; + enum ubus_msg_status rv; + + if (!c || !*c || !(*c)->ctx) + err_return(UBUS_STATUS_CONNECTION_FAILED); + + if (objname && !json_object_is_type(objname, json_type_string)) + err_return(UBUS_STATUS_INVALID_ARGUMENT); + + res = json_object_new_array(); + + if (!res) + err_return(UBUS_STATUS_UNKNOWN_ERROR); + + rv = ubus_lookup((*c)->ctx, + objname ? json_object_get_string(objname) : NULL, + objname ? ut_ubus_signatures_cb : ut_ubus_objects_cb, + res); + + if (rv != UBUS_STATUS_OK) + err_return(rv); + + return res; +} + +static void +ut_ubus_call_cb(struct ubus_request *req, int type, struct blob_attr *msg) +{ + struct json_object **res = (struct json_object **)req->priv; + + *res = msg ? ut_blob_array_to_json(blob_data(msg), blob_len(msg), true) : NULL; +} + +static struct json_object * +ut_ubus_call(struct ut_state *s, uint32_t off, struct json_object *args) +{ + struct ubus_connection **c = (struct ubus_connection **)ops->get_type(s->ctx, "ubus.connection"); + struct json_object *objname = json_object_array_get_idx(args, 0); + struct json_object *funname = json_object_array_get_idx(args, 1); + struct json_object *funargs = json_object_array_get_idx(args, 2); + struct json_object *res; + enum ubus_msg_status rv; + uint32_t id; + + if (!c || !*c || !(*c)->ctx) + err_return(UBUS_STATUS_CONNECTION_FAILED); + + if (!json_object_is_type(objname, json_type_string) || + !json_object_is_type(funname, json_type_string) || + (funargs && !json_object_is_type(funargs, json_type_object))) + err_return(UBUS_STATUS_INVALID_ARGUMENT); + + blob_buf_init(&(*c)->buf, 0); + + if (funargs && !blobmsg_add_object(&(*c)->buf, funargs)) + err_return(UBUS_STATUS_UNKNOWN_ERROR); + + rv = ubus_lookup_id((*c)->ctx, json_object_get_string(objname), &id); + + if (rv != UBUS_STATUS_OK) + err_return(rv); + + rv = ubus_invoke((*c)->ctx, id, json_object_get_string(funname), (*c)->buf.head, + ut_ubus_call_cb, &res, (*c)->timeout * 1000); + + if (rv != UBUS_STATUS_OK) + err_return(rv); + + return res; +} + +static struct json_object * +ut_ubus_disconnect(struct ut_state *s, uint32_t off, struct json_object *args) +{ + struct ubus_connection **c = (struct ubus_connection **)ops->get_type(s->ctx, "ubus.connection"); + + if (!c || !*c || !(*c)->ctx) + err_return(UBUS_STATUS_CONNECTION_FAILED); + + ubus_free((*c)->ctx); + (*c)->ctx = NULL; + + return json_object_new_boolean(true); +} + + +static const struct { const char *name; ut_c_fn *func; } global_fns[] = { + { "error", ut_ubus_error }, + { "connect", ut_ubus_connect }, +}; + +static const struct { const char *name; ut_c_fn *func; } conn_fns[] = { + { "list", ut_ubus_list }, + { "call", ut_ubus_call }, + { "disconnect", ut_ubus_disconnect }, +}; + + +static void close_connection(void *ud) { + struct ubus_connection *conn = ud; + + blob_buf_free(&conn->buf); + + if (conn->ctx) + ubus_free(conn->ctx); + + free(conn); +} + +void ut_module_init(const struct ut_ops *ut, struct ut_state *s, struct json_object *scope) +{ + int i; + + ops = ut; + ops->register_type("ubus.connection", close_connection); + + for (i = 0; i < ARRAY_SIZE(global_fns); i++) + ops->register_function(s, scope, global_fns[i].name, global_fns[i].func); + + conn_proto = ops->new_object(s, NULL); + + if (conn_proto) + for (i = 0; i < ARRAY_SIZE(global_fns); i++) + ops->register_function(s, conn_proto, conn_fns[i].name, conn_fns[i].func); +} |