Initial import

This commit is contained in:
Felix Fietkau 2010-12-06 03:51:58 +01:00
commit dbd4c2f121
15 changed files with 1748 additions and 0 deletions

9
.gitignore vendored Normal file
View file

@ -0,0 +1,9 @@
Makefile
CMakeCache.txt
CMakeFiles
*.cmake
*.a
ubus.sock
listener
ubusd
ubus

16
CMakeLists.txt Normal file
View file

@ -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)

73
cli.c Normal file
View file

@ -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 <command> [arguments...]\n"
"Commands:\n"
" - list [<path>] 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;
}

515
libubus.c Normal file
View file

@ -0,0 +1,515 @@
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/socket.h>
#include <unistd.h>
#include <libubox/blob.h>
#include <libubox/blobmsg.h>
#include <libubox/usock.h>
#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);
}

141
libubus.h Normal file
View file

@ -0,0 +1,141 @@
#include <libubox/list.h>
#include <libubox/blobmsg.h>
#include <libubox/uloop.h>
#include <stdint.h>
#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);

63
listener.c Normal file
View file

@ -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;
}

15
ubus_common.h Normal file
View file

@ -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

316
ubusd.c Normal file
View file

@ -0,0 +1,316 @@
#include <sys/socket.h>
#include <sys/uio.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <libubox/blob.h>
#include <libubox/uloop.h>
#include <libubox/usock.h>
#include <libubox/list.h>
#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;
}

64
ubusd.h Normal file
View file

@ -0,0 +1,64 @@
#ifndef __UBUSD_H
#define __UBUSD_H
#include <libubox/list.h>
#include <libubox/uloop.h>
#include <libubox/blobmsg.h>
#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

46
ubusd_id.c Normal file
View file

@ -0,0 +1,46 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#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;
}

31
ubusd_id.h Normal file
View file

@ -0,0 +1,31 @@
#ifndef __UBUSD_ID_H
#define __UBUSD_ID_H
#include <libubox/avl.h>
#include <stdint.h>
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

147
ubusd_obj.c Normal file
View file

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

37
ubusd_obj.h Normal file
View file

@ -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

203
ubusd_proto.c Normal file
View file

@ -0,0 +1,203 @@
#include <arpa/inet.h>
#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));
}

72
ubusmsg.h Normal file
View file

@ -0,0 +1,72 @@
#ifndef __UBUSMSG_H
#define __UBUSMSG_H
#include <stdint.h>
#include <libubox/blob.h>
#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