Ubus c api example: uci load a config

I am getting ready to swap drives, while going through some of my old projects i found a couple good examples of using the ubus c api. They helped me tremendously while learning to use the api. Hopefully someone else my find the same utility. I put this together from bits and pieces i found on the net at the time.

ubus_uci_ex.c
#include "ubus_uci_ex.h"

static struct uci_context *g_uci_ctx;
static struct blob_buf b;

void parse_config(struct blob_attr *config)
{
	fprintf(stderr, "########### uci config: test ##########\n");
	struct blob_attr *tb[__POLICY_ATTR_MAX];
	blobmsg_parse(policy_attrs, __POLICY_ATTR_MAX, tb, blob_data(config), blob_len(config));
	
	// do something with *tb[]
	if(tb[POLICY_ATTR_NAME])
	{
		char *name= blobmsg_get_string(tb[POLICY_ATTR_NAME]);
		printf("name: %s\n", name);
	}
	if(tb[POLICY_ATTR_ENABLE])
	{
		bool enable = blobmsg_get_bool(tb[POLICY_ATTR_ENABLE]);
		printf("enable: %d --> %s\n", enable, enable != 0 ? "true" : "false");
	}
	if(tb[POLICY_ATTR_DNS])
	{
		struct blob_attr *cur;
		unsigned int rem;
		char *dns;
                int i = 0;
		blobmsg_for_each_attr(cur, tb[POLICY_ATTR_DNS], rem) {
		//int i = 0;

		if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) continue;

                if (!blobmsg_check_attr(cur, false)) continue;

		
			dns = blobmsg_get_string(cur);
			printf("dns-%d: %s\n", i++, dns);
			//free(dns);
		}
		//free(dns);
	}
	printf("\n");
	
}

static int load_config(const char *conf)
{
        struct uci_context *ctx = g_uci_ctx; 
        struct uci_package *p = NULL;

        if(!ctx)
        {
                ctx = uci_alloc_context();
                g_uci_ctx = ctx;
                uci_set_confdir(ctx, "./ex_config");
        }
        else
        {
                p = uci_lookup_package(ctx, conf);
                if(p)
                    uci_unload(ctx, p);
        }

        if(uci_load(ctx, conf, &p))
        {
                fprintf(stderr, "ERROR --> UBUS_UCI_EX: FAILED TO LOAD CONFIG FILE [ %s ]\n", conf);
                uci_free_context(ctx);
                g_uci_ctx = NULL;
                return -1;
        } 

        struct uci_element *e;
        struct blob_attr *config = NULL;
        
        uci_foreach_element(&p->sections, e)
        {
                struct uci_section *s = uci_to_section(e);

                blob_buf_init(&b, 0);
                uci_to_blob(&b, s, &policy_attr_list);
                config = blob_memdup(b.head);

                parse_config(config);
		free(config);
                blob_buf_free(&b);
	        uci_free_context(ctx);
	        g_uci_ctx = NULL;
                return 0;
        }
        uci_free_context(ctx);
        g_uci_ctx = NULL;
        return 1;
}

int main()
{
        load_config("test");
        return 0;
}
ubus_uci_ex.h
#ifndef UBUS_UCI_EX_H
#define UBUS_UCI_EX_H

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

#include <libubus.h>
#include <libubox/blobmsg_json.h>
#include <libubox/blobmsg.h>
#include <uci_blob.h>
#include <uci.h>

enum 
{
        POLICY_ATTR_NAME,
        POLICY_ATTR_ENABLE,
        POLICY_ATTR_DNS,
        __POLICY_ATTR_MAX,
};

static const struct blobmsg_policy policy_attrs[__POLICY_ATTR_MAX] = {
        [POLICY_ATTR_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING},
        [POLICY_ATTR_ENABLE] = { .name = "enable", .type = BLOBMSG_TYPE_BOOL},
        [POLICY_ATTR_DNS] = { .name = "dns", .type = BLOBMSG_TYPE_ARRAY},
};

static const struct uci_blob_param_info policy_attr_info[__POLICY_ATTR_MAX] = {
        [POLICY_ATTR_DNS] = { .type = BLOBMSG_TYPE_STRING},

};

static const struct uci_blob_param_list policy_attr_list = {
        .n_params = __POLICY_ATTR_MAX,
        .params = policy_attrs,
        .info = policy_attr_info,
};

#endif
ubuntu ./ex_config/test  or in openwrt etc/config/test
config policy test
        option name 'test'
        option enable '1'
        option dns '1.1.1.1 2.2.2.2 3.3.3.3'

compile in ubuntu 23+

gcc -o ubus_uci_ex -g -Wall -Werror -Wextra ubus_uci_ex.c -luci -lubox -lubus

This is setup for testing in .. my case ubuntu23 X64. To use it in openwrt, simply comment out the line following line in ubus_uci_ex.c , and put test in the /etc/config/

uci_set_confdir(ctx, "./ex_config");

sample output

hostle@dani:~/luamg/rootED7/src/mg_ubus/src/ubus_uci_example$ gcc -o ubus_uci_ex -g -Wall -Werror -Wextra ubus_uci_ex.c -luci -lubox -lubus
hostle@dani:~/luamg/rootED7/src/mg_ubus/src/ubus_uci_example$ valgrind ./ubus_uci_ex
==116996== Memcheck, a memory error detector
==116996== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==116996== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==116996== Command: ./ubus_uci_ex
==116996== 
########### uci config: test ##########
name: test
enable: 1 --> true
dns-0: 1.1.1.1
dns-1: 2.2.2.2
dns-2: 3.3.3.3

==116996== 
==116996== HEAP SUMMARY:
==116996==     in use at exit: 0 bytes in 0 blocks
==116996==   total heap usage: 29 allocs, 29 frees, 7,357 bytes allocated
==116996== 
==116996== All heap blocks were freed -- no leaks are possible
==116996== 
==116996== For lists of detected and suppressed errors, rerun with: -s
==116996== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
3 Likes

Looks good!

I'm sort of curious about your use case for C, did you need to do low-level device access or something?

What with the lua-ectomy almost complete (I think some fringe packages still depend on it, but base functionality is all clean), I've been using ucode for almost everything like this, since it's got the full uci and ubus interfaces available "for free" on standard installs.

For example, reading (and writing) config files and sections turns into a few lines of code:

#!/usr/bin/ucode -S

import { cursor } from 'uci';
let uci = cursor();

function show_section(config, section)
{
    printf("%s.%s:\n", config, section);
    for (let item, value in uci.get_all(config, section)) {
        printf("  %s = %s\n", item, value);
    }
}

show_section("dhcp", "@host[0]");
show_section("system", "ntp");

printf("network wan device: %s\n", uci.get("network", "wan", "device"));

giving

$ ./uci-get.uc
dhcp.@host[0]:
  .anonymous = true
  .type = host
  .name = cfg05fe63
  name = alma9
  duid = 00046D7CB893B8C1AF265E7A9926BD953CB4
  mac = 00:15:5D:01:A0:0C
  ip = 192.168.1.204
  hostid = 204
system.ntp:
  .anonymous = false
  .type = timeserver
  .name = ntp
  server = [ "0.openwrt.pool.ntp.org", "1.openwrt.pool.ntp.org", "2.openwrt.pool.ntp.org", "3.openwrt.pool.ntp.org" ]
  use_dhcp = 0
network wan device: eth0

Yes, I am very interested in ucode. I have not played with it much yet, I am still in the docs and browsing src stage in regards to that.

Here is an example on one of my projects from last fall,
openwrt-ubus-with-esp-now-and-kasa-smart-home-devices-iot-network
I did indeed need LL device access for this project. Also, the library I wrote for interacting with Kasa Smart Home Devices, KasaC has a ubus module and lua-ubus bindings (kasalua). It was needed ... or preferred rather, in this project.

I am currently working on a ubus mod for the Mongoose C Networking library.I'm working on the websockets module right now. It will all be configured thru luci webui eventually so the bindings are a must.