diff options
-rw-r--r-- | lib/ubus.c | 1819 | ||||
-rw-r--r-- | lib/uloop.c | 3 |
2 files changed, 1645 insertions, 177 deletions
@@ -17,55 +17,304 @@ #include <unistd.h> #include <libubus.h> #include <libubox/blobmsg.h> -#include <libubox/blobmsg_json.h> #include "ucode/module.h" -#define err_return(err) do { last_error = err; return NULL; } while(0) +#define err_return(err, ...) do { set_error(err, __VA_ARGS__); return NULL; } while(0) -static enum ubus_msg_status last_error = 0; +static struct { + enum ubus_msg_status code; + char *msg; +} last_error; + +__attribute__((format(printf, 2, 3))) static void +set_error(int errcode, const char *fmt, ...) +{ + va_list ap; + + free(last_error.msg); + + last_error.code = errcode; + last_error.msg = NULL; + + if (fmt) { + va_start(ap, fmt); + xvasprintf(&last_error.msg, fmt, ap); + va_end(ap); + } +} + +static char * +_arg_type(uc_type_t type) +{ + switch (type) { + case UC_INTEGER: return "an integer value"; + case UC_BOOLEAN: return "a boolean value"; + case UC_STRING: return "a string value"; + case UC_DOUBLE: return "a double value"; + case UC_ARRAY: return "an array"; + case UC_OBJECT: return "an object"; + case UC_REGEXP: return "a regular expression"; + case UC_CLOSURE: return "a function"; + default: return "the expected type"; + } +} + +static bool +_args_get(uc_vm_t *vm, size_t nargs, ...) +{ + uc_value_t **ptr, *arg; + uc_type_t type, t; + const char *name; + size_t index = 0; + va_list ap; + bool opt; + + va_start(ap, nargs); + + while (true) { + name = va_arg(ap, const char *); + + if (!name) + break; + + arg = uc_fn_arg(index++); + + type = va_arg(ap, uc_type_t); + opt = va_arg(ap, int); + ptr = va_arg(ap, uc_value_t **); + + if (!opt && !arg) + err_return(UBUS_STATUS_INVALID_ARGUMENT, "Argument %s is required", name); + + t = ucv_type(arg); + + if (t == UC_CFUNCTION) + t = UC_CLOSURE; + + if (arg && t != type) + err_return(UBUS_STATUS_INVALID_ARGUMENT, "Argument %s is not %s", name, _arg_type(type)); + + *ptr = arg; + } + + va_end(ap); + + return true; +} + +#define args_get(vm, nargs, ...) do { if (!_args_get(vm, nargs, __VA_ARGS__, NULL)) return NULL; } while(0) + +static uc_resource_type_t *subscriber_type; +static uc_resource_type_t *listener_type; +static uc_resource_type_t *request_type; +static uc_resource_type_t *notify_type; +static uc_resource_type_t *object_type; static uc_resource_type_t *defer_type; static uc_resource_type_t *conn_type; -static uc_value_t *cb_registry; static uint64_t n_cb_active; static bool have_own_uloop; +static struct blob_buf buf; + typedef struct { int timeout; struct blob_buf buf; struct ubus_context *ctx; -} ubus_connection; +} uc_ubus_connection_t; typedef struct { - struct ubus_context *context; struct ubus_request request; struct uloop_timeout timeout; + struct ubus_context *ctx; + size_t registry_index; bool complete; uc_vm_t *vm; uc_value_t *callback; uc_value_t *response; -} ubus_deferred; +} uc_ubus_deferred_t; + +typedef struct { + struct ubus_object obj; + struct ubus_context *ctx; + size_t registry_index; + uc_vm_t *vm; +} uc_ubus_object_t; + +typedef struct { + struct ubus_request_data req; + struct uloop_timeout timeout; + struct ubus_context *ctx; + size_t registry_index; + bool replied; + uc_vm_t *vm; +} uc_ubus_request_t; + +typedef struct { + struct ubus_notify_request req; + struct ubus_context *ctx; + size_t registry_index; + bool complete; + uc_vm_t *vm; +} uc_ubus_notify_t; + +typedef struct { + struct ubus_event_handler ev; + struct ubus_context *ctx; + size_t registry_index; + uc_vm_t *vm; +} uc_ubus_listener_t; + +typedef struct { + struct ubus_subscriber sub; + struct ubus_context *ctx; + size_t registry_index; + uc_vm_t *vm; +} uc_ubus_subscriber_t; static uc_value_t * uc_ubus_error(uc_vm_t *vm, size_t nargs) { - uc_value_t *errmsg; + uc_stringbuf_t *buf; + const char *s; - if (last_error == 0) + if (last_error.code == 0) return NULL; - errmsg = ucv_string_new(ubus_strerror(last_error)); - last_error = 0; + buf = ucv_stringbuf_new(); + + if (last_error.code == UBUS_STATUS_UNKNOWN_ERROR && last_error.msg) { + ucv_stringbuf_addstr(buf, last_error.msg, strlen(last_error.msg)); + } + else { + s = ubus_strerror(last_error.code); + + ucv_stringbuf_addstr(buf, s, strlen(s)); + + if (last_error.msg) + ucv_stringbuf_printf(buf, ": %s", last_error.msg); + } + + set_error(0, NULL); + + return ucv_stringbuf_finish(buf); +} + +static void +_uc_reg_get(uc_vm_t *vm, const char *key, size_t idx, size_t nptrs, ...) +{ + uc_value_t *reg = uc_vm_registry_get(vm, key), **val; + va_list ap; + size_t i; + + va_start(ap, nptrs); + + for (i = 0; i < nptrs; i++) { + val = va_arg(ap, uc_value_t **); + + if (val) + *val = ucv_array_get(reg, idx + i); + } + + va_end(ap); +} + +static size_t +_uc_reg_add(uc_vm_t *vm, const char *key, size_t nptrs, ...) +{ + uc_value_t *reg = uc_vm_registry_get(vm, key); + size_t idx, i; + va_list ap; + + if (!reg) { + reg = ucv_array_new(vm); + uc_vm_registry_set(vm, key, reg); + } + + va_start(ap, nptrs); + + for (idx = 0;; idx += nptrs) { + if (ucv_array_get(reg, idx) == NULL) { + for (i = 0; i < nptrs; i++) + ucv_array_set(reg, idx + i, va_arg(ap, uc_value_t *)); + + break; + } + } + + va_end(ap); - return errmsg; + return idx; } +static void +_uc_reg_clear(uc_vm_t *vm, const char *key, size_t idx, size_t nptrs) +{ + uc_value_t *reg = uc_vm_registry_get(vm, key); + + while (nptrs > 0) { + nptrs--; + ucv_array_set(reg, idx + nptrs, NULL); + } +} + + +#define request_reg_add(vm, request, cb) \ + _uc_reg_add(vm, "ubus.requests", 2, request, cb) + +#define request_reg_get(vm, idx, request, cb) \ + _uc_reg_get(vm, "ubus.requests", idx, 2, request, cb) + +#define request_reg_clear(vm, idx) \ + _uc_reg_clear(vm, "ubus.requests", idx, 2) + + +#define object_reg_add(vm, obj, msg, cb) \ + _uc_reg_add(vm, "ubus.objects", 3, obj, msg, cb) + +#define object_reg_get(vm, idx, obj, msg, cb) \ + _uc_reg_get(vm, "ubus.objects", idx, 3, obj, msg, cb) + +#define object_reg_clear(vm, idx) \ + _uc_reg_clear(vm, "ubus.objects", idx, 3) + + +#define notify_reg_add(vm, notify, dcb, scb, ccb) \ + _uc_reg_add(vm, "ubus.notifications", 4, notify, dcb, scb, ccb) + +#define notify_reg_get(vm, idx, notify, dcb, scb, ccb) \ + _uc_reg_get(vm, "ubus.notifications", idx, 4, notify, dcb, scb, ccb) + +#define notify_reg_clear(vm, idx) \ + _uc_reg_clear(vm, "ubus.notifications", idx, 4) + + +#define listener_reg_add(vm, listener, cb) \ + _uc_reg_add(vm, "ubus.listeners", 2, listener, cb) + +#define listener_reg_get(vm, idx, listener, cb) \ + _uc_reg_get(vm, "ubus.listeners", idx, 2, listener, cb) + +#define listener_reg_clear(vm, idx) \ + _uc_reg_clear(vm, "ubus.listeners", idx, 2) + + +#define subscriber_reg_add(vm, subscriber, ncb, rcb) \ + _uc_reg_add(vm, "ubus.subscribers", 3, subscriber, ncb, rcb) + +#define subscriber_reg_get(vm, idx, subscriber, ncb, rcb) \ + _uc_reg_get(vm, "ubus.subscribers", idx, 3, subscriber, ncb, rcb) + +#define subscriber_reg_clear(vm, idx) \ + _uc_reg_clear(vm, "ubus.subscribers", idx, 3) + + static uc_value_t * -uc_blob_to_json(uc_vm_t *vm, struct blob_attr *attr, bool table, const char **name); +blob_to_ucv(uc_vm_t *vm, struct blob_attr *attr, bool table, const char **name); static uc_value_t * -uc_blob_array_to_json(uc_vm_t *vm, struct blob_attr *attr, size_t len, bool table) +blob_array_to_ucv(uc_vm_t *vm, struct blob_attr *attr, size_t len, bool table) { uc_value_t *o = table ? ucv_object_new(vm) : ucv_array_new(vm); uc_value_t *v; @@ -78,7 +327,7 @@ uc_blob_array_to_json(uc_vm_t *vm, struct blob_attr *attr, size_t len, bool tabl __blob_for_each_attr(pos, attr, rem) { name = NULL; - v = uc_blob_to_json(vm, pos, table, &name); + v = blob_to_ucv(vm, pos, table, &name); if (table && name) ucv_object_add(o, name, v); @@ -92,7 +341,7 @@ uc_blob_array_to_json(uc_vm_t *vm, struct blob_attr *attr, size_t len, bool tabl } static uc_value_t * -uc_blob_to_json(uc_vm_t *vm, struct blob_attr *attr, bool table, const char **name) +blob_to_ucv(uc_vm_t *vm, struct blob_attr *attr, bool table, const char **name) { void *data; int len; @@ -134,53 +383,113 @@ uc_blob_to_json(uc_vm_t *vm, struct blob_attr *attr, bool table, const char **na return ucv_string_new(data); case BLOBMSG_TYPE_ARRAY: - return uc_blob_array_to_json(vm, data, len, false); + return blob_array_to_ucv(vm, data, len, false); case BLOBMSG_TYPE_TABLE: - return uc_blob_array_to_json(vm, data, len, true); + return blob_array_to_ucv(vm, data, len, true); default: return NULL; } } +static void +ucv_array_to_blob(uc_value_t *val, struct blob_buf *blob); -static uc_value_t * -uc_ubus_connect(uc_vm_t *vm, size_t nargs) +static void +ucv_object_to_blob(uc_value_t *val, struct blob_buf *blob); + +static void +ucv_to_blob(const char *name, uc_value_t *val, struct blob_buf *blob) +{ + int64_t n; + void *c; + + switch (ucv_type(val)) { + case UC_NULL: + blobmsg_add_field(blob, BLOBMSG_TYPE_UNSPEC, name, NULL, 0); + break; + + case UC_BOOLEAN: + blobmsg_add_u8(blob, name, ucv_boolean_get(val)); + break; + + case UC_INTEGER: + n = ucv_int64_get(val); + + if (errno == ERANGE) + blobmsg_add_u64(blob, name, ucv_uint64_get(val)); + else if (n >= INT32_MIN && n <= INT32_MAX) + blobmsg_add_u32(blob, name, n); + else + blobmsg_add_u64(blob, name, n); + + break; + + case UC_DOUBLE: + blobmsg_add_double(blob, name, ucv_double_get(val)); + break; + + case UC_STRING: + blobmsg_add_string(blob, name, ucv_string_get(val)); + break; + + case UC_ARRAY: + c = blobmsg_open_array(blob, name); + ucv_array_to_blob(val, blob); + blobmsg_close_array(blob, c); + break; + + case UC_OBJECT: + c = blobmsg_open_table(blob, name); + ucv_object_to_blob(val, blob); + blobmsg_close_table(blob, c); + break; + + default: + break; + } +} + +static void +ucv_array_to_blob(uc_value_t *val, struct blob_buf *blob) { - uc_value_t *socket = uc_fn_arg(0); - uc_value_t *timeout = uc_fn_arg(1); - uc_value_t *co; - ubus_connection *c; + size_t i; - if ((socket && ucv_type(socket) != UC_STRING) || - (timeout && ucv_type(timeout) != UC_INTEGER)) - err_return(UBUS_STATUS_INVALID_ARGUMENT); + for (i = 0; i < ucv_array_length(val); i++) + ucv_to_blob(NULL, ucv_array_get(val, i), blob); +} + +static void +ucv_object_to_blob(uc_value_t *val, struct blob_buf *blob) +{ + ucv_object_foreach(val, k, v) + ucv_to_blob(k, v, blob); +} - c = calloc(1, sizeof(*c)); - if (!c) - err_return(UBUS_STATUS_UNKNOWN_ERROR); +static uc_value_t * +uc_ubus_connect(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *socket, *timeout; + uc_ubus_connection_t *c; + args_get(vm, nargs, + "socket", UC_STRING, true, &socket, + "timeout", UC_INTEGER, true, &timeout); + + c = xalloc(sizeof(*c)); c->ctx = ubus_connect(socket ? ucv_string_get(socket) : NULL); c->timeout = timeout ? ucv_int64_get(timeout) : 30; if (!c->ctx) { free(c); - err_return(UBUS_STATUS_UNKNOWN_ERROR); + err_return(UBUS_STATUS_UNKNOWN_ERROR, "Unable to connect to ubus socket"); } if (c->timeout < 0) c->timeout = 30; - co = ucv_object_new(vm); - - if (!co) { - ubus_free(c->ctx); - free(c); - err_return(ENOMEM); - } - ubus_add_uloop(c->ctx); return uc_resource_new(conn_type, c); @@ -195,7 +504,7 @@ uc_ubus_signatures_cb(struct ubus_context *c, struct ubus_object_data *o, void * if (!o->signature) return; - sig = uc_blob_array_to_json(NULL, blob_data(o->signature), blob_len(o->signature), true); + sig = blob_array_to_ucv(NULL, blob_data(o->signature), blob_len(o->signature), true); if (sig) ucv_array_push(arr, sig); @@ -204,41 +513,35 @@ uc_ubus_signatures_cb(struct ubus_context *c, struct ubus_object_data *o, void * static void uc_ubus_objects_cb(struct ubus_context *c, struct ubus_object_data *o, void *p) { - json_object *arr = p; - json_object *obj; - - obj = json_object_new_string(o->path); + uc_value_t *arr = p; - if (obj) - json_object_array_add(arr, obj); + ucv_array_push(arr, ucv_string_new(o->path)); } static uc_value_t * uc_ubus_list(uc_vm_t *vm, size_t nargs) { - ubus_connection **c = uc_fn_this("ubus.connection"); - uc_value_t *objname = uc_fn_arg(0); - uc_value_t *res = NULL; + uc_ubus_connection_t **c = uc_fn_this("ubus.connection"); + uc_value_t *objname, *res = NULL; enum ubus_msg_status rv; if (!c || !*c || !(*c)->ctx) - err_return(UBUS_STATUS_CONNECTION_FAILED); + err_return(UBUS_STATUS_CONNECTION_FAILED, NULL); - if (objname && ucv_type(objname) != UC_STRING) - err_return(UBUS_STATUS_INVALID_ARGUMENT); + args_get(vm, nargs, + "object name", UC_STRING, true, &objname); res = ucv_array_new(vm); - if (!res) - err_return(UBUS_STATUS_UNKNOWN_ERROR); - rv = ubus_lookup((*c)->ctx, objname ? ucv_string_get(objname) : NULL, objname ? uc_ubus_signatures_cb : uc_ubus_objects_cb, res); - if (rv != UBUS_STATUS_OK) - err_return(rv); + if (rv != UBUS_STATUS_OK) { + ucv_put(res); + err_return(rv, NULL); + } return res; } @@ -248,35 +551,27 @@ uc_ubus_call_cb(struct ubus_request *req, int type, struct blob_attr *msg) { uc_value_t **res = (uc_value_t **)req->priv; - *res = msg ? uc_blob_array_to_json(NULL, blob_data(msg), blob_len(msg), true) : NULL; + *res = msg ? blob_array_to_ucv(NULL, blob_data(msg), blob_len(msg), true) : NULL; } static void -uc_ubus_invoke_async_callback(ubus_deferred *defer, int ret, uc_value_t *reply) +uc_ubus_call_user_cb(uc_ubus_deferred_t *defer, int ret, uc_value_t *reply) { - uc_resource_t *r; - size_t i; + uc_value_t *this, *func; - if (defer->callback) { - uc_vm_stack_push(defer->vm, ucv_get(defer->callback)); + request_reg_get(defer->vm, defer->registry_index, &this, &func); + + if (ucv_is_callable(func)) { + uc_vm_stack_push(defer->vm, ucv_get(this)); + uc_vm_stack_push(defer->vm, ucv_get(func)); uc_vm_stack_push(defer->vm, ucv_int64_new(ret)); uc_vm_stack_push(defer->vm, ucv_get(reply)); - if (uc_vm_call(defer->vm, false, 2) == EXCEPTION_NONE) + if (uc_vm_call(defer->vm, true, 2) == EXCEPTION_NONE) ucv_put(uc_vm_stack_pop(defer->vm)); - - defer->callback = NULL; } - for (i = 0; i < ucv_array_length(cb_registry); i += 2) { - r = (uc_resource_t *)ucv_array_get(cb_registry, i); - - if (r && r->data == defer) { - ucv_array_set(cb_registry, i, NULL); - ucv_array_set(cb_registry, i + 1, NULL); - break; - } - } + request_reg_clear(defer->vm, defer->registry_index); n_cb_active--; @@ -287,16 +582,16 @@ uc_ubus_invoke_async_callback(ubus_deferred *defer, int ret, uc_value_t *reply) static void uc_ubus_call_data_cb(struct ubus_request *req, int type, struct blob_attr *msg) { - ubus_deferred *defer = container_of(req, ubus_deferred, request); + uc_ubus_deferred_t *defer = container_of(req, uc_ubus_deferred_t, request); if (defer->response == NULL) - defer->response = uc_blob_array_to_json(defer->vm, blob_data(msg), blob_len(msg), true); + defer->response = blob_array_to_ucv(defer->vm, blob_data(msg), blob_len(msg), true); } static void uc_ubus_call_done_cb(struct ubus_request *req, int ret) { - ubus_deferred *defer = container_of(req, ubus_deferred, request); + uc_ubus_deferred_t *defer = container_of(req, uc_ubus_deferred_t, request); if (defer->complete) return; @@ -304,21 +599,21 @@ uc_ubus_call_done_cb(struct ubus_request *req, int ret) defer->complete = true; uloop_timeout_cancel(&defer->timeout); - uc_ubus_invoke_async_callback(defer, ret, defer->response); + uc_ubus_call_user_cb(defer, ret, defer->response); } static void uc_ubus_call_timeout_cb(struct uloop_timeout *timeout) { - ubus_deferred *defer = container_of(timeout, ubus_deferred, timeout); + uc_ubus_deferred_t *defer = container_of(timeout, uc_ubus_deferred_t, timeout); if (defer->complete) return; defer->complete = true; - ubus_abort_request(defer->context, &defer->request); + ubus_abort_request(defer->ctx, &defer->request); - uc_ubus_invoke_async_callback(defer, UBUS_STATUS_TIMEOUT, NULL); + uc_ubus_call_user_cb(defer, UBUS_STATUS_TIMEOUT, NULL); } static bool @@ -334,47 +629,54 @@ uc_ubus_have_uloop(void) return active; } +static bool +_conn_get(uc_vm_t *vm, uc_ubus_connection_t ***conn) +{ + *conn = uc_fn_this("ubus.connection"); + + if (!*conn || !**conn) + err_return(UBUS_STATUS_INVALID_ARGUMENT, "Invalid connection context"); + + if (!(**conn)->ctx) + err_return(UBUS_STATUS_CONNECTION_FAILED, "Connection is closed"); + + return true; +} + +#define conn_get(vm, ptr) do { if (!_conn_get(vm, ptr)) return NULL; } while(0) + static uc_value_t * uc_ubus_call(uc_vm_t *vm, size_t nargs) { - ubus_connection **c = uc_fn_this("ubus.connection"); - uc_value_t *objname = uc_fn_arg(0); - uc_value_t *funname = uc_fn_arg(1); - uc_value_t *funargs = uc_fn_arg(2); - uc_value_t *res = NULL; + uc_value_t *objname, *funname, *funargs, *res = NULL; + uc_ubus_connection_t **c; enum ubus_msg_status rv; - json_object *o; uint32_t id; - if (!c || !*c || !(*c)->ctx) - err_return(UBUS_STATUS_CONNECTION_FAILED); + conn_get(vm, &c); - if (ucv_type(objname) != UC_STRING || - ucv_type(funname) != UC_STRING || - (funargs && ucv_type(funargs) != UC_OBJECT)) - err_return(UBUS_STATUS_INVALID_ARGUMENT); + args_get(vm, nargs, + "object name", UC_STRING, false, &objname, + "function name", UC_STRING, false, &funname, + "function arguments", UC_OBJECT, true, &funargs); blob_buf_init(&(*c)->buf, 0); - if (funargs) { - o = ucv_to_json(funargs); - rv = blobmsg_add_object(&(*c)->buf, o); - json_object_put(o); - - if (!rv) - err_return(UBUS_STATUS_UNKNOWN_ERROR); - } + if (funargs) + ucv_object_to_blob(funargs, &(*c)->buf); rv = ubus_lookup_id((*c)->ctx, ucv_string_get(objname), &id); if (rv != UBUS_STATUS_OK) - err_return(rv); + err_return(rv, "Failed to resolve object name '%s'", + ucv_string_get(objname)); rv = ubus_invoke((*c)->ctx, id, ucv_string_get(funname), (*c)->buf.head, uc_ubus_call_cb, &res, (*c)->timeout * 1000); if (rv != UBUS_STATUS_OK) - err_return(rv); + err_return(rv, "Failed to invoke function '%s' on object '%s'", + ucv_string_get(funname), ucv_string_get(objname)); return res; } @@ -382,42 +684,30 @@ uc_ubus_call(uc_vm_t *vm, size_t nargs) static uc_value_t * uc_ubus_defer(uc_vm_t *vm, size_t nargs) { - ubus_connection **c = uc_fn_this("ubus.connection"); - uc_value_t *objname = uc_fn_arg(0); - uc_value_t *funname = uc_fn_arg(1); - uc_value_t *funargs = uc_fn_arg(2); - uc_value_t *replycb = uc_fn_arg(3); - uc_value_t *res = NULL; + uc_value_t *objname, *funname, *funargs, *replycb, *res = NULL; + uc_ubus_deferred_t *defer; + uc_ubus_connection_t **c; enum ubus_msg_status rv; - ubus_deferred *defer; - json_object *o; uint32_t id; - size_t i; - if (!c || !*c || !(*c)->ctx) - err_return(UBUS_STATUS_CONNECTION_FAILED); + conn_get(vm, &c); - if (ucv_type(objname) != UC_STRING || - ucv_type(funname) != UC_STRING || - (funargs && ucv_type(funargs) != UC_OBJECT) || - (replycb && !ucv_is_callable(replycb))) - err_return(UBUS_STATUS_INVALID_ARGUMENT); + args_get(vm, nargs, + "object name", UC_STRING, false, &objname, + "function name", UC_STRING, false, &funname, + "function arguments", UC_OBJECT, true, &funargs, + "reply callback", UC_CLOSURE, true, &replycb); blob_buf_init(&(*c)->buf, 0); - if (funargs) { - o = ucv_to_json(funargs); - rv = blobmsg_add_object(&(*c)->buf, o); - json_object_put(o); - - if (!rv) - err_return(UBUS_STATUS_UNKNOWN_ERROR); - } + if (funargs) + ucv_object_to_blob(funargs, &(*c)->buf); rv = ubus_lookup_id((*c)->ctx, ucv_string_get(objname), &id); if (rv != UBUS_STATUS_OK) - err_return(rv); + err_return(rv, "Failed to resolve object name '%s'", + ucv_string_get(objname)); defer = xalloc(sizeof(*defer)); @@ -426,8 +716,7 @@ uc_ubus_defer(uc_vm_t *vm, size_t nargs) if (rv == UBUS_STATUS_OK) { defer->vm = vm; - defer->context = (*c)->ctx; - defer->callback = replycb; + defer->ctx = (*c)->ctx; defer->request.data_cb = uc_ubus_call_data_cb; defer->request.complete_cb = uc_ubus_call_done_cb; @@ -438,14 +727,7 @@ uc_ubus_defer(uc_vm_t *vm, size_t nargs) res = uc_resource_new(defer_type, defer); - for (i = 0;; i += 2) { - if (ucv_array_get(cb_registry, i) == NULL) { - ucv_array_set(cb_registry, i, ucv_get(res)); - ucv_array_set(cb_registry, i + 1, ucv_get(replycb)); - n_cb_active++; - break; - } - } + defer->registry_index = request_reg_add(vm, ucv_get(res), ucv_get(replycb)); if (!uc_ubus_have_uloop()) { have_own_uloop = true; @@ -463,18 +745,1139 @@ uc_ubus_defer(uc_vm_t *vm, size_t nargs) } if (rv != UBUS_STATUS_OK) - err_return(rv); + err_return(rv, "Failed to invoke function '%s' on object '%s'", + ucv_string_get(funname), ucv_string_get(objname)); + + return res; +} + + +/* + * ubus object request context functions + * -------------------------------------------------------------------------- + */ + +static void +uc_ubus_request_finish(uc_ubus_request_t *callctx, int code, uc_value_t *reply) +{ + if (callctx->replied) + return; + + if (reply) { + blob_buf_init(&buf, 0); + ucv_object_to_blob(reply, &buf); + ubus_send_reply(callctx->ctx, &callctx->req, buf.head); + } + + callctx->replied = true; + + ubus_complete_deferred_request(callctx->ctx, &callctx->req, code); + request_reg_clear(callctx->vm, callctx->registry_index); +} + +static void +uc_ubus_request_timeout(struct uloop_timeout *timeout) +{ + uc_ubus_request_t *callctx = container_of(timeout, uc_ubus_request_t, timeout); + + uc_ubus_request_finish(callctx, UBUS_STATUS_TIMEOUT, NULL); +} + +static uc_value_t * +uc_ubus_request_reply(uc_vm_t *vm, size_t nargs) +{ + uc_ubus_request_t **callctx = uc_fn_this("ubus.request"); + int64_t code = UBUS_STATUS_OK; + uc_value_t *reply, *rcode; + + if (!callctx || !*callctx) + err_return(UBUS_STATUS_INVALID_ARGUMENT, "Invalid call context"); + + args_get(vm, nargs, + "reply", UC_OBJECT, true, &reply, + "rcode", UC_INTEGER, true, &rcode); + + if ((*callctx)->replied) + err_return(UBUS_STATUS_INVALID_ARGUMENT, "Reply has already been sent"); + + if (rcode) { + code = ucv_int64_get(rcode); + + if (errno == ERANGE || code < 0 || code > __UBUS_STATUS_LAST) + code = UBUS_STATUS_UNKNOWN_ERROR; + } + + uc_ubus_request_finish(*callctx, code, reply); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_ubus_request_error(uc_vm_t *vm, size_t nargs) +{ + uc_ubus_request_t **callctx = uc_fn_this("ubus.request"); + uc_value_t *rcode = uc_fn_arg(0); + int64_t code; + + if (!callctx || !*callctx) + err_return(UBUS_STATUS_INVALID_ARGUMENT, "Invalid call context"); + + args_get(vm, nargs, + "rcode", UC_INTEGER, false, &rcode); + + if ((*callctx)->replied) + err_return(UBUS_STATUS_INVALID_ARGUMENT, "Reply has already been sent"); + + code = ucv_int64_get(rcode); + + if (errno == ERANGE || code < 0 || code > __UBUS_STATUS_LAST) + code = UBUS_STATUS_UNKNOWN_ERROR; + + uc_ubus_request_finish(*callctx, code, NULL); + + return ucv_boolean_new(true); +} + + +/* + * ubus object notify + * -------------------------------------------------------------------------- + */ + +static uc_value_t * +uc_ubus_notify_completed(uc_vm_t *vm, size_t nargs) +{ + uc_ubus_notify_t **notifyctx = uc_fn_this("ubus.notify"); + + if (!notifyctx || !*notifyctx) + err_return(UBUS_STATUS_INVALID_ARGUMENT, "Invalid notify context"); + + return ucv_boolean_new((*notifyctx)->complete); +} + +static uc_value_t * +uc_ubus_notify_abort(uc_vm_t *vm, size_t nargs) +{ + uc_ubus_notify_t **notifyctx = uc_fn_this("ubus.notify"); + + if (!notifyctx || !*notifyctx) + err_return(UBUS_STATUS_INVALID_ARGUMENT, "Invalid notify context"); + + if ((*notifyctx)->complete) + return ucv_boolean_new(false); + + ubus_abort_request((*notifyctx)->ctx, &(*notifyctx)->req.req); + (*notifyctx)->complete = true; + + return ucv_boolean_new(true); +} + +static void +uc_ubus_object_notify_data_cb(struct ubus_notify_request *req, int type, struct blob_attr *msg) +{ + uc_ubus_notify_t *notifyctx = (uc_ubus_notify_t *)req; + uc_value_t *this, *func; + + notify_reg_get(notifyctx->vm, notifyctx->registry_index, &this, &func, NULL, NULL); + + if (ucv_is_callable(func)) { + uc_vm_stack_push(notifyctx->vm, ucv_get(this)); + uc_vm_stack_push(notifyctx->vm, ucv_get(func)); + uc_vm_stack_push(notifyctx->vm, ucv_int64_new(type)); + uc_vm_stack_push(notifyctx->vm, blob_array_to_ucv(notifyctx->vm, blob_data(msg), blob_len(msg), true)); + + if (uc_vm_call(notifyctx->vm, true, 2) == EXCEPTION_NONE) + ucv_put(uc_vm_stack_pop(notifyctx->vm)); + } +} + +static void +uc_ubus_object_notify_status_cb(struct ubus_notify_request *req, int idx, int ret) +{ + uc_ubus_notify_t *notifyctx = (uc_ubus_notify_t *)req; + uc_value_t *this, *func; + + notify_reg_get(notifyctx->vm, notifyctx->registry_index, &this, NULL, &func, NULL); + + if (ucv_is_callable(func)) { + uc_vm_stack_push(notifyctx->vm, ucv_get(this)); + uc_vm_stack_push(notifyctx->vm, ucv_get(func)); + uc_vm_stack_push(notifyctx->vm, ucv_int64_new(idx)); + uc_vm_stack_push(notifyctx->vm, ucv_int64_new(ret)); + + if (uc_vm_call(notifyctx->vm, true, 2) == EXCEPTION_NONE) + ucv_put(uc_vm_stack_pop(notifyctx->vm)); + } +} + +static void +uc_ubus_object_notify_complete_cb(struct ubus_notify_request *req, int idx, int ret) +{ + uc_ubus_notify_t *notifyctx = (uc_ubus_notify_t *)req; + uc_value_t *this, *func; + + notify_reg_get(notifyctx->vm, notifyctx->registry_index, &this, NULL, NULL, &func); + + if (ucv_is_callable(func)) { + uc_vm_stack_push(notifyctx->vm, ucv_get(this)); + uc_vm_stack_push(notifyctx->vm, ucv_get(func)); + uc_vm_stack_push(notifyctx->vm, ucv_int64_new(idx)); + uc_vm_stack_push(notifyctx->vm, ucv_int64_new(ret)); + + if (uc_vm_call(notifyctx->vm, true, 2) == EXCEPTION_NONE) + ucv_put(uc_vm_stack_pop(notifyctx->vm)); + } + + notifyctx->complete = true; + + notify_reg_clear(notifyctx->vm, notifyctx->registry_index); +} + +static uc_value_t * +uc_ubus_object_notify(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *typename, *message, *data_cb, *status_cb, *complete_cb, *timeout; + uc_ubus_object_t **uuobj = uc_fn_this("ubus.object"); + uc_ubus_notify_t *notifyctx; + uc_value_t *res; + int64_t t; + int rv; + + if (!uuobj || !*uuobj) + err_return(UBUS_STATUS_INVALID_ARGUMENT, "Invalid object context"); + + args_get(vm, nargs, + "typename", UC_STRING, false, &typename, + "message", UC_OBJECT, true, &message, + "data callback", UC_CLOSURE, true, &data_cb, + "status callback", UC_CLOSURE, true, &status_cb, + "completion callback", UC_CLOSURE, true, &complete_cb, + "timeout", UC_INTEGER, true, &timeout); + + t = timeout ? ucv_int64_get(timeout) : -1; + + if (errno) + err_return(UBUS_STATUS_INVALID_ARGUMENT, + "Invalid timeout value: %s", strerror(errno)); + + notifyctx = xalloc(sizeof(*notifyctx)); + notifyctx->vm = vm; + notifyctx->ctx = (*uuobj)->ctx; + + blob_buf_init(&buf, 0); + + if (message) + ucv_object_to_blob(message, &buf); + + rv = ubus_notify_async((*uuobj)->ctx, &(*uuobj)->obj, + ucv_string_get(typename), buf.head, + ¬ifyctx->req); + + if (rv != UBUS_STATUS_OK) { + free(notifyctx); + err_return(rv, "Failed to send notification"); + } + + notifyctx->req.data_cb = uc_ubus_object_notify_data_cb; + notifyctx->req.status_cb = uc_ubus_object_notify_status_cb; + notifyctx->req.complete_cb = uc_ubus_object_notify_complete_cb; + + res = uc_resource_new(notify_type, notifyctx); + + notifyctx->registry_index = notify_reg_add(vm, + ucv_get(res), ucv_get(data_cb), ucv_get(status_cb), ucv_get(complete_cb)); + + if (t >= 0) { + rv = ubus_complete_request((*uuobj)->ctx, ¬ifyctx->req.req, t); + + notify_reg_clear(vm, notifyctx->registry_index); + + ucv_put(res); + + return ucv_int64_new(rv); + } + + ubus_complete_request_async((*uuobj)->ctx, ¬ifyctx->req.req); return res; } + +/* + * ubus object remove + * -------------------------------------------------------------------------- + */ + +static int +uc_ubus_object_remove_common(uc_ubus_object_t *uuobj) +{ + int rv = ubus_remove_object(uuobj->ctx, &uuobj->obj); + + if (rv == UBUS_STATUS_OK) + object_reg_clear(uuobj->vm, uuobj->registry_index); + + return rv; +} + +static uc_value_t * +uc_ubus_object_remove(uc_vm_t *vm, size_t nargs) +{ + uc_ubus_object_t **uuobj = uc_fn_this("ubus.object"); + int rv; + + if (!uuobj || !*uuobj) + err_return(UBUS_STATUS_INVALID_ARGUMENT, "Invalid object context"); + + rv = uc_ubus_object_remove_common(*uuobj); + + if (rv != UBUS_STATUS_OK) + err_return(rv, "Failed to remove object"); + + return ucv_boolean_new(true); +} + + +/* + * ubus object subscription status + */ + +static uc_value_t * +uc_ubus_object_subscribed(uc_vm_t *vm, size_t nargs) +{ + uc_ubus_object_t **uuobj = uc_fn_this("ubus.object"); + + if (!uuobj || !*uuobj) + err_return(UBUS_STATUS_INVALID_ARGUMENT, "Invalid object context"); + + return ucv_boolean_new((*uuobj)->obj.has_subscribers); +} + + +/* + * ubus object method call handling + * -------------------------------------------------------------------------- + */ + +static int +uc_ubus_object_call_args(struct ubus_object *obj, const char *ubus_method_name, + struct blob_attr *msg, uc_value_t **res) +{ + uc_ubus_object_t *uuobj = (uc_ubus_object_t *)obj; + const struct ubus_method *method = NULL; + const struct blobmsg_hdr *hdr; + struct blob_attr *attr; + size_t len; + bool found; + int i; + + for (i = 0; i < obj->n_methods; i++) { + if (!strcmp(obj->methods[i].name, ubus_method_name)) { + method = &obj->methods[i]; + break; + } + } + + if (!method) + return UBUS_STATUS_METHOD_NOT_FOUND; + + len = blob_len(msg); + + __blob_for_each_attr(attr, blob_data(msg), len) { + if (!blobmsg_check_attr_len(attr, false, len)) + return UBUS_STATUS_INVALID_ARGUMENT; + + if (!blob_is_extended(attr)) + return UBUS_STATUS_INVALID_ARGUMENT; + + hdr = blob_data(attr); + found = false; + + for (i = 0; i < method->n_policy; i++) { + if (blobmsg_namelen(hdr) != strlen(method->policy[i].name)) + continue; + + if (strcmp(method->policy[i].name, (char *)hdr->name)) + continue; + + /* named argument found but wrong type */ + if (blob_id(attr) != method->policy[i].type) + goto inval; + + found = true; + break; + } + + /* named argument not found in policy */ + if (!found) + goto inval; + } + + *res = blob_array_to_ucv(uuobj->vm, blob_data(msg), blob_len(msg), true); + + return UBUS_STATUS_OK; + +inval: + *res = NULL; + + return UBUS_STATUS_INVALID_ARGUMENT; +} + +static uc_value_t * +uc_ubus_object_call_info(uc_vm_t *vm, + struct ubus_context *ctx, struct ubus_request_data *req, + struct ubus_object *obj, const char *ubus_method_name) +{ + uc_value_t *info, *o; + + info = ucv_object_new(vm); + + o = ucv_object_new(vm); + + ucv_object_add(o, "user", ucv_string_new(req->acl.user)); + ucv_object_add(o, "group", ucv_string_new(req->acl.group)); + + if (req->acl.object) + ucv_object_add(o, "object", ucv_string_new(req->acl.object)); + + ucv_object_add(info, "acl", o); + + o = ucv_object_new(vm); + + ucv_object_add(o, "id", ucv_int64_new(obj->id)); + + if (obj->name) + ucv_object_add(o, "name", ucv_string_new(obj->name)); + + if (obj->path) + ucv_object_add(o, "path", ucv_string_new(obj->path)); + + ucv_object_add(info, "object", o); + + if (ubus_method_name) + ucv_object_add(info, "method", ucv_string_new(ubus_method_name)); + + return info; +} + +static int +uc_ubus_handle_reply_common(struct ubus_context *ctx, + struct ubus_request_data *req, + uc_vm_t *vm, uc_value_t *this, uc_value_t *func, + uc_value_t *reqproto) +{ + uc_ubus_request_t *callctx; + uc_value_t *reqobj, *res; + int rv; + + /* allocate deferred method call context */ + callctx = xalloc(sizeof(*callctx)); + callctx->ctx = ctx; + callctx->vm = vm; + + ubus_defer_request(ctx, req, &callctx->req); + + /* create ucode request type object and set properties */ + reqobj = uc_resource_new(request_type, callctx); + + if (reqproto) + ucv_prototype_set(ucv_prototype_get(reqobj), reqproto); + + /* push object context, handler and request object onto stack */ + uc_vm_stack_push(vm, ucv_get(this)); + uc_vm_stack_push(vm, ucv_get(func)); + uc_vm_stack_push(vm, ucv_get(reqobj)); + + /* execute request handler function */ + switch (uc_vm_call(vm, true, 1)) { + case EXCEPTION_NONE: + res = uc_vm_stack_pop(vm); + + /* The handler function invoked a nested aync ubus request and returned it */ + if (ucv_resource_dataptr(res, "ubus.deferred")) { + /* Install guard timer in case the reply callback is never called */ + callctx->timeout.cb = uc_ubus_request_timeout; + uloop_timeout_set(&callctx->timeout, 10000 /* FIXME */); + + /* Add wrapped request context into registry to prevent GC'ing + * until reply or timeout occurred */ + callctx->registry_index = request_reg_add(vm, ucv_get(reqobj), NULL); + } + + + /* Otherwise, when the function returned an object, treat it as + * reply data and conclude deferred request immediately */ + else if (ucv_type(res) == UC_OBJECT) { + blob_buf_init(&buf, 0); + ucv_object_to_blob(res, &buf); + ubus_send_reply(ctx, &callctx->req, buf.head); + + ubus_complete_deferred_request(ctx, &callctx->req, UBUS_STATUS_OK); + callctx->replied = true; + } + + /* If neither a deferred ubus request, nor a plain object were + * returned and if reqobj.reply() hasn't been called, immediately + * finish deferred request with UBUS_STATUS_NO_DATA. */ + else if (!callctx->replied) { + ubus_complete_deferred_request(ctx, &callctx->req, UBUS_STATUS_NO_DATA); + callctx->replied = true; + } + + ucv_put(res); + break; + + /* if the handler function invoked exit(), forward exit status as ubus + * return code, map out of range values to UBUS_STATUS_UNKNOWN_ERROR. */ + case EXCEPTION_EXIT: + rv = vm->arg.s32; + + if (rv < UBUS_STATUS_OK || rv >= __UBUS_STATUS_LAST) + rv = UBUS_STATUS_UNKNOWN_ERROR; + + ubus_complete_deferred_request(ctx, &callctx->req, rv); + callctx->replied = true; + break; + + /* treat other exceptions as unknown error */ + default: + ubus_complete_deferred_request(ctx, &callctx->req, UBUS_STATUS_UNKNOWN_ERROR); + callctx->replied = true; + break; + } + + /* release request object */ + ucv_put(reqobj); + + /* garbage collect */ + ucv_gc(vm); + + return UBUS_STATUS_OK; +} + +static int +uc_ubus_object_call_cb(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *ubus_method_name, + struct blob_attr *msg) +{ + uc_value_t *this, *func, *args = NULL, *reqproto, *methods; + uc_ubus_object_t *uuobj = (uc_ubus_object_t *)obj; + int rv; + + object_reg_get(uuobj->vm, uuobj->registry_index, &this, &methods, NULL); + + func = ucv_object_get(ucv_object_get(methods, ubus_method_name, NULL), "call", NULL); + + if (!ucv_is_callable(func)) + return UBUS_STATUS_METHOD_NOT_FOUND; + + rv = uc_ubus_object_call_args(obj, ubus_method_name, msg, &args); + + if (rv != UBUS_STATUS_OK) + return rv; + + reqproto = ucv_object_new(uuobj->vm); + + ucv_object_add(reqproto, "args", args); + ucv_object_add(reqproto, "info", + uc_ubus_object_call_info(uuobj->vm, ctx, req, obj, ubus_method_name)); + + return uc_ubus_handle_reply_common(ctx, req, uuobj->vm, this, func, reqproto); +} + + +/* + * ubus object registration + * -------------------------------------------------------------------------- + */ + +static void +uc_ubus_object_subscribe_cb(struct ubus_context *ctx, struct ubus_object *obj) +{ + uc_ubus_object_t *uuobj = (uc_ubus_object_t *)obj; + uc_value_t *this, *func; + + object_reg_get(uuobj->vm, uuobj->registry_index, &this, NULL, &func); + + uc_vm_stack_push(uuobj->vm, ucv_get(this)); + uc_vm_stack_push(uuobj->vm, ucv_get(func)); + + if (uc_vm_call(uuobj->vm, true, 0) == EXCEPTION_NONE) + ucv_put(uc_vm_stack_pop(uuobj->vm)); + else + uloop_end(); +} + +static bool +uc_ubus_object_methods_validate(uc_value_t *methods) +{ + uc_value_t *func, *args; + + ucv_object_foreach(methods, ubus_method_name, ubus_method_definition) { + (void)ubus_method_name; + + func = ucv_object_get(ubus_method_definition, "call", NULL); + args = ucv_object_get(ubus_method_definition, "args", NULL); + + if (!ucv_is_callable(func)) + err_return(UBUS_STATUS_INVALID_ARGUMENT, + "Method '%s' field 'call' is not a function value", + ubus_method_name); + + if (args) { + if (ucv_type(args) != UC_OBJECT) + err_return(UBUS_STATUS_INVALID_ARGUMENT, + "Method '%s' field 'args' is not an object value", + ubus_method_name); + + ucv_object_foreach(args, ubus_argument_name, ubus_argument_typehint) { + (void)ubus_argument_name; + + switch (ucv_type(ubus_argument_typehint)) { + case UC_BOOLEAN: + case UC_INTEGER: + case UC_DOUBLE: + case UC_STRING: + case UC_ARRAY: + case UC_OBJECT: + continue; + + default: + err_return(UBUS_STATUS_INVALID_ARGUMENT, + "Method '%s' field 'args' argument '%s' hint has unsupported type %s", + ubus_method_name, ubus_argument_name, + ucv_typename(ubus_argument_typehint)); + } + } + } + } + + return true; +} + +static bool +uc_ubus_object_method_register(struct ubus_method *method, const char *ubus_method_name, + uc_value_t *ubus_method_arguments) +{ + struct blobmsg_policy *policy; + enum blobmsg_type type; + + method->name = strdup(ubus_method_name); + method->policy = calloc(ucv_object_length(ubus_method_arguments), sizeof(*method->policy)); + method->handler = uc_ubus_object_call_cb; + + if (!method->name || !method->policy) + return false; + + ucv_object_foreach(ubus_method_arguments, ubus_argument_name, ubus_argument_typehint) { + switch (ucv_type(ubus_argument_typehint)) { + case UC_BOOLEAN: + type = BLOBMSG_TYPE_INT8; + break; + + case UC_INTEGER: + switch (ucv_int64_get(ubus_argument_typehint)) { + 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; + } + + break; + + case UC_DOUBLE: + type = BLOBMSG_TYPE_DOUBLE; + break; + + case UC_ARRAY: + type = BLOBMSG_TYPE_ARRAY; + break; + + case UC_OBJECT: + type = BLOBMSG_TYPE_TABLE; + break; + + default: + type = BLOBMSG_TYPE_STRING; + break; + } + + policy = (struct blobmsg_policy *)&method->policy[method->n_policy++]; + policy->type = type; + policy->name = strdup(ubus_argument_name); + + if (!policy->name) + return false; + } + + return true; +} + +static uc_ubus_object_t * +uc_ubus_object_register(struct ubus_context *ctx, const char *ubus_object_name, + uc_value_t *ubus_object_methods) +{ + const struct blobmsg_policy *policy; + uc_ubus_object_t *uuobj = NULL; + int rv = UBUS_STATUS_UNKNOWN_ERROR; + char *tptr, *tnptr, *onptr, *mptr; + struct ubus_method *method; + struct ubus_object *obj; + size_t typelen, namelen; + uc_value_t *args; + + namelen = strlen(ubus_object_name); + typelen = strlen("ucode-ubus-") + namelen; + + uuobj = calloc_a(sizeof(*uuobj), + &onptr, namelen + 1, + &mptr, ucv_object_length(ubus_object_methods) * sizeof(struct ubus_method), + &tptr, sizeof(struct ubus_object_type), + &tnptr, typelen + 1); + + if (!uuobj) + err_return(rv, "Out of memory"); + + snprintf(tnptr, typelen, "ucode-ubus-%s", ubus_object_name); + + method = (struct ubus_method *)mptr; + + obj = &uuobj->obj; + obj->name = strncpy(onptr, ubus_object_name, namelen); + obj->methods = method; + + if (ubus_object_methods) { + ucv_object_foreach(ubus_object_methods, ubus_method_name, ubus_method_definition) { + args = ucv_object_get(ubus_method_definition, "args", NULL); + + if (!uc_ubus_object_method_register(&method[obj->n_methods++], ubus_method_name, args)) + goto out; + } + } + + obj->type = (struct ubus_object_type *)tptr; + obj->type->name = tnptr; + obj->type->methods = obj->methods; + obj->type->n_methods = obj->n_methods; + + rv = ubus_add_object(ctx, obj); + + if (rv == UBUS_STATUS_OK) + return uuobj; + +out: + for (; obj->n_methods > 0; method++, obj->n_methods--) { + for (policy = method->policy; method->n_policy > 0; policy++, method->n_policy--) + free((char *)policy->name); + + free((char *)method->name); + free((char *)method->policy); + } + + free(uuobj); + + err_return(rv, "Unable to add ubus object"); +} + +static uc_value_t * +uc_ubus_publish(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *objname, *methods, *subscribecb; + uc_ubus_connection_t **c; + uc_ubus_object_t *uuobj; + uc_value_t *res; + + conn_get(vm, &c); + + args_get(vm, nargs, + "object name", UC_STRING, false, &objname, + "object methods", UC_OBJECT, true, &methods, + "subscribe callback", UC_CLOSURE, true, &subscribecb); + + if (!methods && !subscribecb) + err_return(UBUS_STATUS_INVALID_ARGUMENT, "Either methods or subscribe callback required"); + + if (methods && !uc_ubus_object_methods_validate(methods)) + return NULL; + + uuobj = uc_ubus_object_register((*c)->ctx, ucv_string_get(objname), methods); + + if (!uuobj) + return NULL; + + if (subscribecb) + uuobj->obj.subscribe_cb = uc_ubus_object_subscribe_cb; + + res = uc_resource_new(object_type, uuobj); + + uuobj->vm = vm; + uuobj->ctx = (*c)->ctx; + uuobj->registry_index = object_reg_add(vm, ucv_get(res), ucv_get(methods), ucv_get(subscribecb)); + + return res; +} + + +/* + * ubus events + * -------------------------------------------------------------------------- + */ + +static int +uc_ubus_listener_remove_common(uc_ubus_listener_t *uul) +{ + int rv = ubus_unregister_event_handler(uul->ctx, &uul->ev); + + if (rv == UBUS_STATUS_OK) + listener_reg_clear(uul->vm, uul->registry_index); + + return rv; +} + +static uc_value_t * +uc_ubus_listener_remove(uc_vm_t *vm, size_t nargs) +{ + uc_ubus_listener_t **uul = uc_fn_this("ubus.listener"); + int rv; + + if (!uul || !*uul) + err_return(UBUS_STATUS_INVALID_ARGUMENT, "Invalid listener context"); + + rv = uc_ubus_listener_remove_common(*uul); + + if (rv != UBUS_STATUS_OK) + err_return(rv, "Failed to remove listener object"); + + return ucv_boolean_new(true); +} + +static void +uc_ubus_listener_cb(struct ubus_context *ctx, struct ubus_event_handler *ev, + const char *type, struct blob_attr *msg) +{ + uc_ubus_listener_t *uul = (uc_ubus_listener_t *)ev; + uc_value_t *this, *func; + + listener_reg_get(uul->vm, uul->registry_index, &this, &func); + + uc_vm_stack_push(uul->vm, ucv_get(this)); + uc_vm_stack_push(uul->vm, ucv_get(func)); + uc_vm_stack_push(uul->vm, ucv_string_new(type)); + uc_vm_stack_push(uul->vm, blob_array_to_ucv(uul->vm, blob_data(msg), blob_len(msg), true)); + + if (uc_vm_call(uul->vm, true, 2) == EXCEPTION_NONE) + ucv_put(uc_vm_stack_pop(uul->vm)); + else + uloop_end(); +} + +static uc_value_t * +uc_ubus_listener(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *cb, *pattern; + uc_ubus_connection_t **c; + uc_ubus_listener_t *uul; + uc_value_t *res; + int rv; + + conn_get(vm, &c); + + args_get(vm, nargs, + "event type pattern", UC_STRING, false, &pattern, + "event callback", UC_CLOSURE, false, &cb); + + uul = xalloc(sizeof(*uul)); + uul->vm = vm; + uul->ctx = (*c)->ctx; + uul->ev.cb = uc_ubus_listener_cb; + + rv = ubus_register_event_handler((*c)->ctx, &uul->ev, + ucv_string_get(pattern)); + + if (rv != UBUS_STATUS_OK) { + free(uul); + err_return(rv, "Failed to register listener object"); + } + + res = uc_resource_new(listener_type, uul); + + uul->registry_index = listener_reg_add(vm, ucv_get(res), ucv_get(cb)); + + return res; +} + +static uc_value_t * +uc_ubus_event(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *eventtype, *eventdata; + uc_ubus_connection_t **c; + int rv; + + conn_get(vm, &c); + + args_get(vm, nargs, + "event id", UC_STRING, false, &eventtype, + "event data", UC_OBJECT, true, &eventdata); + + blob_buf_init(&buf, 0); + + if (eventdata) + ucv_object_to_blob(eventdata, &buf); + + rv = ubus_send_event((*c)->ctx, ucv_string_get(eventtype), buf.head); + + if (rv != UBUS_STATUS_OK) + err_return(rv, "Unable to send event"); + + return ucv_boolean_new(true); +} + + +/* + * ubus subscriptions + * -------------------------------------------------------------------------- + */ + +static int +uc_ubus_subscriber_notify_cb(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct ubus_subscriber *sub = container_of(obj, struct ubus_subscriber, obj); + uc_ubus_subscriber_t *uusub = container_of(sub, uc_ubus_subscriber_t, sub); + uc_value_t *this, *func, *reqproto; + + subscriber_reg_get(uusub->vm, uusub->registry_index, &this, &func, NULL); + + if (!ucv_is_callable(func)) + return UBUS_STATUS_METHOD_NOT_FOUND; + + reqproto = ucv_object_new(uusub->vm); + + ucv_object_add(reqproto, "type", ucv_string_new(method)); + + ucv_object_add(reqproto, "data", + blob_array_to_ucv(uusub->vm, blob_data(msg), blob_len(msg), true)); + + ucv_object_add(reqproto, "info", + uc_ubus_object_call_info(uusub->vm, ctx, req, obj, NULL)); + + return uc_ubus_handle_reply_common(ctx, req, uusub->vm, this, func, reqproto); +} + +static void +uc_ubus_subscriber_remove_cb(struct ubus_context *ctx, + struct ubus_subscriber *sub, uint32_t id) +{ + uc_ubus_subscriber_t *uusub = container_of(sub, uc_ubus_subscriber_t, sub); + uc_value_t *this, *func; + + subscriber_reg_get(uusub->vm, uusub->registry_index, &this, NULL, &func); + + if (!ucv_is_callable(func)) + return; + + uc_vm_stack_push(uusub->vm, ucv_get(this)); + uc_vm_stack_push(uusub->vm, ucv_get(func)); + uc_vm_stack_push(uusub->vm, ucv_uint64_new(id)); + + if (uc_vm_call(uusub->vm, true, 1) == EXCEPTION_NONE) + ucv_put(uc_vm_stack_pop(uusub->vm)); + else + uloop_end(); +} + +static uc_value_t * +uc_ubus_subscriber_subunsub_common(uc_vm_t *vm, size_t nargs, bool subscribe) +{ + uc_ubus_subscriber_t **uusub = uc_fn_this("ubus.subscriber"); + uc_value_t *objname; + uint32_t id; + int rv; + + if (!uusub || !*uusub) + err_return(UBUS_STATUS_INVALID_ARGUMENT, "Invalid subscriber context"); + + args_get(vm, nargs, + "object name", UC_STRING, false, &objname); + + rv = ubus_lookup_id((*uusub)->ctx, ucv_string_get(objname), &id); + + if (rv != UBUS_STATUS_OK) + err_return(rv, "Failed to resolve object name '%s'", + ucv_string_get(objname)); + + if (subscribe) + rv = ubus_subscribe((*uusub)->ctx, &(*uusub)->sub, id); + else + rv = ubus_unsubscribe((*uusub)->ctx, &(*uusub)->sub, id); + + if (rv != UBUS_STATUS_OK) + err_return(rv, "Failed to %s object '%s'", + subscribe ? "subscribe" : "unsubscribe", + ucv_string_get(objname)); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_ubus_subscriber_subscribe(uc_vm_t *vm, size_t nargs) +{ + return uc_ubus_subscriber_subunsub_common(vm, nargs, true); +} + +static uc_value_t * +uc_ubus_subscriber_unsubscribe(uc_vm_t *vm, size_t nargs) +{ + return uc_ubus_subscriber_subunsub_common(vm, nargs, false); +} + +static int +uc_ubus_subscriber_remove_common(uc_ubus_subscriber_t *uusub) +{ + int rv = ubus_unregister_subscriber(uusub->ctx, &uusub->sub); + + if (rv == UBUS_STATUS_OK) + subscriber_reg_clear(uusub->vm, uusub->registry_index); + + return rv; +} + +static uc_value_t * +uc_ubus_subscriber_remove(uc_vm_t *vm, size_t nargs) +{ + uc_ubus_subscriber_t **uusub = uc_fn_this("ubus.subscriber"); + int rv; + + if (!uusub || !*uusub) + err_return(UBUS_STATUS_INVALID_ARGUMENT, "Invalid subscriber context"); + + rv = uc_ubus_subscriber_remove_common(*uusub); + + if (rv != UBUS_STATUS_OK) + err_return(rv, "Failed to remove subscriber object"); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_ubus_subscriber(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *notify_cb, *remove_cb; + uc_ubus_subscriber_t *uusub; + uc_ubus_connection_t **c; + uc_value_t *res; + int rv; + + conn_get(vm, &c); + + args_get(vm, nargs, + "notify callback", UC_CLOSURE, true, ¬ify_cb, + "remove callback", UC_CLOSURE, true, &remove_cb); + + if (!notify_cb && !remove_cb) + err_return(UBUS_STATUS_INVALID_ARGUMENT, "Either notify or remove callback required"); + + uusub = xalloc(sizeof(*uusub)); + uusub->vm = vm; + uusub->ctx = (*c)->ctx; + + rv = ubus_register_subscriber((*c)->ctx, &uusub->sub); + + if (rv != UBUS_STATUS_OK) { + free(uusub); + err_return(rv, "Failed to register subscriber object"); + } + + if (notify_cb) + uusub->sub.cb = uc_ubus_subscriber_notify_cb; + + if (remove_cb) + uusub->sub.remove_cb = uc_ubus_subscriber_remove_cb; + + res = uc_resource_new(subscriber_type, uusub); + + uusub->registry_index = subscriber_reg_add(vm, + ucv_get(res), ucv_get(notify_cb), ucv_get(remove_cb)); + + return res; +} + + +/* + * connection methods + * -------------------------------------------------------------------------- + */ + +static uc_value_t * +uc_ubus_remove(uc_vm_t *vm, size_t nargs) +{ + uc_ubus_subscriber_t **uusub; + uc_ubus_connection_t **c; + uc_ubus_object_t **uuobj; + uc_ubus_listener_t **uul; + int rv; + + conn_get(vm, &c); + + uusub = (uc_ubus_subscriber_t **)ucv_resource_dataptr(uc_fn_arg(0), "ubus.subscriber"); + uuobj = (uc_ubus_object_t **)ucv_resource_dataptr(uc_fn_arg(0), "ubus.object"); + uul = (uc_ubus_listener_t **)ucv_resource_dataptr(uc_fn_arg(0), "ubus.listener"); + + if (uusub && *uusub) { + if ((*uusub)->ctx != (*c)->ctx) + err_return(UBUS_STATUS_INVALID_ARGUMENT, + "Subscriber belongs to different connection"); + + rv = uc_ubus_subscriber_remove_common(*uusub); + + if (rv != UBUS_STATUS_OK) + err_return(rv, "Unable to remove subscriber"); + } + else if (uuobj && *uuobj) { + if ((*uuobj)->ctx != (*c)->ctx) + err_return(UBUS_STATUS_INVALID_ARGUMENT, + "Object belongs to different connection"); + + rv = uc_ubus_object_remove_common(*uuobj); + + if (rv != UBUS_STATUS_OK) + err_return(rv, "Unable to remove object"); + } + else if (uul && *uul) { + if ((*uul)->ctx != (*c)->ctx) + err_return(UBUS_STATUS_INVALID_ARGUMENT, + "Listener belongs to different connection"); + + rv = uc_ubus_listener_remove_common(*uul); + + if (rv != UBUS_STATUS_OK) + err_return(rv, "Unable to remove listener"); + } + else { + err_return(UBUS_STATUS_INVALID_ARGUMENT, "Unhandled resource type"); + } + + return ucv_boolean_new(true); +} + + static uc_value_t * uc_ubus_disconnect(uc_vm_t *vm, size_t nargs) { - ubus_connection **c = uc_fn_this("ubus.connection"); + uc_ubus_connection_t **c; - if (!c || !*c || !(*c)->ctx) - err_return(UBUS_STATUS_CONNECTION_FAILED); + conn_get(vm, &c); ubus_free((*c)->ctx); (*c)->ctx = NULL; @@ -483,9 +1886,9 @@ uc_ubus_disconnect(uc_vm_t *vm, size_t nargs) } static uc_value_t * -uc_ubus_defer_complete(uc_vm_t *vm, size_t nargs) +uc_ubus_defer_completed(uc_vm_t *vm, size_t nargs) { - ubus_deferred **d = uc_fn_this("ubus.deferred"); + uc_ubus_deferred_t **d = uc_fn_this("ubus.deferred"); if (!d || !*d) return NULL; @@ -496,9 +1899,7 @@ uc_ubus_defer_complete(uc_vm_t *vm, size_t nargs) static uc_value_t * uc_ubus_defer_abort(uc_vm_t *vm, size_t nargs) { - ubus_deferred **d = uc_fn_this("ubus.deferred"); - uc_resource_t *r; - size_t i; + uc_ubus_deferred_t **d = uc_fn_this("ubus.deferred"); if (!d || !*d) return NULL; @@ -506,25 +1907,16 @@ uc_ubus_defer_abort(uc_vm_t *vm, size_t nargs) if ((*d)->complete) return ucv_boolean_new(false); - ubus_abort_request((*d)->context, &(*d)->request); + ubus_abort_request((*d)->ctx, &(*d)->request); uloop_timeout_cancel(&(*d)->timeout); - for (i = 0; i < ucv_array_length(cb_registry); i += 2) { - r = (uc_resource_t *)ucv_array_get(cb_registry, i); - - if (r && r->data == *d) { - ucv_array_set(cb_registry, i, NULL); - ucv_array_set(cb_registry, i + 1, NULL); - break; - } - } + request_reg_clear((*d)->vm, (*d)->registry_index); n_cb_active--; if (have_own_uloop && n_cb_active == 0) uloop_end(); - (*d)->callback = NULL; (*d)->complete = true; return ucv_boolean_new(true); @@ -532,26 +1924,56 @@ uc_ubus_defer_abort(uc_vm_t *vm, size_t nargs) static const uc_function_list_t global_fns[] = { - { "error", uc_ubus_error }, - { "connect", uc_ubus_connect }, + { "error", uc_ubus_error }, + { "connect", uc_ubus_connect }, }; static const uc_function_list_t conn_fns[] = { - { "list", uc_ubus_list }, - { "call", uc_ubus_call }, - { "defer", uc_ubus_defer }, - { "error", uc_ubus_error }, - { "disconnect", uc_ubus_disconnect }, + { "list", uc_ubus_list }, + { "call", uc_ubus_call }, + { "defer", uc_ubus_defer }, + { "publish", uc_ubus_publish }, + { "remove", uc_ubus_remove }, + { "listener", uc_ubus_listener }, + { "subscriber", uc_ubus_subscriber }, + { "event", uc_ubus_event }, + { "error", uc_ubus_error }, + { "disconnect", uc_ubus_disconnect }, }; static const uc_function_list_t defer_fns[] = { - { "complete", uc_ubus_defer_complete }, - { "abort", uc_ubus_defer_abort }, + { "completed", uc_ubus_defer_completed }, + { "abort", uc_ubus_defer_abort }, +}; + +static const uc_function_list_t object_fns[] = { + { "subscribed", uc_ubus_object_subscribed }, + { "notify", uc_ubus_object_notify }, + { "remove", uc_ubus_object_remove }, +}; + +static const uc_function_list_t request_fns[] = { + { "reply", uc_ubus_request_reply }, + { "error", uc_ubus_request_error }, }; +static const uc_function_list_t notify_fns[] = { + { "completed", uc_ubus_notify_completed }, + { "abort", uc_ubus_notify_abort }, +}; -static void close_connection(void *ud) { - ubus_connection *conn = ud; +static const uc_function_list_t listener_fns[] = { + { "remove", uc_ubus_listener_remove }, +}; + +static const uc_function_list_t subscriber_fns[] = { + { "subscribe", uc_ubus_subscriber_subscribe }, + { "unsubscribe", uc_ubus_subscriber_unsubscribe }, + { "remove", uc_ubus_subscriber_remove }, +}; + +static void free_connection(void *ud) { + uc_ubus_connection_t *conn = ud; blob_buf_free(&conn->buf); @@ -561,21 +1983,64 @@ static void close_connection(void *ud) { free(conn); } -static void close_deferred(void *ud) { - ubus_deferred *defer = ud; +static void free_deferred(void *ud) { + uc_ubus_deferred_t *defer = ud; uloop_timeout_cancel(&defer->timeout); ucv_put(defer->response); free(defer); } +static void free_object(void *ud) { + uc_ubus_object_t *uuobj = ud; + struct ubus_object *obj = &uuobj->obj; + int i, j; + + for (i = 0; i < obj->n_methods; i++) { + for (j = 0; j < obj->methods[i].n_policy; j++) + free((char *)obj->methods[i].policy[j].name); + + free((char *)obj->methods[i].name); + free((char *)obj->methods[i].policy); + } + + free(uuobj); +} + +static void free_request(void *ud) { + uc_ubus_request_t *callctx = ud; + + uloop_timeout_cancel(&callctx->timeout); + free(callctx); +} + +static void free_notify(void *ud) { + uc_ubus_notify_t *notifyctx = ud; + + free(notifyctx); +} + +static void free_listener(void *ud) { + uc_ubus_listener_t *listener = ud; + + free(listener); +} + +static void free_subscriber(void *ud) { + uc_ubus_subscriber_t *subscriber = ud; + + free(subscriber); +} + void uc_module_init(uc_vm_t *vm, uc_value_t *scope) { uc_function_list_register(scope, global_fns); - conn_type = uc_type_declare(vm, "ubus.connection", conn_fns, close_connection); - defer_type = uc_type_declare(vm, "ubus.deferred", defer_fns, close_deferred); - cb_registry = ucv_array_new(vm); - - uc_vm_registry_set(vm, "ubus.cb_registry", cb_registry); + conn_type = uc_type_declare(vm, "ubus.connection", conn_fns, free_connection); + defer_type = uc_type_declare(vm, "ubus.deferred", defer_fns, free_deferred); + object_type = uc_type_declare(vm, "ubus.object", object_fns, free_object); + notify_type = uc_type_declare(vm, "ubus.notify", notify_fns, free_notify); + request_type = uc_type_declare(vm, "ubus.request", request_fns, free_request); + listener_type = uc_type_declare(vm, "ubus.listener", listener_fns, free_listener); + subscriber_type = uc_type_declare(vm, "ubus.subscriber", subscriber_fns, free_subscriber); } diff --git a/lib/uloop.c b/lib/uloop.c index 8b5adff..bc57336 100644 --- a/lib/uloop.c +++ b/lib/uloop.c @@ -111,6 +111,7 @@ uc_uloop_run(uc_vm_t *vm, size_t nargs) uc_value_t *timeout = uc_fn_arg(0); int t, rv; + errno = 0; t = timeout ? (int)ucv_int64_get(timeout) : -1; if (errno) @@ -182,6 +183,7 @@ uc_uloop_timer_set(uc_vm_t *vm, size_t nargs) if (!timer || !*timer) err_return(EINVAL); + errno = 0; t = timeout ? (int)ucv_int64_get(timeout) : -1; if (errno) @@ -243,6 +245,7 @@ uc_uloop_timer(uc_vm_t *vm, size_t nargs) uc_value_t *res; int t; + errno = 0; t = timeout ? ucv_int64_get(timeout) : -1; if (errno) |