/How-to-UBUS

A brief C code for service object with method "hello" to be registered in ubus Daemon (suitable for UBUS learning).

Primary LanguageCMIT LicenseMIT

How-to-UBUS

This repository aims to run and test UBUSD using Ubuntu Linux. ubus is typically executed in OpenWRT-based and this repo can serve as guide to simulate or experiment ubus in Linux-based terminal.

image

📚 Contents


🛠️ How to Prepare Environment

1. Prepare dependencies.

Using apt, update and install the necessary dependencies in your machine.

sudo apt update
sudo apt install -y build-essential git libjson-c-dev libblobmsg-json-dev pkg-config

[RECOMMENDED] Instead of manually pasting these commands on your terminal, you may use the shell script in this repo:

./Scripts/01_install-dependencies.sh

2. Prepare libubox and ubus code.

The libubox and ubus code are the vital part of the code to run ubusd. Their codebase can be collected in checking OpenWRT links by doing the following commands:

git clone https://git.openwrt.org/project/libubox.git
git clone https://git.openwrt.org/project/ubus.git

The cloning step can be done by manually running the script file below:

./Scripts/02_clone-repos.sh

[RECOMMENDED] Fortunately, you do not have to do the steps above. After you clone this repo, the libubox and ubus code are already added in Packages directory.

3. Prepare your service code.

In the Source directory, the main.c file serves as the main program for hello service. This hello service intends to be registered in the ubus daemon, so that later, it can be called when someone triggers it in ubus. This service just prints hello as its message attribute value when triggered.

A simple hello service in C could be the following:

#include <libubus.h>
#include <libubox/blobmsg_json.h>
#include <stdio.h>

static struct ubus_context *ctx;

static int hello_handler(struct ubus_context *ctx, struct ubus_object *obj,
                         struct ubus_request_data *req, const char *method,
                         struct blob_attr *msg) {
    struct blob_buf b = {};
    blob_buf_init(&b, 0);
    blobmsg_add_string(&b, "message", "Hello from ubus!");
    ubus_send_reply(ctx, req, b.head);
    blob_buf_free(&b);
    return 0;
}

static const struct ubus_method hello_methods[] = {
    UBUS_METHOD_NOARG("say", hello_handler),
};

static struct ubus_object_type hello_type =
    UBUS_OBJECT_TYPE("hello_type", hello_methods);

static struct ubus_object hello_object = {
    .name = "hello",
    .type = &hello_type,
    .methods = hello_methods,
    .n_methods = ARRAY_SIZE(hello_methods),
};

int main() {
    ctx = ubus_connect(NULL);
    if (!ctx) {
        fprintf(stderr, "Failed to connect to ubusd\n");
        return 1;
    }

    if (ubus_add_object(ctx, &hello_object)) {
        fprintf(stderr, "Failed to add ubus object\n");
        ubus_free(ctx);
        return 1;
    }

    printf("UBUS 'hello' object registered. Waiting for calls...\n");
    ubus_add_uloop(ctx);
    uloop_init();
    uloop_run();

    ubus_free(ctx);
    return 0;
}

4. Build the Packages and Source

There are two main directories to compile in this repo: (a) Packages and (b) Source.

Packages contain the needed OpenWRT-based code to run ubusd. To build the packages, go to Packages directory then do make command.

Source is where our service code is located. To build the packages, go to Source directory then do make command. So, if you want to add your own service, add or modify file in Source directory.

What happens when you do make in Source are the following commands but you do not need to manually type these because it is aready handled by Makefile for abstraction.

gcc -o hello_service main.c \
    -I../libubox -I../ubus -I../Include \
    -L../libubox/build -lubox -lblobmsg_json \
    -L../ubus/build -lubus

[RECOMMENDED] You can skip the above step for simplicity. Just do make in the top directory and both Packages and Source are compiled.

To remove the compiled file, just do:

make clean

5. Run ubusd.

The compiled ubusd, ubus and hello-service are copied to the Scripts directory if build is successful. Go to this directory and run the following:

sudo ./ubusd -s "my_own_socket.sock" &

[ALTERNATIVE] Or simply run the 03_run-ubusd.sh in Scripts folder.

6. Register hello service in ubus.

Register the hello service and its object and method in ubus by running the following service code:

sudo ./Scripts/hello-service -s my_own_socket.sock &

if this command failed to run hello-service, export the ubox shared object. See the export command in Scripts/04_run-hello-service.sh. You can just simply run this shell script if encountered difficulties in executing hello-service.

If hello service is successfully registered in ubus, you should see your hello_service running in ubus waiting to be called. Verify if it is listed in ubus by doing the following command:

sudo ./Scripts/ubus -s my_own_socket.sock list

7. Trigger hello from ubus.

sudo ./Scripts/ubus call hello say -s my_own_socket.sock

🛠️ Service Code C Structure in UBUS

Your service code is what we would like to register in the ubus daemon. To create one, your code should contain the following:

Includes

This is needed to find our libubox and ubus code in the Packages. stdio is also included as default.

#include <libubus.h>
#include <libubox/blobmsg_json.h>
#include <stdio.h>

Define your hello service handler.

What we would like to do is to add a message attribute with value "Hello from ubus!" in this hello_handler function. You may add other attributes but in this case, we make it simple.

static struct ubus_context *ctx;

static int hello_handler(struct ubus_context *ctx, struct ubus_object *obj,
                         struct ubus_request_data *req, const char *method,
                         struct blob_attr *msg) {
    struct blob_buf b = {};
    blob_buf_init(&b, 0);
    blobmsg_add_string(&b, "message", "Hello from ubus!");
    ubus_send_reply(ctx, req, b.head);
    blob_buf_free(&b);
    return 0;
}

Define your hello service method.

What we added previously is the handler. This time, we specify which method is going to trigger the previous handler. If the ubus command uses the say method, the handler above is going to be executed. You may add other ubus methods here by appending it to the array below.

static const struct ubus_method hello_methods[] = {
    UBUS_METHOD_NOARG("say", hello_handler),
};

Define the hello service type.

What we added previously is the method. This time, we specify which type is our hello service. In this part, we link the hello_type to use hello_methods array as its ubus methods.

static struct ubus_object_type hello_type =
    UBUS_OBJECT_TYPE("hello_type", hello_methods);

Define the hello service object

The hello service is going to be abstracted as a whole object. This object requires the method and type we set earlier. In this case, the following code suffices to add hello object:

static struct ubus_object hello_object = {
    .name = "hello",
    .type = &hello_type,
    .methods = hello_methods,
    .n_methods = ARRAY_SIZE(hello_methods),
};