うたカモ技術ブログ

Linux OpenWrt

OpenWrt   サンプルプログラム掲載! UBUS用C言語ライブラリ-libubus(object・method登録実装)

post:     update: 

この記事は作成中です。
この記事で紹介するソースコードは自由に使っていただいて構いません。 アプリケーションの開発や自己学習にお役立て下さい。ただし、当ブログでは掲載するソースコードを流用・利用したことによる損害等につきましては 一切の責任を負いません。自己責任で利用お願いします。

この記事では、UBUSシステムに対応したサーバーアプリケーションのサンプルプログラム(ubus-sample01)実装について紹介します。

ubus-sample01は、UBUSが提供するプロセス間通信を利用し、クライアントからの実行要求を受付けてその応答結果を返すアプリケーションです。

このアプリケーションをOpenWrtデバイスにインストールすると、ubus-sample01の機能が利用できるようになります。 具体的には、クライアント側でubus call sample01 check_json_formatなどを実行することで、 ubus-sample01の特定処理(例:check_json_format)を呼び出すことが可能になります。

このときクライアント側で実行するコマンドは、別の記事「UBUSコマンドの使い方」で紹介したUBUSのcallコマンドです。

つまり、今回はこのcallコマンドに対応したUBUS対応アプリケーションの実装について取り上げます。

それでは、行ってみましょう。

※UBUSの仕組みについて知りたい方はこちらを読んでみてください。
UBUSコマンドの使い方(作成中)

UBUS学習の参考サイト紹介
UBUSの学習に役立つサイトを紹介します。以下のサイトを読むことでコマンドの使用方法だけでなく、その実装方法についても理解できます。 私の記事と併用して読むとより理解に繋がると思います。

  1. What is UBUS?
  2. OpenWRT UBUS mechanism

目次

サンプルプログラム(ubus-sample01)のダウンロード

今回取り上げるubus-sample01のソースは私のGitHubリポジトリ(UtakamoStudyApps)にあります。

読者ご自身の環境で本アプリをビルド・インストールしてみたい方は私のGitHubリポジトリをダウンロードするか、 次のようにクローンしてください。

OpenWrt:~# git clone https://github.com/utakamo/UtakamoStudyApps.git

ビルド方法に関してはGitHubのリポジトリ説明を参照するか、以下の記事をご覧ください。
【第 6 回】OpenWrt開発入門 自作アプリのパッケージ作成とインストール - パッケージ作成手順

【ラズパイ限定】このサイトからパッケージを入手してインストールする。

このサイトでは、ラズベリーパイ限定でパッケージを配布しています。 シリーズ別のパッケージを以下に掲載しますので、インストールしてみてください。

モデル パッケージ ハッシュ(SHA256)
Raspberry Pi1 ubus-sample01_1.0-1_arm_arm1176jzf-s_vfp.ipk 9fa0bb4dfa60f27c3beeb0b0b86a570d4577f481c8ead7bc28d35ae8ec3ea7c1
Raspberry Pi2 ubus-sample01_1.0-1_arm_cortex-a7_neon-vfpv4.ipk 1a345977f494d1e5d4fef1cf974cdd7592e821ff283707306fb10e329f008dc3
Raspberry Pi3 ubus-sample01_1.0-1_aarch64_cortex-a53.ipk f1043b2e773dc8b37cc8f887c0e3ee45fc1d2ceca2e24de2d6b9c542350f6ef5
Raspberry Pi4 ubus-sample01_1.0-1_aarch64_cortex-a72.ipk bdd486f5f3e6e892846549e2744659ddafeeb594bcc89a756752bae5028f81ac

次のコマンドを実行すると、パッケージをインストールすることができます。(以下はRaspberry Pi3用パッケージのインストール例です。)

root@OpenWrt:~# wget https://utakamo.com/repo/openwrt/raspi3/ubus/ubus-sample01_1.0-1_aarch64_cortex-a53.ipk
root@OpenWrt:~# opkg install ubus-sample01_1.0-1_aarch64_cortex-a53.ipk

それでは、次節から説明に入ります。

サンプルプログラム(ubus-sample01)の概要

記事の冒頭で説明したように、今回取り上げるubus-sample01はUBUSのcallコマンドに対応した アプリケーションです。

UBUSのcallコマンドはUBUS対応アプリケーションが外部公開する機能を呼び出すコマンドです。

そのため、UBUS対応アプリケーションであるubus-sample01には外部へ自身の機能を公開するための処理が実装されています。

外部公開する機能は、ubus上ではオブジェクト<path>とメソッド<method>と呼ばれる単位で管理されます。

オブジェクトとは、関連する機能を集めたグループです。
通常、関連する機能のまとまりは同一のオブジェクトで管理されます。今回はubus-sample01の公開オブジェクトとしてsample01を 定義し、その中でメソッドを管理しています。(UBUS対応アプリケーションは複数のオブジェクトを持つことができますが、ubus-sample01は1つのオブジェクト(sample01)のみ持ちます。)

メソッドとは、公開機能そのものの名前です。
callコマンドではオブジェクトに続いてメソッドを引数に指定することで、該当機能を持つアプリケーションに実行要求を出します。

今回、ubus-sample01はsample01オブジェクトの中で以下の3つのメソッドを管理しています。

メソッド名 機能説明
check_json_format 応答結果(json)出力のサンプル処理です。
show_uci_option ubus-sample01専用のUCIコンフィグレーションファイルを内容を応答結果で返します。
update_uci_option ubus-sample01専用のUCIコンフィグレーションファイルの内容を指定引数で更新します。

メソッドの中にはJSON形式の引数が必要なものがあります。 今回の場合では、update_uci_optionメソッドの呼び出しでJSON形式の引数(例:'{"option":"data1","value":"Hello World!!"}')が必要です。

以上から、OpenWrtのコマンドプロンプトからubus-sample01の各公開機能を呼び出す例は以下のようになります。

root@OpenWrt:~# ubus call sample01 check_json_format
root@OpenWrt:~# ubus call sample01 show_uci_option
root@OpenWrt:~# ubus call sample01 update_uci_option '{"option":"data1","value":"Hello World!!"}'

サンプルプログラム(ubus-sample01)の概要説明は以上になります。
次節では、UBUS対応アプリケーションの補足事項とubus-sample01の各メソッドの詳細を説明します。

補足事項とサンプルプログラム(ubus-sample01)の詳細説明

UBUSに関連する補足事項と今回のubus-sample01の各メソッド詳細について説明します。

補足:オブジェクトとメソッドの登録タイミング

ubus-sample01の公開機能を管理するオブジェクトとメソッドは、クライアントからの実行依頼が来る前にUBUSシステム(ubusd)に登録されている必要があります。

通常、UBUSシステムへの登録契機は以下の3つです。

  1. アプリインストール時
  2. システムブート時(アプリ起動時)
  3. アプリの再起動時

以上の理由から、ubus-sample01をOpenWrtデバイスにインストールしたからと言って、システム稼働中の全期間でいつでも使えるとは限りません。 UBUSにubus-sample01の機能が登録されるか否かのタイミングで呼び出したい場合は、UBUSのwait_forコマンドを使用して、 UBUSシステムへの機能登録を待つ必要があります。

UBUS対応アプリケーションをシステムブートなどのタイミングで使用する際に少しだけ意識してみてください。

補足:登録オブジェクト・メソッドの確認

UBUSシステムへのオブジェクトとメソッドの登録が完了すると、以降、クライアント側はcallコマンドを実行することで その公開機能を利用できます。

通常、利用可能となったオブジェクト・メソッドはUBUSのlistコマンドを以下のように実行することで確認できます。

root@OpenWrt:~# ubus -v list sample01
'ubus-sample01' @2cb5db60
	"check_json_format":{}
	"show_uci_option":{}
	"update_uci_option":{"option":"String","value":"String"}

上記はubus-sample01の公開オブジェクト・メソッドをリストアップしてみた例です。 前節で紹介したメソッドの呼び出し仕様が表示されていることが分かります。

今回表示されたオブジェクト・メソッドの内容はJSON形式のデータです。 ubusdが記録しているubus-sample01のオブジェクト・メソッド情報が応答結果として出力されています。

listコマンドによる公開機能の表示問い合わせでは、ubus-sample01自体に問い合わせが行くわけではありません。窓口の UBUSシステム(ubusd)がlistコマンドの問い合わせを引き受け、クライアント側に問い合わせ結果(応答)を返します。

ubus call ubus-sample01 check_json_format

ubus-sample01の公開機能の1つで、JSON形式のデータ出力をテストしたものです。 実行要求を出すと、以下のような結果が呼び出し元に返ります。

root@OpenWrt:~# ubus call sample01 check_json_format
{
	"Description": "[ubus json format]",
	"string": "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
	"bool": true,
	"numeric data": 100,
	"table": {
		"table-element1": "string data",
		"table-element2": true,
		"table-element3": 200
	},
	"Note": "Please read this source code for detailed usage."
}

この機能は、UBUS対応アプリケーションがUBUSを介して送ることができるJSONのデータ型をすべて出力します。 JSONデータの出力結果とその実装ソースを比較することで、JSON出力のUBUS関連関数の使い方が理解できることを目的に作成した機能です。

実行の流れ
処理主体はubus-sample01なので、クライアント側からcallコマンドが実行されると、次のようにUBUSシステム(ubusd)を経由して ubus-sample01の実行プロセスに要求が通知されます(①)。

実行要求を受け取ると、ubus-sample01側でメソッド名check_json_formatに対応する関数が 呼び出されます。

そして、その中でJSON出力処理が実行されるため、結果としてクライアント側にJSONデータが通知されます(②)。

callコマンドによって、実行される公開機能はすべてこの流れで処理されます。 ただし、クライアント側にJSONデータを返さない機能もありますので、注意が必要です。

ubus call ubus-sample01 show_uci_option

クライアントからcallコマンドによって呼び出されると、ubus-sample01の専用UCIコンフィグレーションファイル (/etc/config/ubus-sample01)を読み込み、その内容をJSONとして返す機能です。

root@OpenWrt:~# ubus call sample01 show_uci_option
{
	"Description": "Output /etc/config/ubus-sample01",
	"data1": "100",
	"data2": "200",
	"data3": "300"
}

基本的な実装は上記で説明したcheck_json_formatと変わりません。 そのため、クライアント側でcallコマンドを使用すると、UBUSシステム(ubusd)を経由して show_uci_optionメソッドの実行要求がubus-sample01に通知されます(①)。

show_uci_optionメソッドは、内部でUCIコンフィグレーションファイル(/etc/config/ubus-sample01)を読み込みます。 このコンフィグレーションファイルの読み込みはUCIコマンドのC言語実装であるlibuciライブラリを使用して実現しています。

具体的には、UCIのgetコマンドを内部実装し、それを呼び出すことでUCIコンフィグレーションファイルを読み込んでいます(②)。 ※UCIのgetコマンド相当のC言語実装は本記事の「おまけ(UCIコマンド実装)」で取り上げています。興味があれば参照してみてください。

そして、最後に②で取得したUCIオプションの値をJSONとして作成し、クライアントに返します。

このように、ただ静的な内容のJSONを脊髄反射のように返すだけでなく、callコマンドを契機として特定処理を実行し、その処理で得た情報を JSONとして返すことも当然出来ます。

ubus call sample01 update_uci_option '{"option":"data2","value":"Hello"}'

引数(例:'{"option":"data2","value":"Hello"}')を伴ってクライアントからcallコマンドで呼び出されると、 与えられ引数の値でUCIコンフィグレーションファイル (/etc/config/ubus-sample01)を更新します。

root@OpenWrt:~# ubus call sample01 update_uci_option '{"option":"data2","value":"Hello"}'
{
	"Result": "[UCI SET SUCCESS] Please check ubus-sample01 config!!",
	"Note": "ex) user~# uci show ubus-sample01"
}

基本的な実装は上記で説明したcheck_json_formatやshow_uci_optionと変わりません。 そのため、クライアント側でcallコマンドを使用すると、UBUSシステム(ubusd)を経由して update_uci_optionメソッドの実行要求がubus-sample01に通知されます(①)。

コンフィグレーションファイルの更新(書き込み)はUCIコマンドのC言語実装であるlibuciライブラリを使用して実現しています。

具体的には、UCIのsetコマンドを内部実装し、それを呼び出すことでUCIコンフィグレーションファイルを新しい値のものに更新しています。 ※UCIのsetコマンド相当のC言語実装は本記事の「おまけ(UCIコマンド実装)」で取り上げています。興味があれば参照してみてください。

正常に処理が完了すると、コンフィグレーションファイルを更新した旨を呼び出し元のクライアントにJSONで通知します(③)。 逆にエラーが発生した場合は、その内容をクライアントに通知します。

以上が、サンプルプログラム(ubus-sample01)の各メソッドの詳細です。

ソースコードと実行結果の掲載

ubus-sample01の初期化スクリプト

#!/bin/sh /etc/rc.common

USE_PROCD=1
START=99
STOP=10

start_service() {
    echo 'ubus-sample01 start'
    procd_open_instance 'ubus-sample01'
    procd_set_param command /usr/bin/ubus-sample01
    procd_close_instance
}

ubus-sample01のソースコード

//ubus-sample01.c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <syslog.h>
#include <time.h>
#include <ctype.h>
#include <libubus.h>
#include <libubox/uloop.h>
#include <libubox/blobmsg_json.h>
#include <libubox/blobmsg.h>
#include <uci.h>

enum {
	UCI_SET_INFO_OPTION,
	UCI_SET_INFO_VALUE,
	UCI_SET_INFO_MAX,
};

static struct blob_buf blob;

/* Ubus method policy */
static const struct blobmsg_policy check_json_format_policy[] = {};
static const struct blobmsg_policy show_uci_option_policy[] = {};
static const struct blobmsg_policy update_uci_option_policy[] =
{
	[UCI_SET_INFO_OPTION] = { .name="option", .type=BLOBMSG_TYPE_STRING },
	[UCI_SET_INFO_VALUE] = { .name="value", .type=BLOBMSG_TYPE_STRING },
};


/* ubus methods */
static int check_json_format(struct ubus_context *ctx, struct ubus_object *obj,
			  struct ubus_request_data *req, const char *method,
			  struct blob_attr *msg);

static int show_uci_option(struct ubus_context *ctx, struct ubus_object *obj,
			  struct ubus_request_data *req, const char *method,
			  struct blob_attr *msg);

static int update_uci_option(struct ubus_context *ctx, struct ubus_object *obj,
			  struct ubus_request_data *req, const char *method,
			  struct blob_attr *msg);


void ubus_process(void);

//Function equivalent to the uci get command.
bool uci_get_option(char* str, char* value);

//Function equivalent to the uci set command.
bool uci_set_option(char* str);

static void ubus_sample_handle_signal(int signo)
{
	uloop_end();
}

static void ubus_sample_setup_signals(void)
{
	struct sigaction s;

	memset(&s, 0, sizeof(s));
	s.sa_handler = ubus_sample_handle_signal;
	s.sa_flags = 0;
	sigaction(SIGTERM, &s, NULL);
}


int main(int argc, char** argv)
{
	ubus_sample_setup_signals();
	ubus_process();

	return EXIT_SUCCESS;
}

// Ubus method functions
//output the uci configuration file.
//Execution is triggered at startup and [/etc/init.d/ubus-sample01].
int check_json_format(struct ubus_context *ctx, struct ubus_object *obj,
				struct ubus_request_data *req, const char *method,
				struct blob_attr *msg) {
	void *s;
	blob_buf_init(&blob, 0);
	blobmsg_add_string(&blob, "Description", "[ubus json format]");
	blobmsg_add_string(&blob, "string", "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
	blobmsg_add_u8(&blob, "bool", true);
	blobmsg_add_u32(&blob, "numeric data", 100);
	s = blobmsg_open_table(&blob, "table");
	blobmsg_add_string(&blob, "table-element1", "string data");
	blobmsg_add_u8(&blob, "table-element2", true);
	blobmsg_add_u32(&blob, "table-element3", 200);
	blobmsg_close_table(&blob, s);
	blobmsg_add_string(&blob, "Note", "Please read this source code for detailed usage.");
	ubus_send_reply(ctx, req, blob.head);

	return 0;
}

int show_uci_option(struct ubus_context *ctx, struct ubus_object *obj,
			  struct ubus_request_data *req, const char *method,
			  struct blob_attr *msg) {

	char data1[256] = {0};
	char data2[256] = {0};
	char data3[256] = {0};

	bool is_option = false;
	//uci get ubus-sample01.test.user
	uci_get_option("ubus-sample01.test.data1", data1);
	uci_get_option("ubus-sample01.test.data2", data2);
	uci_get_option("ubus-sample01.test.data3", data3);

	blob_buf_init(&blob, 0);
	blobmsg_add_string(&blob, "Description", "Output /etc/config/ubus-sample01");
	blobmsg_add_string(&blob, "data1", data1);
	blobmsg_add_string(&blob, "data2", data2);
	blobmsg_add_string(&blob, "data3", data3);
	ubus_send_reply(ctx, req, blob.head);

	return 0;
}


int update_uci_option(struct ubus_context *ctx, struct ubus_object *obj,
			  struct ubus_request_data *req, const char *method,
			  struct blob_attr *msg) {

	struct blob_attr *tb[UCI_SET_INFO_MAX];

	blobmsg_parse(update_uci_option_policy, UCI_SET_INFO_MAX, tb, blob_data(msg), blob_len(msg));

	if ((!tb[UCI_SET_INFO_OPTION]) || (!tb[UCI_SET_INFO_VALUE])){
		blob_buf_init(&blob, 0);
		blobmsg_add_string(&blob, "Result", "[UCI SET FAILED] NO INPUT or INSUFFICIENT JSON DATA!!");
		ubus_send_reply(ctx, req, blob.head);

		return -1;
	}

	const char *option = blobmsg_get_string(tb[UCI_SET_INFO_OPTION]);
	const char *value = blobmsg_get_string(tb[UCI_SET_INFO_VALUE]);

	if (!option || !value) {
		blob_buf_init(&blob, 0);
		blobmsg_add_string(&blob, "Result", "[UCI SET FAILED] ERROR RETRIEVING UCI OPTION!!");
		ubus_send_reply(ctx, req, blob.head);

		return -1;
	}

	char update_data[1024];
	snprintf(update_data, sizeof(update_data), "ubus-sample01.test.%s=%s", option, value);

	bool result = uci_set_option(update_data);

	if (!result) {
		blob_buf_init(&blob, 0);
		blobmsg_add_string(&blob, "Result", "[UCI SET FAILED] UCI ERROR!!");
		ubus_send_reply(ctx, req, blob.head);

		return -1;
	}

	blob_buf_init(&blob, 0);
	blobmsg_add_string(&blob, "Result", "[UCI SET SUCCESS] Please check ubus-sample01 config!!");
	blobmsg_add_string(&blob, "Note", "ex) user~# uci show ubus-sample01");
	ubus_send_reply(ctx, req, blob.head);

	return 0;
}

/* Ubus object methods */
const struct ubus_method ubus_sample_methods[] =
{
	/* UBUS_METHOD(method_name, method_call_function, method_policy) */
	UBUS_METHOD("check_json_format", check_json_format, check_json_format_policy),
	UBUS_METHOD("show_uci_option", show_uci_option, show_uci_option_policy),
	UBUS_METHOD("update_uci_option", update_uci_option, update_uci_option_policy),
};

/* Ubus object type */
struct ubus_object_type ubus_sample_obj_type = UBUS_OBJECT_TYPE("ubus-sample01-uobj", ubus_sample_methods);

/* Ubus object */
struct ubus_object ubus_sample_object=
{
	.name = "sample01", //objpath
	.type = &ubus_sample_obj_type,
	.methods = ubus_sample_methods,
	.n_methods = ARRAY_SIZE(ubus_sample_methods),
};

void ubus_process(void) {
	uloop_init();
	struct ubus_context *ctx = ubus_connect(NULL);
	ubus_add_uloop(ctx);
	ubus_add_object(ctx, &ubus_sample_object);
	uloop_run();
	uloop_done();
	return;
}

//Function equivalent to the uci get command.
bool uci_get_option(char* str, char* value){
	struct uci_context *ctx;
	struct uci_ptr ptr;

	char* param = strdup(str);

	ctx = uci_alloc_context();

	if (param == NULL) {
		return false;
	}

	if (ctx == NULL) {
		return false;
	}

	if (uci_lookup_ptr(ctx, &ptr, param, true) != UCI_OK) {
		uci_perror(ctx, "uci set error");
		uci_free_context(ctx);
		return false;
	}

	if (ptr.o != 0 && ptr.o->type == UCI_TYPE_STRING) {
		if (sizeof(value) <= sizeof(ptr.o->v.string)) {
			strcpy(value, ptr.o->v.string);
		}
	}

	uci_free_context(ctx);
	free(param);
	return true;
}

//Function equivalent to the uci set command.
bool uci_set_option(char* str) {
	struct uci_context *ctx;
	struct uci_ptr ptr;
	int ret = UCI_OK;

	ctx = uci_alloc_context();

	char* param = strdup(str);

	if (uci_lookup_ptr(ctx, &ptr, param, true) != UCI_OK) {
		uci_perror(ctx, "uci set error");
		uci_free_context(ctx);
		return false;
	}

	if (ptr.value)
		ret = uci_set(ctx, &ptr);
	else {
		ret = UCI_ERR_PARSE;
		uci_free_context(ctx);
		return false;
	}

	if (ret == UCI_OK) {
		uci_save(ctx, ptr.p);
		uci_commit(ctx, &ptr.p, true);
	}

	uci_free_context(ctx);
	return true;
}

ubus-sample01インストール後のコンソール操作

ubus-sample01のメソッド一覧を表示
root@OpenWrt:~# ubus -v list sample01
'ubus-sample01' @2cb5db60
	"check_json_format":{}
	"show_uci_option":{}
	"update_uci_option":{"option":"String","value":"String"}

ubus-sample01のcheck_json_formatを実行
root@OpenWrt:~# ubus call sample01 check_json_format
{
	"Description": "[ubus json format]",
	"string": "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
	"bool": true,
	"numeric data": 100,
	"table": {
		"table-element1": "string data",
		"table-element2": true,
		"table-element3": 200
	},
	"Note": "Please read this source code for detailed usage."
}

ubus-sample01のshow_uci_optionを実行
root@OpenWrt:~# ubus call sample01 show_uci_option
{
	"Description": "Output /etc/config/ubus-sample01",
	"data1": "100",
	"data2": "200",
	"data3": "300"
}

ubus-sample01のupdate_uci_optionを実行
root@OpenWrt:~# ubus call sample01 update_uci_option '{"option":"data2","value":"Hello!!"}'
{
	"Result": "[UCI SET SUCCESS] Please check ubus-sample01 config!!",
	"Note": "ex) user~# uci show ubus-sample01"
}

update_uci_optionで変更したオプション値をuci showコマンドで確認
root@OpenWrt:~# uci show sample01
ubus-sample01.test=sample
ubus-sample01.test.data1='100'
ubus-sample01.test.data2='Hello!!'
ubus-sample01.test.data3='300'
root@OpenWrt:~# ubus call ubus-sample01 show_uci_option
{
	"Description": "Output /etc/config/ubus-sample01",
	"data1": "100",
	"data2": "Hello!!",     <--- 変更されたことが確認できる
	"data3": "300"
}

ポイントをピックアップ

root@OpenWrt:~# ubus -v list sample01
'ubus-sample01' @2cb5db60
    "check_json_format":{}
    "show_uci_option":{}
    "update_uci_option":{"option":"String","value":"String"}
root@OpenWrt:~# ubus call sample01 check_json_format
{
    "Description": "[ubus json format]",
    "string": "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
    "bool": true,
    "numeric data": 100,
    "table": {
        "table-element1": "string data",
        "table-element2": true,
        "table-element3": 200
    },
    "Note": "Please read this source code for detailed usage."
}
int check_json_format(struct ubus_context *ctx, struct ubus_object *obj,
                        struct ubus_request_data *req, const char *method,
                        struct blob_attr *msg) {

    void *s;
    blob_buf_init(&blob, 0);
    blobmsg_add_string(&blob, "Description", "[ubus json format]");
    blobmsg_add_string(&blob, "string", "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
    blobmsg_add_u8(&blob, "bool", true);
    blobmsg_add_u32(&blob, "numeric data", 100);
    s = blobmsg_open_table(&blob, "table");
    blobmsg_add_string(&blob, "table-element1", "string data");
    blobmsg_add_u8(&blob, "table-element2", true);
    blobmsg_add_u32(&blob, "table-element3", 200);
    blobmsg_close_table(&blob, s);
    blobmsg_add_string(&blob, "Note", "Please read this source code for detailed usage.");
    ubus_send_reply(ctx, req, blob.head);

    return 0;
}
root@OpenWrt:~# ubus call sample01 show_uci_option
{
    "Description": "Output /etc/config/ubus-sample01",
    "data1": "100",
    "data2": "200",
    "data3": "300"
}
int show_uci_option(struct ubus_context *ctx, struct ubus_object *obj,
                    struct ubus_request_data *req, const char *method,
                    struct blob_attr *msg) {

    char data1[256] = {0};
    char data2[256] = {0};
    char data3[256] = {0};

    bool is_option = false;
    //uci get ubus-sample01.test.user
    uci_get_option("ubus-sample01.test.data1", data1);
    uci_get_option("ubus-sample01.test.data2", data2);
    uci_get_option("ubus-sample01.test.data3", data3);

    blob_buf_init(&blob, 0);
    blobmsg_add_string(&blob, "Description", "Output /etc/config/ubus-sample01");
    blobmsg_add_string(&blob, "data1", data1);
    blobmsg_add_string(&blob, "data2", data2);
    blobmsg_add_string(&blob, "data3", data3);
    ubus_send_reply(ctx, req, blob.head);

    return 0;
}
root@OpenWrt:~# ubus call sample01 update_uci_option '{"option":"data2","value":"Hello!!"}'
{
    "Result": "[UCI SET SUCCESS] Please check ubus-sample01 config!!",
    "Note": "ex) user~# uci show ubus-sample01"
}
int update_uci_option(struct ubus_context *ctx, struct ubus_object *obj,
                        struct ubus_request_data *req, const char *method,
                        struct blob_attr *msg) {

    struct blob_attr *tb[UCI_SET_INFO_MAX];

    blobmsg_parse(update_uci_option_policy, UCI_SET_INFO_MAX, tb, blob_data(msg), blob_len(msg));

    if ((!tb[UCI_SET_INFO_OPTION]) || (!tb[UCI_SET_INFO_VALUE])){
        blob_buf_init(&blob, 0);
        blobmsg_add_string(&blob, "Result", "[UCI SET FAILED] NO INPUT or INSUFFICIENT JSON DATA!!");
        ubus_send_reply(ctx, req, blob.head);

        return -1;
    }

    const char *option = blobmsg_get_string(tb[UCI_SET_INFO_OPTION]);
    const char *value = blobmsg_get_string(tb[UCI_SET_INFO_VALUE]);

    if (!option || !value) {
        blob_buf_init(&blob, 0);
        blobmsg_add_string(&blob, "Result", "[UCI SET FAILED] ERROR RETRIEVING UCI OPTION!!");
        ubus_send_reply(ctx, req, blob.head);

        return -1;
    }

    char update_data[1024];
    snprintf(update_data, sizeof(update_data), "ubus-sample01.test.%s=%s", option, value);

    bool result = uci_set_option(update_data);

    if (!result) {
        blob_buf_init(&blob, 0);
        blobmsg_add_string(&blob, "Result", "[UCI SET FAILED] UCI ERROR!!");
        ubus_send_reply(ctx, req, blob.head);

        return -1;
    }

    blob_buf_init(&blob, 0);
    blobmsg_add_string(&blob, "Result", "[UCI SET SUCCESS] Please check ubus-sample01 config!!");
    blobmsg_add_string(&blob, "Note", "ex) user~# uci show ubus-sample01");
    ubus_send_reply(ctx, req, blob.head);

    return 0;
}

おまけ(UCIコマンド実装)

//Function equivalent to the uci get command.
bool uci_get_option(char* str, char* value){
    struct uci_context *ctx;
    struct uci_ptr ptr;

    char* param = strdup(str);

    ctx = uci_alloc_context();

    if (param == NULL) {
        return false;
    }

    if (ctx == NULL) {
        return false;
    }

    if (uci_lookup_ptr(ctx, &ptr, param, true) != UCI_OK) {
        uci_perror(ctx, "uci set error");
        uci_free_context(ctx);
        return false;
    }

    if (ptr.o != 0 && ptr.o->type == UCI_TYPE_STRING) {
        if (sizeof(value) <= sizeof(ptr.o->v.string)) {
            strcpy(value, ptr.o->v.string);
        }
    }

    uci_free_context(ctx);
    free(param);
    return true;
}
//Function equivalent to the uci set command.
bool uci_set_option(char* str) {
    struct uci_context *ctx;
    struct uci_ptr ptr;
    int ret = UCI_OK;

    ctx = uci_alloc_context();

    char* param = strdup(str);

    if (uci_lookup_ptr(ctx, &ptr, param, true) != UCI_OK) {
        uci_perror(ctx, "uci set error");
        uci_free_context(ctx);
        return false;
    }

    if (ptr.value)
        ret = uci_set(ctx, &ptr);
    else {
        ret = UCI_ERR_PARSE;
        uci_free_context(ctx);
        return false;
    }

    if (ret == UCI_OK) {
        uci_save(ctx, ptr.p);
        uci_commit(ctx, &ptr.p, true);
    }

    uci_free_context(ctx);
    return true;
}

おわりに

作成中

参考文献