在openwrt使用C语言增加ubus接口(包含C uci操作)
本文主要讲解在openwrt中,如何使用C语言实现增加自己的ubus接口,其中会涉及一些调用uci接口的操作。
创建自己的软件包
软件包结构
首先需要下载openwrt的源码,并且自己编译出镜像文件并创建虚拟机。
在openwrt的package中创建自己的软件包,如下图所示。
- files:配置文件和启动脚本
- Makefile:控制这个软件包的编译
- src:代码和编译代码的Makefile
编写代码和启动脚本(重点)
案例大致分析
接下来就举个简单的案例:实现一个ubus接口,提供查看uci配置文件内容的功能。
- 先从files开始分析,这个案例中,我们不需要用到配置文件,所以不用去编写conf的内容。
- 因为是要实现ubus接口,所以必须将其加入procd管理,可以在启动脚本中进行编写(内容不深入分析)。
- 其次就是编写我们软件的主要代码。
- src下的Makefile主要功能就是编译我们的代码,是最简单的Makefile。
- 软件包下顶层的Makefile用来控制整个软件包的编译,其中大部分都是模板,需要修改的地方并不多。
实现过程
ubus_demo.init
- 脚本实现的主要功能:加入procd管理、监控配置文件的变化。
- 因为设置了USE_PROCD=1,所以start、stop、reload函数名变成start_service、stop_service、reload_service。
- START、STOP变量设置是控制程序在/etc/init.d中的启动、停止顺序。
- service_triggers设置了触发器,当配置文件ubus_demo(/etc/config/ubus_demo.conf)发生改变,就会触发reload_service方法。(这里我们没有编写ubus_demo.conf,所以这个方法并不会起作用)
- start_service方法中,使用procd提供的方法将这个程序加入到procd管理(成为’杀不死’的守护进程)
#!/bin/sh /etc/rc.common
# ubus_demo script
USE_PROCD=1
START=15
STOP=85
PROG=/etc/ubus_demo
service_triggers() {
procd_add_reload_trigger "ubus_demo"
}
start_service() {
echo "start ubus_demo!"
procd_open_instance
procd_set_param command "$PROG"
procd_set_param respawn
procd_set_param file "/etc/config/ubus_demo"
procd_close_instance
}
stop_service() {
echo "stop ubus_demo!"
}
reload_service() {
stop
echo "reloading"
start
}
ubus_demo.c
所需头文件:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <uci.h>
#include <libubus.h>
大致可以分成以下三部分来完成:
- 首先是main函数,里面是ubus注册接口流程,基本都是不变的。
- 其次是第二个框,设置ubus_object对象,设置ubus接口一些信息,包括方法名和调用的处理方法。
- 最后是第一个框,编写对应调用的处理方法(重要)。
main函数中注册ubus接口
- 大致流程是固定套路,重点在ubus_add_object这个方法,其中的demo_object是需要我们自己编写设计的。
- UBUSD_SOCK是ubus套接字的路径(/var/run/ubus/ubus.sock)。
int main(int argc, char* argv[])
{
// 创建epoll句柄
uloop_init();
// 建立连接
struct ubus_context *ctx = ubus_connect(UBUSD_SOCK);
if (ctx == NULL) {
printf("ctx null...\n");
} else {
printf("ctx not null\n");
}
// 向ubusd请求增加一个新的object(这个object在第二部分中实现)
ubus_add_object(ctx, &demo_object);
// 把建立的连接加入到uloop中
ubus_add_uloop(ctx);
// 等待事件发生,调用对应函数
uloop_run();
// 关闭连接
ubus_free(ctx);
// 关闭epoll句柄
uloop_done();
return 0;
}
设计ubus_object
- ubus_object中主要需要有接口对象名称,以及对应的调用方法
- ubus_method中设置对应调用的方法,两种实现方法,代码注释中有详细解释
// * ubus_object对象中注册的方法
static struct ubus_method demo_methods[] = {
// * 添加成员方法的操作(policy是传入的参数) { .name = "uci_get", .handler = get_uci, .policy = stu_policy, .n_policy = ARRAY_SIZE(stu_policy) },
// * 没有传参可以省略{ .name = "test", .handler = func },
// * 使用宏定义的方法来简化操作 UBUS_METHOD(ubus调用名,对应处理函数,传入的参数)
UBUS_METHOD("uci_get", get_uci, uci_get_policy), // uci_get就是ubus中的接口名称,get_uci是调用的处理函数
UBUS_METHOD("uci_set", set_uci, uci_set_policy) // 这边set的实现不详细讲
};
// * ubus_object对象中类型
static struct ubus_object_type demo_object_type =
UBUS_OBJECT_TYPE("test", demo_methods); // 直接调宏定义函数即可
// * 设置ubus_object对象
static struct ubus_object demo_object = {
.name = "test", // 接口对象名称
.type = &demo_object_type, // object类型
.methods = demo_methods, // 对应调用的方法
.n_methods = ARRAY_SIZE(demo_methods), // 方法个数
};
实现调用方法(重点)
- 需要定义好接受的参数个数及类型(blobmsg_policy)
#define PACKAGE_NAME 0
#define SECTION_NAME 1
#define OPTION_NAME 2
#define __UCI_GET_MAX 3
// .name是参数名称,.type是参数的数据类型
static const struct blobmsg_policy uci_get_policy[__UCI_GET_MAX] = {
[PACKAGE_NAME] = {.name = "package", .type = BLOBMSG_TYPE_STRING},
[SECTION_NAME] = {.name = "section", .type = BLOBMSG_TYPE_STRING},
[OPTION_NAME] = {.name = "option", .type = BLOBMSG_TYPE_STRING}
};
- 处理方法中参数列表是固定的(cv即可),实现过程分为三步:
- 解析参数:使用blobmsg_parse方法进行解析
- 处理过程:我们这边通过uci提供的接口去读取配置文件的内容
- 响应过程:使用ubus_send_reply方法来响应
// * get对应的处理方法
static int get_uci(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
char buf[256] = {0};
char value[256] = {0};
char package_name[256] = {0};
char section_name[256] = {0};
char option_name[256] = {0};
struct blob_attr *param[__UCI_GET_MAX] = {0};
struct uci_context *uci_ctx = uci_alloc_context(); // 分析uci上下文内容
struct uci_package *pkg = NULL;
struct uci_section *sec = NULL;
struct uci_option *opt = NULL;
struct uci_ptr ptr = {0};
struct blob_buf b_buf = {0};
// * 解析传入的参数
blobmsg_parse(uci_get_policy, ARRAY_SIZE(uci_get_policy), param, blob_data(msg), blob_len(msg));
// * 处理过程
if (uci_ctx == NULL) {
return -1;
}
snprintf(package_name, sizeof(package_name), "%s", blobmsg_get_string(param[PACKAGE_NAME]));
snprintf(section_name, sizeof(section_name), "%s", blobmsg_get_string(param[SECTION_NAME]));
snprintf(option_name, sizeof(option_name), "%s", blobmsg_get_string(param[OPTION_NAME]));
if (uci_load(uci_ctx, package_name, &pkg) != UCI_OK) { // 加载配置文件到pkg中
snprintf(package_name, sizeof(package_name), "package no exist");
goto RESPONSE;
}
sec = uci_lookup_section(uci_ctx, pkg, section_name); // 读取pkg中的配置节到sec中
if (sec == NULL) {
snprintf(section_name, sizeof(section_name), "section no exist");
goto RESPONSE;
}
opt = uci_lookup_option(uci_ctx, sec, option_name); // 读取sec中的选项内容到opt中
if (opt == NULL) {
snprintf(option_name, sizeof(option_name), "option no exist");
goto RESPONSE;
}
// 根据buf读取内容到ptr中
snprintf(buf, sizeof(buf), "%s.%s.%s", package_name, section_name, option_name);
if (uci_lookup_ptr(uci_ctx, &ptr, buf, true) == UCI_OK) { // 读取选项中的值到ptr中
strncpy(value, ptr.o->v.string, sizeof(value) - 1);
}
RESPONSE:
uci_free_context(uci_ctx); // 释放uci上下文内容
// * 响应过程
blob_buf_init(&b_buf, 0); // 初始化blob_buf
// * blobmsg_add_* 可以添加不同类型
blobmsg_add_string(&b_buf, "response_config", package_name);
blobmsg_add_string(&b_buf, "response_section", section_name);
blobmsg_add_string(&b_buf, "response_option", option_name);
blobmsg_add_string(&b_buf, "response_value", value);
// 响应
ubus_send_reply(ctx, req, b_buf.head); // 响应内容给req
blob_buf_free(&b_buf); // 释放blob_buf
return 0;
}
Makefile编写
src下的Makefile只是需要编译.c文件,记得添加编译选项-luci -lubus -lbox
.PHONY:all clean
all:
$(CC) $(CFLAGS) -g -luci -lubus -lubox ubus_demo.c -o ubus_demo
clean:
rm *.o ubus_demo
软件包下顶层的Makefile是大头,直接在模板上修改就可以,我们需要修改的内容只有:
- PKG_NAME 是我们软件包的名称
- SECTION 和 CATEGORY 是我们在menuconfig中的分类
- DEPENDS 是依赖的链接库
- 最后package/install中是我们软件包的文件(.c .conf .init)
include $(TOPDIR)/rules.mk
include $(INCLUDE_DIR)/kernel.mk
PKG_NAME:=ubus_demo
PKG_RELEASE:=1.0
PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
PKG_CONFIG_DEPENDS :=
include $(INCLUDE_DIR)/package.mk
define Package/$(PKG_NAME)
SECTION:=utils
CATEGORY:=Milesight
TITLE:=ubus_demo utility
DEPENDS:=+libuci +libubus +libubox
endef
define Package/$(PKG_NAME)/description
This is Ubus Test OpenWrt.
endef
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./src/* $(PKG_BUILD_DIR)/
endef
define Build/Configure
endef
define Build/Compile
$(MAKE) -C $(PKG_BUILD_DIR) \
$(TARGET_CONFIGURE_OPTS) \
CFLAGS="$(TARGET_CFLAGS)" \
CPPFLAGS="$(TARGET_CPPFLAGS)"\
LDFLAGS="$(TARGET_LDFLAGS)"
endef
define Package/$(PKG_NAME)/install
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) ./files/ubus_demo.init $(1)/etc/init.d/ubus_demo
$(INSTALL_DIR) $(1)/etc
$(INSTALL_BIN) $(PKG_BUILD_DIR)/ubus_demo $(1)/etc/ubus_demo
endef
$(eval $(call BuildPackage,$(PKG_NAME)))
到这里,我们软件包的编写就完成了,接下来就是编译安装阶段了。
编译安装自己的软件包
编译
- 首先回到openwrt的顶层目录下,执行make menuconfig,进入配置菜单界面,找到自己的软件包
在自己的软件包中使用m选中,然后保存退出。
当然也有高效的方法–>直接对.config文件内容作修改,在.config中加上一句:CONFIG_PACKAGE_ubus_demo=m,这个是固定形式,可以了解并应用。
- 返回顶层目录,执行make package/ubus_demo/compile V=s进行编译,编译成功后可以进入安装。
安装/卸载
- 在openwrt系统中,可以使用opkg来进行安装和卸载。
- 其中卸载只需要程序名即可(也可以通过opkg list-installed | grep ubus_demo查看安装信息)
-
- 查看是否加入了procd管理(ubus call service list ‘{“name”:“ubus_demo”}’)
- 查看自己是否成功注册了ubus接口(ubus list)
现在也正常安装到openwrt中了,接下来进行测试。
测试ubus
- 查看我们所注册的方法(ubus -v list test)
- 试着调用方法,比如查看network中mng的ipaddr
- 调用ubus方法ubus call test uci_get ‘{“package”:“network”,“section”:“mng”,“option”:“ipaddr”}’
返回的值跟我们查看的值是一致的,说明这个方法成功调用了。
可能遇到的一些问题
参考本人整理的一些错误:ubus编译出错。