From: Felix Fietkau Date: Mon, 6 Dec 2010 02:51:58 +0000 (+0100) Subject: Initial import X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=dbd4c2f121be08e514828f2533687b145a6e16dd;p=oweals%2Fubus.git Initial import --- dbd4c2f121be08e514828f2533687b145a6e16dd diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..210271d --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +Makefile +CMakeCache.txt +CMakeFiles +*.cmake +*.a +ubus.sock +listener +ubusd +ubus diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3114aa9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 2.8) + +PROJECT(ubus C) +ADD_DEFINITIONS(-Os -Wall -Werror --std=gnu99 -g3) +ADD_LIBRARY(ubus STATIC libubus.c) + +ADD_EXECUTABLE(ubusd ubusd.c ubusd_id.c ubusd_obj.c ubusd_proto.c) +TARGET_LINK_LIBRARIES(ubusd ubox) + +ADD_EXECUTABLE(cli cli.c) +SET_TARGET_PROPERTIES(cli PROPERTIES OUTPUT_NAME ubus) +TARGET_LINK_LIBRARIES(cli ubus ubox) + +ADD_EXECUTABLE(listener listener.c) +TARGET_LINK_LIBRARIES(listener ubus ubox) + diff --git a/cli.c b/cli.c new file mode 100644 index 0000000..ba6f30f --- /dev/null +++ b/cli.c @@ -0,0 +1,73 @@ +#include "libubus.h" + +static struct blob_buf b; +static struct ubus_context *ctx; + +static void receive_lookup(struct ubus_request *req, int type, struct blob_attr *msg) +{ + struct blob_attr **attr, *cur; + char *s; + int rem; + + attr = ubus_parse_msg(msg); + if (!attr[UBUS_ATTR_OBJID] || !attr[UBUS_ATTR_OBJPATH]) + return; + + fprintf(stderr, "'%s' @%08x\n", + (char *) blob_data(attr[UBUS_ATTR_OBJPATH]), + blob_get_int32(attr[UBUS_ATTR_OBJID])); + + if (!attr[UBUS_ATTR_SIGNATURE]) + return; + + blob_for_each_attr(cur, attr[UBUS_ATTR_SIGNATURE], rem) { + s = blobmsg_format_json(cur, false); + fprintf(stderr, "\t%s\n", s); + free(s); + } +} + +static int usage(char *prog) +{ + fprintf(stderr, + "Usage: %s [arguments...]\n" + "Commands:\n" + " - list [] List objects\n" + "\n", prog); + return 1; +} + +int main(int argc, char **argv) +{ + struct ubus_request req; + char *cmd; + int ret; + + ctx = ubus_connect(NULL); + if (!ctx) { + fprintf(stderr, "Failed to connect to ubus\n"); + return -1; + } + + cmd = argv[1]; + if (argc < 2) + return usage(argv[0]); + + blob_buf_init(&b, 0); + + if (!strcmp(cmd, "list")) { + if (argc == 3) + blob_put_string(&b, UBUS_ATTR_OBJPATH, argv[2]); + + ubus_start_request(ctx, &req, b.head, UBUS_MSG_LOOKUP, 0); + req.data_cb = receive_lookup; + ret = ubus_complete_request(ctx, &req); + if (ret) + fprintf(stderr, "Failed: %d\n", ret); + } else { + return usage(argv[0]); + } + + ubus_free(ctx); + return 0; +} diff --git a/libubus.c b/libubus.c new file mode 100644 index 0000000..5fdde30 --- /dev/null +++ b/libubus.c @@ -0,0 +1,515 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include "libubus.h" +#include "ubusmsg.h" + +#define DEBUG 1 + +#ifdef DEBUG +#define DPRINTF(_format, ...) fprintf(stderr, "ubus: " _format, ## __VA_ARGS__) +#else +#define DPRINTF(...) do {} while(0) +#endif + +#define STATIC_IOV(_var) { .iov_base = (char *) &(_var), .iov_len = sizeof(_var) } + +const char *__ubus_strerror[__UBUS_STATUS_LAST] = { + [UBUS_STATUS_OK] = "Success", + [UBUS_STATUS_INVALID_COMMAND] = "Invalid command", + [UBUS_STATUS_INVALID_ARGUMENT] = "Invalid argument", + [UBUS_STATUS_NOT_FOUND] = "Not found", + [UBUS_STATUS_NO_DATA] = "No response", +}; + +static struct blob_buf b; + +static const struct blob_attr_info ubus_policy[UBUS_ATTR_MAX] = { + [UBUS_ATTR_STATUS] = { .type = BLOB_ATTR_INT32 }, + [UBUS_ATTR_OBJID] = { .type = BLOB_ATTR_INT32 }, + [UBUS_ATTR_OBJPATH] = { .type = BLOB_ATTR_STRING }, +}; +static struct blob_attr *attrbuf[UBUS_ATTR_MAX]; + +struct ubus_pending_data { + struct list_head list; + int type; + struct blob_attr data[]; +}; + +struct blob_attr **ubus_parse_msg(struct blob_attr *msg) +{ + blob_parse(msg, attrbuf, ubus_policy, UBUS_ATTR_MAX); + return attrbuf; +} + +const char *ubus_strerror(int error) +{ + static char err[32]; + + if (error < 0 || error >= __UBUS_STATUS_LAST) + goto out; + + if (!__ubus_strerror[error]) + goto out; + + return __ubus_strerror[error]; + +out: + sprintf(err, "Unknown error: %d", error); + return err; +} + +int ubus_start_request(struct ubus_context *ctx, struct ubus_request *req, + struct blob_attr *msg, int cmd, uint32_t peer) +{ + struct ubus_msghdr hdr; + struct iovec iov[2] = { + STATIC_IOV(hdr) + }; + + memset(req, 0, sizeof(*req)); + hdr.version = 0; + hdr.type = cmd; + hdr.seq = ++ctx->request_seq; + hdr.peer = peer; + + req->peer = hdr.peer; + req->seq = hdr.seq; + + if (!msg) { + blob_buf_init(&b, 0); + msg = b.head; + } + + iov[1].iov_base = (char *) msg; + iov[1].iov_len = blob_raw_len(msg); + INIT_LIST_HEAD(&req->list); + INIT_LIST_HEAD(&req->pending); + + return writev(ctx->sock.fd, iov, 2); +} + +static bool recv_retry(int fd, struct iovec *iov, bool wait) +{ + int bytes; + + while (iov->iov_len > 0) { + bytes = read(fd, iov->iov_base, iov->iov_len); + if (bytes < 0) { + bytes = 0; + if (errno == EINTR) + continue; + + if (errno != EAGAIN) { + perror("read"); + return false; + } + } + if (!wait && !bytes) + return false; + + wait = true; + iov->iov_len -= bytes; + iov->iov_base += bytes; + } + + return true; +} + +static bool ubus_validate_hdr(struct ubus_msghdr *hdr) +{ + if (hdr->version != 0) + return false; + + if (blob_raw_len(hdr->data) < sizeof(*hdr->data)) + return false; + + if (blob_raw_len(hdr->data) + sizeof(*hdr) > UBUS_MAX_MSGLEN) + return false; + + return true; +} + +static bool get_next_msg(struct ubus_context *ctx, bool wait) +{ + struct iovec iov = STATIC_IOV(ctx->msgbuf.hdr); + + /* receive header + start attribute */ + iov.iov_len += sizeof(struct blob_attr); + if (!recv_retry(ctx->sock.fd, &iov, wait)) + return false; + + iov.iov_len = blob_len(ctx->msgbuf.hdr.data); + if (iov.iov_len > 0 && !recv_retry(ctx->sock.fd, &iov, true)) + return false; + + return ubus_validate_hdr(&ctx->msgbuf.hdr); +} + +static bool ubus_get_status(struct ubus_msghdr *hdr, int *ret) +{ + ubus_parse_msg(hdr->data); + + if (!attrbuf[UBUS_ATTR_STATUS]) + return false; + + *ret = blob_get_int32(attrbuf[UBUS_ATTR_STATUS]); + return true; +} + +static void ubus_process_req_data(struct ubus_request *req) +{ + struct ubus_pending_data *data; + + while (!list_empty(&req->pending)) { + data = list_first_entry(&req->pending, + struct ubus_pending_data, list); + list_del(&data->list); + req->data_cb(req, data->type, data->data); + free(data); + } +} + +static void ubus_req_complete_cb(struct ubus_request *req) +{ + ubus_complete_handler_t cb = req->complete_cb; + + if (!cb) + return; + + req->complete_cb = NULL; + cb(req, req->status_code); +} + +static int ubus_process_req_status(struct ubus_request *req, struct ubus_msghdr *hdr) +{ + int ret = UBUS_STATUS_INVALID_ARGUMENT; + + if (!list_empty(&req->list)) + list_del(&req->list); + + ubus_get_status(hdr, &ret); + req->peer = hdr->peer; + req->status_msg = true; + req->status_code = ret; + if (!req->blocked) + ubus_req_complete_cb(req); + + return ret; +} + +static void ubus_req_data(struct ubus_request *req, struct ubus_msghdr *hdr) +{ + struct ubus_pending_data *data; + int len; + + if (!req->blocked) { + req->blocked = true; + req->data_cb(req, hdr->type, hdr->data); + ubus_process_req_data(req); + req->blocked = false; + + if (req->status_msg) + ubus_req_complete_cb(req); + + return; + } + + len = blob_raw_len(hdr->data); + data = calloc(1, sizeof(*data) + len); + if (!data) + return; + + data->type = hdr->type; + memcpy(data->data, hdr->data, len); + list_add(&data->list, &req->pending); +} + +static void ubus_process_msg(struct ubus_context *ctx, struct ubus_msghdr *hdr) +{ + struct ubus_request *req; + + list_for_each_entry(req, &ctx->requests, list) { + if (hdr->seq != req->seq || hdr->peer != req->peer) + continue; + + switch(hdr->type) { + case UBUS_MSG_STATUS: + ubus_process_req_status(req, hdr); + return; + case UBUS_MSG_DATA: + if (req->data_cb) + ubus_req_data(req, hdr); + break; + default: + DPRINTF("unknown message type: %d\n", hdr->type); + break; + } + } +} + +void ubus_abort_request(struct ubus_context *ctx, struct ubus_request *req) +{ + if (!list_empty(&req->list)) + return; + + list_del(&req->list); +} + +void ubus_complete_request_async(struct ubus_context *ctx, struct ubus_request *req) +{ + if (!list_empty(&req->list)) + return; + + list_add(&req->list, &ctx->requests); +} + +static void ubus_handle_data(struct uloop_fd *u, unsigned int events) +{ + struct ubus_context *ctx = container_of(u, struct ubus_context, sock); + struct ubus_msghdr *hdr = &ctx->msgbuf.hdr; + + while (get_next_msg(ctx, false)) + ubus_process_msg(ctx, hdr); +} + +int ubus_complete_request(struct ubus_context *ctx, struct ubus_request *req) +{ + struct ubus_msghdr *hdr = &ctx->msgbuf.hdr; + + if (!list_empty(&req->list)) + list_del(&req->list); + + while (1) { + if (req->status_msg) + return req->status_code; + + if (!get_next_msg(ctx, true)) + return UBUS_STATUS_NO_DATA; + + if (hdr->seq != req->seq || hdr->peer != req->peer) + goto skip; + + switch(hdr->type) { + case UBUS_MSG_STATUS: + return ubus_process_req_status(req, hdr); + case UBUS_MSG_DATA: + if (req->data_cb) + ubus_req_data(req, hdr); + continue; + default: + DPRINTF("unknown message type: %d\n", hdr->type); + continue; + } + +skip: + ubus_process_msg(ctx, hdr); + } +} + +void ubus_invoke_async(struct ubus_context *ctx, uint32_t obj, const char *method, + struct blob_attr *msg, struct ubus_request *req) +{ + blob_buf_init(&b, 0); + blob_put_int32(&b, UBUS_ATTR_OBJID, obj); + blob_put_string(&b, UBUS_ATTR_METHOD, method); + blob_put(&b, UBUS_ATTR_DATA, blob_data(msg), blob_len(msg)); + + ubus_start_request(ctx, req, b.head, UBUS_MSG_INVOKE, obj); +} + +int ubus_invoke(struct ubus_context *ctx, uint32_t obj, const char *method, + struct blob_attr *msg, ubus_data_handler_t cb, void *priv) +{ + struct ubus_request req; + + ubus_invoke_async(ctx, obj, method, msg, &req); + req.data_cb = cb; + req.priv = priv; + return ubus_complete_request(ctx, &req); +} + +static void ubus_publish_cb(struct ubus_request *req, int type, struct blob_attr *msg) +{ + struct ubus_object *obj = req->priv; + + ubus_parse_msg(msg); + + if (!attrbuf[UBUS_ATTR_OBJID]) + return; + + obj->id = blob_get_int32(attrbuf[UBUS_ATTR_OBJID]); + + if (attrbuf[UBUS_ATTR_OBJTYPE]) + obj->type->id = blob_get_int32(attrbuf[UBUS_ATTR_OBJTYPE]); +} + +static bool ubus_push_table_data(const struct ubus_signature **sig, int *rem, bool array) +{ + const struct ubus_signature *cur; + bool nest_type; + void *nest; + + while (rem) { + cur = (*sig)++; + (*rem)--; + switch(cur->type) { + case UBUS_SIGNATURE_END: + return !array; + case BLOBMSG_TYPE_INT32: + case BLOBMSG_TYPE_STRING: + blobmsg_add_u32(&b, cur->name, cur->type); + break; + case BLOBMSG_TYPE_TABLE: + case BLOBMSG_TYPE_ARRAY: + nest_type = cur->type == BLOBMSG_TYPE_ARRAY; + nest = blobmsg_open_nested(&b, cur->name, nest_type); + if (!ubus_push_table_data(sig, rem, nest_type)) + return false; + blobmsg_close_table(&b, nest); + break; + default: + return false; + } + if (array) + return true; + } + return false; +} + +static bool ubus_push_object_type(struct ubus_object_type *type) +{ + void *s, *m; + int rem = type->n_signature; + const struct ubus_signature *sig = type->signature; + + s = blob_nest_start(&b, UBUS_ATTR_SIGNATURE); + while (rem) { + if (sig->type != UBUS_SIGNATURE_METHOD) + return false; + + m = blobmsg_open_table(&b, sig->name); + + sig++; + rem--; + if (!ubus_push_table_data(&sig, &rem, false)) + return false; + + blobmsg_close_table(&b, m); + } + blob_nest_end(&b, s); + + return true; +} + +int ubus_publish(struct ubus_context *ctx, struct ubus_object *obj) +{ + struct ubus_request req; + int ret; + + if (obj->id || !obj->name || !obj->type) + return UBUS_STATUS_INVALID_ARGUMENT; + + blob_buf_init(&b, 0); + blob_put_string(&b, UBUS_ATTR_OBJPATH, obj->name); + if (obj->parent) + blob_put_int32(&b, UBUS_ATTR_OBJID, obj->parent->id); + + if (obj->type->id) + blob_put_int32(&b, UBUS_ATTR_OBJTYPE, obj->type->id); + else if (!ubus_push_object_type(obj->type)) + return UBUS_STATUS_INVALID_ARGUMENT; + + ubus_start_request(ctx, &req, b.head, UBUS_MSG_PUBLISH, 0); + req.data_cb = ubus_publish_cb; + req.priv = obj; + ret = ubus_complete_request(ctx, &req); + if (ret) + return ret; + + if (!obj->id) + return UBUS_STATUS_NO_DATA; + + return 0; +} + +struct ubus_context *ubus_connect(const char *path) +{ + struct ubus_context *ctx; + struct { + struct ubus_msghdr hdr; + struct blob_attr data; + } hdr; + struct blob_attr *buf; + + if (!path) + path = UBUS_UNIX_SOCKET; + + ctx = calloc(1, sizeof(*ctx)); + if (!ctx) + goto error; + + ctx->sock.fd = usock(USOCK_UNIX, path, NULL); + if (ctx->sock.fd < 0) { + DPRINTF("Failed to connect to server\n"); + goto error_free; + } + ctx->sock.cb = ubus_handle_data; + + if (read(ctx->sock.fd, &hdr, sizeof(hdr)) != sizeof(hdr)) { + DPRINTF("Failed to read initial message data\n"); + goto error_close; + } + + if (!ubus_validate_hdr(&hdr.hdr)) { + DPRINTF("Failed to validate initial message header\n"); + goto error_close; + } + + if (hdr.hdr.type != UBUS_MSG_HELLO) { + DPRINTF("Unexpected initial message\n"); + goto error_close; + } + + buf = calloc(1, blob_raw_len(&hdr.data)); + if (!buf) + goto error_close; + + memcpy(buf, &hdr.data, sizeof(hdr.data)); + if (read(ctx->sock.fd, blob_data(buf), blob_len(buf)) != blob_len(buf)) { + DPRINTF("Failed to retrieve initial message data\n"); + goto error_free_buf; + } + + ctx->local_id = hdr.hdr.peer; + free(buf); + + if (!ctx->local_id) { + DPRINTF("Failed to get local peer id\n"); + goto error_close; + } + + return ctx; + +error_free_buf: + free(buf); +error_close: + close(ctx->sock.fd); +error_free: + free(ctx); +error: + return NULL; +} + +void ubus_free(struct ubus_context *ctx) +{ + close(ctx->sock.fd); + free(ctx); +} diff --git a/libubus.h b/libubus.h new file mode 100644 index 0000000..bd3d736 --- /dev/null +++ b/libubus.h @@ -0,0 +1,141 @@ +#include +#include +#include +#include +#include "ubusmsg.h" +#include "ubus_common.h" + +struct ubus_msg_src; +struct ubus_object; +struct ubus_request; +struct ubus_request_data; + +typedef void (*ubus_handler_t)(struct ubus_object *obj, + struct ubus_request_data *req, + const char *method, struct blob_attr *msg); +typedef void (*ubus_data_handler_t)(struct ubus_request *req, + int type, struct blob_attr *msg); +typedef void (*ubus_complete_handler_t)(struct ubus_request *req, int ret); + + +#define UBUS_SIGNATURE(_type, _name) { .type = _type, .name = _name } + +#define UBUS_METHOD_START(_name) UBUS_SIGNATURE(UBUS_SIGNATURE_METHOD, _name) +#define UBUS_METHOD_END() UBUS_SIGNATURE(UBUS_SIGNATURE_END, NULL) + +#define UBUS_FIELD(_type, _name) UBUS_SIGNATURE(BLOBMSG_TYPE_ ## _type, _name) + +#define UBUS_ARRAY(_name) UBUS_FIELD(ARRAY, _name) +#define UBUS_ARRAY_END() UBUS_SIGNATURE(UBUS_SIGNATURE_END, NULL) + +#define UBUS_TABLE_START(_name) UBUS_FIELD(TABLE, _name) +#define UBUS_TABLE_END() UBUS_SIGNATURE(UBUS_SIGNATURE_END, NULL) + +#define UBUS_OBJECT_TYPE(_name, _signature) \ + { \ + .name = _name, \ + .id = 0, \ + .n_signature = ARRAY_SIZE(_signature), \ + .signature = _signature \ + } + +struct ubus_signature { + enum blobmsg_type type; + const char *name; +}; + +struct ubus_object_type { + const char *name; + uint32_t id; + int n_signature; + const struct ubus_signature *signature; +}; + +struct ubus_object { + const char *name; + uint32_t id; + + const char *path; + struct ubus_object *parent; + + struct ubus_object_type *type; +}; + +struct ubus_context { + struct list_head requests; + struct list_head objects; + struct uloop_fd sock; + + uint32_t local_id; + uint32_t request_seq; + + struct { + struct ubus_msghdr hdr; + char data[UBUS_MAX_MSGLEN - sizeof(struct ubus_msghdr)]; + } msgbuf; +}; + +struct ubus_request_data { + uint32_t object; + uint32_t peer; + uint32_t seq; +}; + +struct ubus_request { + struct list_head list; + + struct list_head pending; + bool status_msg; + int status_code; + bool blocked; + + uint32_t peer; + uint32_t seq; + + ubus_data_handler_t data_cb; + ubus_complete_handler_t complete_cb; + + void *priv; +}; + +#define BLOBMSG_END_TABLE BLOBMSG_TYPE_UNSPEC + +struct ubus_context *ubus_connect(const char *path); +void ubus_free(struct ubus_context *ctx); + +const char *ubus_strerror(int error); + +/* ----------- helpers for message handling ----------- */ + +struct blob_attr **ubus_parse_msg(struct blob_attr *msg); + +/* ----------- raw request handling ----------- */ + +/* start a raw request */ +int ubus_start_request(struct ubus_context *ctx, struct ubus_request *req, + struct blob_attr *msg, int cmd, uint32_t peer); + +/* wait for a request to complete and return its status */ +int ubus_complete_request(struct ubus_context *ctx, struct ubus_request *req); + +/* complete a request asynchronously */ +void ubus_complete_request_async(struct ubus_context *ctx, + struct ubus_request *req); + +/* abort an asynchronous request */ +void ubus_abort_request(struct ubus_context *ctx, struct ubus_request *req); + +/* ----------- rpc ----------- */ + +/* invoke a method on a specific object */ +int ubus_invoke(struct ubus_context *ctx, uint32_t obj, const char *method, + struct blob_attr *msg, ubus_data_handler_t cb, void *priv); + +/* asynchronous version of ubus_invoke() */ +void ubus_invoke_async(struct ubus_context *ctx, uint32_t obj, const char *method, + struct blob_attr *msg, struct ubus_request *req); + +/* make an object visible to remote connections */ +int ubus_publish(struct ubus_context *ctx, struct ubus_object *obj); + + diff --git a/listener.c b/listener.c new file mode 100644 index 0000000..08b7c64 --- /dev/null +++ b/listener.c @@ -0,0 +1,63 @@ +#include "libubus.h" + +static struct ubus_context *ctx; + +static const struct ubus_signature test_object_sig[] = { + UBUS_METHOD_START("hello"), + UBUS_ARRAY("test"), + UBUS_TABLE_START(NULL), + UBUS_FIELD(INT32, "id"), + UBUS_FIELD(STRING, "msg"), + UBUS_TABLE_END(), + UBUS_METHOD_END(), +}; + +static struct ubus_object_type test_object_type = + UBUS_OBJECT_TYPE("test", test_object_sig); + +static struct ubus_object test_object = { + .name = "test", + .type = &test_object_type, +}; + +static struct ubus_object test_object2 = { + .name = "test2", + .type = &test_object_type, +}; + +int main(int argc, char **argv) +{ + int ret; + + ctx = ubus_connect(NULL); + if (!ctx) { + fprintf(stderr, "Failed to connect to ubus\n"); + return -1; + } + + fprintf(stderr, "Connected as ID 0x%08x\n", ctx->local_id); + + fprintf(stderr, "Publishing object\n"); + ret = ubus_publish(ctx, &test_object); + if (ret) + fprintf(stderr, "Failed to publish object: %s\n", ubus_strerror(ret)); + else { + fprintf(stderr, "Object ID: %08x\n", test_object.id); + fprintf(stderr, "Object Type ID: %08x\n", test_object.type->id); + } + + fprintf(stderr, "Publishing object\n"); + ret = ubus_publish(ctx, &test_object2); + if (ret) + fprintf(stderr, "Failed to publish object: %s\n", ubus_strerror(ret)); + else { + fprintf(stderr, "Object ID: %08x\n", test_object2.id); + fprintf(stderr, "Object Type ID: %08x\n", test_object2.type->id); + } + uloop_init(); + uloop_fd_add(&ctx->sock, ULOOP_READ | ULOOP_EDGE_TRIGGER); + uloop_run(); + + ubus_free(ctx); + return 0; +} diff --git a/ubus_common.h b/ubus_common.h new file mode 100644 index 0000000..e002b27 --- /dev/null +++ b/ubus_common.h @@ -0,0 +1,15 @@ +#ifndef __UBUS_COMMON_H +#define __UBUS_COMMON_H + +#define UBUS_UNIX_SOCKET "./ubus.sock" + +#define UBUS_SIGNATURE_METHOD (BLOBMSG_TYPE_LAST + 1) +#define UBUS_SIGNATURE_END (BLOBMSG_TYPE_LAST + 2) + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#endif + +#define __init __attribute__((constructor)) + +#endif diff --git a/ubusd.c b/ubusd.c new file mode 100644 index 0000000..87b9519 --- /dev/null +++ b/ubusd.c @@ -0,0 +1,316 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ubusd.h" + +static struct avl_tree clients; + +static struct ubus_msg_buf *ubus_msg_unshare(struct ubus_msg_buf *ub) +{ + ub = realloc(ub, sizeof(*ub) + ub->len); + if (!ub) + return NULL; + + ub->refcount = 1; + memcpy(ub + 1, ub->data, ub->len); + ub->data = (void *) (ub + 1); + return ub; +} + +struct ubus_msg_buf *ubus_msg_ref(struct ubus_msg_buf *ub) +{ + if (ub->refcount == ~0) + return ubus_msg_unshare(ub); + + ub->refcount++; + return ub; +} + +struct ubus_msg_buf *ubus_msg_new(void *data, int len, bool shared) +{ + struct ubus_msg_buf *ub; + int buflen = sizeof(*ub); + + if (!shared) + buflen += len; + + ub = calloc(1, buflen); + if (!ub) + return NULL; + + if (shared) { + ub->refcount = ~0; + ub->data = data; + } else { + ub->refcount = 1; + ub->data = (void *) (ub + 1); + if (data) + memcpy(ub + 1, data, len); + } + + ub->len = len; + return ub; +} + +void ubus_msg_free(struct ubus_msg_buf *ub) +{ + switch (ub->refcount) { + case 1: + case ~0: + free(ub); + break; + default: + ub->refcount--; + break; + } +} + +static int ubus_msg_writev(int fd, struct ubus_msg_buf *ub, int offset) +{ + struct iovec iov[2]; + + if (offset < sizeof(ub->hdr)) { + iov[0].iov_base = ((char *) &ub->hdr) + offset; + iov[0].iov_len = sizeof(ub->hdr) - offset; + iov[1].iov_base = (char *) ub->data; + iov[1].iov_len = ub->len; + return writev(fd, iov, 2); + } else { + offset -= sizeof(ub->hdr); + return write(fd, ((char *) ub->data) + offset, ub->len - offset); + } +} + +/* takes the msgbuf reference */ +void ubus_msg_send(struct ubus_client *cl, struct ubus_msg_buf *ub) +{ + int written; + + if (cl->buf_head) + goto queue; + + written = ubus_msg_writev(cl->sock.fd, ub, 0); + if (written > 0 && written < ub->len + sizeof(ub->hdr)) { + cl->buf_head_ofs = written; + + /* get an event once we can write to the socket again */ + uloop_fd_add(&cl->sock, ULOOP_READ | ULOOP_WRITE | ULOOP_EDGE_TRIGGER); + goto queue; + } + + ubus_msg_free(ub); + return; + +queue: + ub = ubus_msg_unshare(ub); + ub->next = NULL; + *cl->buf_tail = ub; + cl->buf_tail = &ub->next; +} + +static void handle_client_disconnect(struct ubus_client *cl) +{ + struct ubus_object *obj; + + while (!list_empty(&cl->objects)) { + obj = list_first_entry(&cl->objects, struct ubus_object, list); + ubusd_free_object(obj); + } + + ubus_free_id(&clients, &cl->id); + uloop_fd_delete(&cl->sock); + close(cl->sock.fd); + free(cl); +} + +static void client_cb(struct uloop_fd *sock, unsigned int events) +{ + struct ubus_client *cl = container_of(sock, struct ubus_client, sock); + struct ubus_msg_buf *ub; + + /* first try to tx more pending data */ + while (cl->buf_head) { + struct ubus_msg_buf *ub = cl->buf_head; + int written; + + written = ubus_msg_writev(sock->fd, ub, cl->buf_head_ofs); + if (written < 0) { + switch(errno) { + case EINTR: + case EAGAIN: + break; + default: + goto disconnect; + } + break; + } + if (written == 0) + break; + + cl->buf_head_ofs += written; + if (cl->buf_head_ofs < ub->len + sizeof(ub->hdr)) + break; + + cl->buf_head_ofs = 0; + cl->buf_head = ub->next; + if (!cl->buf_head) + cl->buf_tail = &cl->buf_head; + } + + /* prevent further ULOOP_WRITE events if we don't have data + * to send anymore */ + if (!cl->buf_head && (events & ULOOP_WRITE)) + uloop_fd_add(sock, ULOOP_READ | ULOOP_EDGE_TRIGGER); + +retry: + if (!sock->eof && cl->pending_msg_offset < sizeof(cl->hdrbuf)) { + int offset = cl->pending_msg_offset; + int bytes; + + bytes = read(sock->fd, (char *)&cl->hdrbuf + offset, sizeof(cl->hdrbuf) - offset); + if (bytes < 0) + goto out; + + cl->pending_msg_offset += bytes; + if (cl->pending_msg_offset < sizeof(cl->hdrbuf)) + goto out; + + if (blob_len(&cl->hdrbuf.data) + sizeof(cl->hdrbuf) > UBUS_MAX_MSGLEN) + goto disconnect; + + cl->pending_msg = ubus_msg_new(NULL, blob_raw_len(&cl->hdrbuf.data), false); + if (!cl->pending_msg) + goto disconnect; + + memcpy(&cl->pending_msg->hdr, &cl->hdrbuf.hdr, sizeof(cl->hdrbuf.hdr)); + memcpy(cl->pending_msg->data, &cl->hdrbuf.data, sizeof(cl->hdrbuf.data)); + } + + ub = cl->pending_msg; + if (ub) { + int offset = cl->pending_msg_offset - sizeof(ub->hdr); + int len = blob_raw_len(ub->data) - offset; + int bytes = 0; + + if (len > 0) { + bytes = read(sock->fd, (char *) ub->data + offset, len); + if (bytes <= 0) + goto out; + } + + if (bytes < len) { + cl->pending_msg_offset += bytes; + goto out; + } + + /* accept message */ + cl->pending_msg_offset = 0; + cl->pending_msg = NULL; + ubusd_receive_message(cl, ub); + goto retry; + } + +out: + if (!sock->eof || cl->buf_head) + return; + +disconnect: + handle_client_disconnect(cl); +} + +struct ubus_client *ubusd_get_client_by_id(uint32_t id) +{ + struct ubus_id *clid; + + clid = ubus_find_id(&clients, id); + if (!clid) + return NULL; + + return container_of(clid, struct ubus_client, id); +} + +static bool get_next_connection(int fd) +{ + struct ubus_client *cl; + int client_fd; + + client_fd = accept(fd, NULL, 0); + if (client_fd < 0) { + switch (errno) { + case ECONNABORTED: + case EINTR: + return true; + default: + return false; + } + } + + cl = calloc(1, sizeof(*cl)); + cl->sock.fd = client_fd; + + INIT_LIST_HEAD(&cl->objects); + if (!ubus_alloc_id(&clients, &cl->id)) + goto error; + + cl->sock.cb = client_cb; + uloop_fd_add(&cl->sock, ULOOP_READ | ULOOP_EDGE_TRIGGER); + if (!ubusd_send_hello(cl)) + goto error_free; + + return true; + +error_free: + ubus_free_id(&clients, &cl->id); +error: + close(cl->sock.fd); + free(cl); + return true; +} + +static void server_cb(struct uloop_fd *fd, unsigned int events) +{ + bool next; + + do { + next = get_next_connection(fd->fd); + } while (next); +} + +static struct uloop_fd server_fd = { + .cb = server_cb, +}; + +int main(int argc, char **argv) +{ + int ret = 0; + + signal(SIGPIPE, SIG_IGN); + + ubus_init_id_tree(&clients); + + uloop_init(); + + unlink(UBUS_UNIX_SOCKET); + server_fd.fd = usock(USOCK_UNIX | USOCK_SERVER | USOCK_NONBLOCK, UBUS_UNIX_SOCKET, NULL); + if (server_fd.fd < 0) { + perror("usock"); + ret = -1; + goto out; + } + uloop_fd_add(&server_fd, ULOOP_READ | ULOOP_EDGE_TRIGGER); + + uloop_run(); + +out: + uloop_done(); + return ret; +} diff --git a/ubusd.h b/ubusd.h new file mode 100644 index 0000000..c0b02f5 --- /dev/null +++ b/ubusd.h @@ -0,0 +1,64 @@ +#ifndef __UBUSD_H +#define __UBUSD_H + +#include +#include +#include +#include "ubus_common.h" +#include "ubusd_id.h" +#include "ubusd_obj.h" +#include "ubusmsg.h" + +#define UBUS_UNIX_SOCKET "./ubus.sock" +#define UBUSD_CLIENT_BACKLOG 32 +#define UBUS_OBJ_HASH_BITS 4 + +struct ubus_msg_buf { + struct ubus_msg_buf *next; + uint32_t refcount; /* ~0: uses external data buffer */ + struct ubus_msghdr hdr; + struct blob_attr *data; + int len; +}; + +struct ubus_client { + struct ubus_id id; + struct uloop_fd sock; + + struct { + struct ubus_msghdr hdr; + struct blob_attr data; + } hdrbuf; + + struct list_head objects; + + int pending_msg_offset; + struct ubus_msg_buf *pending_msg; + + unsigned int buf_head_ofs; + struct ubus_msg_buf *buf_head; + struct ubus_msg_buf **buf_tail; + + struct ubus_msg_buf *requests[UBUSD_CLIENT_BACKLOG]; + unsigned int req_head, req_tail; +}; + +struct ubus_path { + struct list_head list; + const char name[]; +}; + +struct ubus_msg_buf *ubus_msg_new(void *data, int len, bool shared); +void ubus_msg_send(struct ubus_client *cl, struct ubus_msg_buf *ub); +struct ubus_msg_buf *ubus_msg_ref(struct ubus_msg_buf *ub); +void ubus_msg_free(struct ubus_msg_buf *ub); + +struct ubus_client *ubusd_get_client_by_id(uint32_t id); + +void ubusd_receive_message(struct ubus_client *cl, struct ubus_msg_buf *ub); +bool ubusd_send_hello(struct ubus_client *cl); + +struct blob_attr **ubus_parse_msg(struct blob_attr *msg); + + +#endif diff --git a/ubusd_id.c b/ubusd_id.c new file mode 100644 index 0000000..9443db9 --- /dev/null +++ b/ubusd_id.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +#include "ubusd_id.h" + +static int random_fd = -1; + +static int ubus_cmp_id(const void *k1, const void *k2, void *ptr) +{ + const uint32_t *id1 = k1, *id2 = k2; + + if (*id1 < *id2) + return -1; + else + return *id1 > *id2; +} + +void ubus_init_id_tree(struct avl_tree *tree) +{ + if (random_fd < 0) { + random_fd = open("/dev/urandom", O_RDONLY); + if (random_fd < 0) { + perror("open"); + exit(1); + } + } + + avl_init(tree, ubus_cmp_id, false, NULL); +} + +bool ubus_alloc_id(struct avl_tree *tree, struct ubus_id *id) +{ + id->avl.key = &id->id; + do { + if (read(random_fd, &id->id, sizeof(id->id)) != sizeof(id->id)) + return false; + + if (!id->id) + continue; + } while (avl_insert(tree, &id->avl) != 0); + + return true; +} + diff --git a/ubusd_id.h b/ubusd_id.h new file mode 100644 index 0000000..ca69d9a --- /dev/null +++ b/ubusd_id.h @@ -0,0 +1,31 @@ +#ifndef __UBUSD_ID_H +#define __UBUSD_ID_H + +#include +#include + +struct ubus_id { + struct avl_node avl; + uint32_t id; +}; + +void ubus_init_id_tree(struct avl_tree *tree); +bool ubus_alloc_id(struct avl_tree *tree, struct ubus_id *id); + +static inline void ubus_free_id(struct avl_tree *tree, struct ubus_id *id) +{ + avl_delete(tree, &id->avl); +} + +static inline struct ubus_id *ubus_find_id(struct avl_tree *tree, uint32_t id) +{ + struct avl_node *avl; + + avl = avl_find(tree, &id); + if (!avl) + return NULL; + + return container_of(avl, struct ubus_id, avl); +} + +#endif diff --git a/ubusd_obj.c b/ubusd_obj.c new file mode 100644 index 0000000..5e08fab --- /dev/null +++ b/ubusd_obj.c @@ -0,0 +1,147 @@ +#include "ubusd.h" +#include "ubusd_obj.h" + +struct avl_tree obj_types; +struct avl_tree objects; +struct avl_tree path; + +static void ubus_unref_object_type(struct ubus_object_type *type) +{ + struct ubus_method *m; + + if (--type->refcount > 0) + return; + + while (!list_empty(&type->methods)) { + m = list_first_entry(&type->methods, struct ubus_method, list); + list_del(&m->list); + free(m); + } + + ubus_free_id(&obj_types, &type->id); + free(type); +} + +static bool ubus_create_obj_method(struct ubus_object_type *type, struct blob_attr *attr) +{ + struct ubus_method *m; + int bloblen = blob_raw_len(attr); + + m = calloc(1, sizeof(*m) + bloblen); + if (!m) + return false; + + list_add(&m->list, &type->methods); + memcpy(m->data, attr, bloblen); + m->name = blobmsg_name(m->data); + + return true; +} + +static struct ubus_object_type *ubus_create_obj_type(struct blob_attr *sig) +{ + struct ubus_object_type *type; + struct blob_attr *pos; + int rem; + + type = calloc(1, sizeof(*type)); + type->refcount = 1; + + if (!ubus_alloc_id(&obj_types, &type->id)) + goto error_free; + + INIT_LIST_HEAD(&type->methods); + + blob_for_each_attr(pos, sig, rem) { + if (!blobmsg_check_attr(pos, true)) + goto error_unref; + + if (!ubus_create_obj_method(type, pos)) + goto error_unref; + } + + return type; + +error_unref: + ubus_unref_object_type(type); + return NULL; + +error_free: + free(type); + return NULL; +} + +static struct ubus_object_type *ubus_get_obj_type(uint32_t obj_id) +{ + struct ubus_object_type *type; + struct ubus_id *id; + + id = ubus_find_id(&obj_types, obj_id); + if (!id) + return NULL; + + type = container_of(id, struct ubus_object_type, id); + type->refcount++; + return type; +} + +struct ubus_object *ubusd_create_object(struct ubus_client *cl, struct blob_attr **attr) +{ + struct ubus_object *obj; + struct ubus_object_type *type = NULL; + + if (attr[UBUS_ATTR_OBJTYPE]) + type = ubus_get_obj_type(blob_get_int32(attr[UBUS_ATTR_OBJTYPE])); + else if (attr[UBUS_ATTR_SIGNATURE]) + type = ubus_create_obj_type(attr[UBUS_ATTR_SIGNATURE]); + + if (!type) + return NULL; + + obj = calloc(1, sizeof(*obj)); + if (!ubus_alloc_id(&objects, &obj->id)) + goto error_free; + + if (attr[UBUS_ATTR_OBJPATH]) { + obj->path.key = strdup(blob_data(attr[UBUS_ATTR_OBJPATH])); + if (avl_insert(&path, &obj->path) != 0) + goto error_del_id; + } + + obj->type = type; + list_add(&obj->list, &cl->objects); + + return obj; + +error_del_id: + free(obj->path.key); + ubus_free_id(&objects, &obj->id); +error_free: + ubus_unref_object_type(type); + free(obj); + return NULL; +} + +void ubusd_free_object(struct ubus_object *obj) +{ + if (obj->path.key) { + avl_delete(&path, &obj->path); + free(obj->path.key); + } + list_del(&obj->list); + ubus_free_id(&objects, &obj->id); + ubus_unref_object_type(obj->type); + free(obj); +} + +static int ubus_cmp_path(const void *k1, const void *k2, void *ptr) +{ + return strcmp(k1, k2); +} + +static void __init ubusd_obj_init(void) +{ + ubus_init_id_tree(&objects); + ubus_init_id_tree(&obj_types); + avl_init(&path, ubus_cmp_path, false, NULL); +} diff --git a/ubusd_obj.h b/ubusd_obj.h new file mode 100644 index 0000000..943ee13 --- /dev/null +++ b/ubusd_obj.h @@ -0,0 +1,37 @@ +#ifndef __UBUSD_OBJ_H +#define __UBUSD_OBJ_H + +#include "ubusd_id.h" + +extern struct avl_tree obj_types; +extern struct avl_tree objects; +extern struct avl_tree path; + +struct ubus_client; +struct ubus_msg_buf; + +struct ubus_object_type { + struct ubus_id id; + int refcount; + struct list_head methods; +}; + +struct ubus_method { + struct list_head list; + const char *name; + struct blob_attr data[]; +}; + +struct ubus_object { + struct ubus_id id; + struct list_head list; + + struct ubus_object_type *type; + + struct avl_node path; +}; + +struct ubus_object *ubusd_create_object(struct ubus_client *cl, struct blob_attr **attr); +void ubusd_free_object(struct ubus_object *obj); + +#endif diff --git a/ubusd_proto.c b/ubusd_proto.c new file mode 100644 index 0000000..a202ace --- /dev/null +++ b/ubusd_proto.c @@ -0,0 +1,203 @@ +#include +#include "ubusd.h" + +static struct blob_buf b; +static struct ubus_msg_buf *retmsg; +static int *retmsg_data; + +static struct blob_attr *attrbuf[UBUS_ATTR_MAX]; + +typedef int (*ubus_cmd_cb)(struct ubus_client *cl, struct ubus_msg_buf *ub); + +static const struct blob_attr_info ubus_policy[UBUS_ATTR_MAX] = { + [UBUS_ATTR_SIGNATURE] = { .type = BLOB_ATTR_NESTED }, + [UBUS_ATTR_OBJTYPE] = { .type = BLOB_ATTR_INT32 }, + [UBUS_ATTR_OBJPATH] = { .type = BLOB_ATTR_STRING }, +}; + +struct blob_attr **ubus_parse_msg(struct blob_attr *msg) +{ + blob_parse(msg, attrbuf, ubus_policy, UBUS_ATTR_MAX); + return attrbuf; +} + +static void ubus_msg_init(struct ubus_msg_buf *ub, uint8_t type, uint16_t seq, uint32_t peer) +{ + ub->hdr.version = 0; + ub->hdr.type = type; + ub->hdr.seq = seq; + ub->hdr.peer = peer; +} + +static struct ubus_msg_buf *ubus_msg_from_blob(bool shared) +{ + return ubus_msg_new(b.head, blob_raw_len(b.head), shared); +} + +static struct ubus_msg_buf *ubus_reply_from_blob(struct ubus_msg_buf *ub, bool shared) +{ + struct ubus_msg_buf *new; + + new = ubus_msg_new(b.head, blob_raw_len(b.head), shared); + if (!new) + return NULL; + + ubus_msg_init(new, UBUS_MSG_DATA, ub->hdr.seq, ub->hdr.peer); + return new; +} + +bool ubusd_send_hello(struct ubus_client *cl) +{ + struct ubus_msg_buf *ub; + + blob_buf_init(&b, 0); + ub = ubus_msg_from_blob(true); + if (!ub) + return false; + + ubus_msg_init(ub, UBUS_MSG_HELLO, 0, cl->id.id); + ubus_msg_send(cl, ub); + return true; +} + +static int ubusd_send_pong(struct ubus_client *cl, struct ubus_msg_buf *ub) +{ + ub->hdr.type = UBUS_MSG_DATA; + ubus_msg_send(cl, ubus_msg_ref(ub)); + return 0; +} + +static int ubusd_handle_publish(struct ubus_client *cl, struct ubus_msg_buf *ub) +{ + struct ubus_object *obj; + struct blob_attr **attr; + + attr = ubus_parse_msg(ub->data); + obj = ubusd_create_object(cl, attr); + if (!obj) + return UBUS_STATUS_INVALID_ARGUMENT; + + blob_buf_init(&b, 0); + blob_put_int32(&b, UBUS_ATTR_OBJID, obj->id.id); + if (attr[UBUS_ATTR_SIGNATURE]) + blob_put_int32(&b, UBUS_ATTR_OBJTYPE, obj->type->id.id); + + ub = ubus_reply_from_blob(ub, true); + if (!ub) + return UBUS_STATUS_NO_DATA; + + ubus_msg_send(cl, ub); + return 0; +} + +static void ubusd_send_obj(struct ubus_client *cl, struct ubus_msg_buf *ub, struct ubus_object *obj) +{ + struct ubus_method *m; + void *s; + + blob_buf_init(&b, 0); + + if (obj->path.key) + blob_put_string(&b, UBUS_ATTR_OBJPATH, obj->path.key); + blob_put_int32(&b, UBUS_ATTR_OBJID, obj->id.id); + + s = blob_nest_start(&b, UBUS_ATTR_SIGNATURE); + list_for_each_entry(m, &obj->type->methods, list) + blob_put(&b, blob_id(m->data), blob_data(m->data), blob_len(m->data)); + blob_nest_end(&b, s); + + ub = ubus_reply_from_blob(ub, true); + if (!ub) + return; + + ubus_msg_send(cl, ub); +} + +static int ubusd_handle_lookup(struct ubus_client *cl, struct ubus_msg_buf *ub) +{ + struct ubus_object *obj; + struct blob_attr **attr; + char *objpath; + bool wildcard = false; + bool found = false; + int len; + + attr = ubus_parse_msg(ub->data); + if (!attr[UBUS_ATTR_OBJPATH]) { + avl_for_each_element(&path, obj, path) + ubusd_send_obj(cl, ub, obj); + return 0; + } + + objpath = blob_data(attr[UBUS_ATTR_OBJPATH]); + len = strlen(objpath); + if (objpath[len - 1] != '*') { + obj = avl_find_element(&path, objpath, obj, path); + if (!obj) + return UBUS_STATUS_NOT_FOUND; + + ubusd_send_obj(cl, ub, obj); + return 0; + } + + objpath[--len] = 0; + wildcard = true; + + obj = avl_find_ge_element(&path, objpath, obj, path); + if (!obj) + return UBUS_STATUS_NOT_FOUND; + + while (!strncmp(objpath, obj->path.key, len)) { + found = true; + ubusd_send_obj(cl, ub, obj); + if (obj == avl_last_element(&path, obj, path)) + break; + obj = avl_next_element(obj, path); + } + + if (!found) + return UBUS_STATUS_NOT_FOUND; + + return 0; +} + +static const ubus_cmd_cb handlers[__UBUS_MSG_LAST] = { + [UBUS_MSG_PING] = ubusd_send_pong, + [UBUS_MSG_PUBLISH] = ubusd_handle_publish, + [UBUS_MSG_LOOKUP] = ubusd_handle_lookup, +}; + +void ubusd_receive_message(struct ubus_client *cl, struct ubus_msg_buf *ub) +{ + ubus_cmd_cb cb = NULL; + int ret; + + retmsg->hdr.seq = ub->hdr.seq; + retmsg->hdr.peer = ub->hdr.peer; + + if (ub->hdr.type < __UBUS_MSG_LAST) + cb = handlers[ub->hdr.type]; + + if (cb) + ret = cb(cl, ub); + else + ret = UBUS_STATUS_INVALID_COMMAND; + + ubus_msg_free(ub); + + *retmsg_data = htonl(ret); + ubus_msg_send(cl, ubus_msg_ref(retmsg)); +} + +static void __init ubusd_proto_init(void) +{ + blob_buf_init(&b, 0); + blob_put_int32(&b, UBUS_ATTR_STATUS, 0); + + retmsg = ubus_msg_from_blob(false); + if (!retmsg) + exit(1); + + retmsg->hdr.type = UBUS_MSG_STATUS; + retmsg_data = blob_data(blob_data(retmsg->data)); +} diff --git a/ubusmsg.h b/ubusmsg.h new file mode 100644 index 0000000..e62a393 --- /dev/null +++ b/ubusmsg.h @@ -0,0 +1,72 @@ +#ifndef __UBUSMSG_H +#define __UBUSMSG_H + +#include +#include + +#define __packetdata __attribute__((packed)) __attribute__((__aligned__(4))) + +#define UBUS_MAX_MSGLEN 65535 + +struct ubus_msghdr { + uint8_t version; + uint8_t type; + uint16_t seq; + uint32_t peer; + struct blob_attr data[]; +} __packetdata; + +enum ubus_msg_type { + /* initial server message */ + UBUS_MSG_HELLO, + + /* generic command response */ + UBUS_MSG_STATUS, + + /* data message response */ + UBUS_MSG_DATA, + + /* ping request */ + UBUS_MSG_PING, + + /* look up one or more objects */ + UBUS_MSG_LOOKUP, + + /* invoke a method on a single object */ + UBUS_MSG_INVOKE, + + /* publish an object */ + UBUS_MSG_PUBLISH, + + /* must be last */ + __UBUS_MSG_LAST, +}; + +enum ubus_msg_attr { + UBUS_ATTR_UNSPEC, + + UBUS_ATTR_STATUS, + + UBUS_ATTR_OBJPATH, + UBUS_ATTR_OBJID, + UBUS_ATTR_METHOD, + + UBUS_ATTR_OBJTYPE, + UBUS_ATTR_SIGNATURE, + + UBUS_ATTR_DATA, + + /* must be last */ + UBUS_ATTR_MAX, +}; + +enum ubus_msg_status { + UBUS_STATUS_OK, + UBUS_STATUS_INVALID_COMMAND, + UBUS_STATUS_INVALID_ARGUMENT, + UBUS_STATUS_NOT_FOUND, + UBUS_STATUS_NO_DATA, + __UBUS_STATUS_LAST +}; + +#endif