OpenWrt UCI框架
UCI简介
MVC(Model-View-Control)模式是经典的Web开发编程模式,OpenWrt也采用该设计模式,分层设计,模型层负责数据的持久化操作。OpenWrt 的模型层采用 统一配置接口(Unified Configuration Interface,UCI)。已经移植支持数千个软件,它采用纯文本文件来保存配置,并提供命令行和 C 语言编程调用接口进行管理。
UCI配置文件格式
config <type> ["<name>"]
option <name> "<value>"
# 说明:
# []里面的内容是可以省略的
# "" 或者''都可以 ,并且可以省略, 如果字符串内容有空格 不可以省略引号
示例如下图所示:

每个config 都称为一个配置节(section),每个选项对(option)都有name和value,并写在配置节中
图中1和2 是配置节的开始 ,3和4共同组成了一个选项对 ,用
#注释
UCI配置文件
UCI配置文件存放在
/etc/config/目录下,当执行/etc/init.d/的配置脚本的时候,系统会读取/etc/config/对应的UCI配置文件,然后把UCI配置文件转换为软件包能识别的配置文件,存放在/var目录下,所以软件包能识别配置文件是UCI转换生成的,不需要存储在flash,在ram中即可,可以看到/var是/tmp的一个软链接UCI配置文件修改方式
直接vim 修改
/etc/config/下对应的配置修改完后生效的方式,重启系统或者执行
/etc/init.d/对应的程序 restart通过UCI接口(shell、Lua、C)
例如web界面就是通过Lua的UCI接口修改
由于程序使用的配置文件是通过UCI配置文件经过
init.d转换过来的,故而如果只修改了/etc/config/对应的配置文件,而没有通过init.d重启应用,或者人为的直接手动启动的应用(反正就是不通过init.d),都不会使用/etc/config/下的配置,切记这一点。UCI配置文件一些说明
文 件 路 径
含 义
/etc/config/dhcpDnsmasq 软件包配置,包含 DHCP 和 DNS 设置
/etc/config/dropbearSSH 服务器选项
/etc/config/firewall防火墙配置,包含网络地址转换、包过滤和端口转发等
/etc/config/network网络配置,包含桥接、接口和路由配置
/etc/config/system系统设置,包含主机名称,网络时间同步等
/etc/config/timeserverrdate 的时间服务列表
/etc/config/luci基本的 LuCI 配置
/etc/config/wireless无线设置和 Wi-Fi 网络定义
/etc/config/uhttpdWeb 服务器选项配置
/etc/config/upnpdminiupnpd UPnP 服务设置
/etc/config/qos网络服务质量的配置文件定义
UCI SHELL接口API
下面代码是修改/etc/config/network目录下 config lan节下的ipaddr选项的案例
uci set network.lan.ipaddr=192.168.6.1
uci commit network # 提交 会写文件
UCI命令格式
uci [<options>] <command> [<arguments>]
options如下:
-c <path>:set the search path for config files (default: /etc/config)
-d <str>:set the delimiter for list values in uci show
-f <file>:use <file> as input instead of stdin
-m:when importing, merge data into an existing package
-n:name unnamed sections on export (default)
-N:don't name unnamed sections
-p <path>:add a search path for config change files
-P <path>:add a search path for config change files and use as default
-q:quiet mode (don't print error messages)
-s:force strict mode (stop on parser errors, default)
-S:disable strict mode
-X:do not use extended syntax on 'show'
command如下:
-add:增加指定配置文件的类型为 section-type 的匿名区段。
-add_list:对已存在的 list 选项增加字符串。
-commit:对给定的配置文件写入修改,如果没有指定配置文件,则将所有的配置文件写入文件系统。uci set、uci add、uci rename和uci delete命令将配置写入一个临时位置, 在运行uci commit时写入实际的存储位置。
-export:导出一个机器可读格式的配置。它是作为操作配置文件的 shell 脚本而在内部使用,导出配置内容时会在前面加“package”和文件名。
-import:以 UCI 语法导入配置文件。
-changes:列出配置文件分阶段修改的内容,即未使用uci commit提交的修改。如果没有指定配置文件,则指所有的配置文件的修改部分。
-show:显示指定的选项、配置节或配置文件。以精简的方式输出,即 key=value 的方式输出。
-get:获取指定区段选项的值。
-set:设置指定配置节选项的值,或者是增加一个配置节,类型设置为指定的值。
-delete:删除指定的配置节或选项。
-rename:对指定的选项或配置节重命名为指定的名字。
-revert:恢复指定的选项,配置节或配置文件。
如下图所示:

注意:有些配置的值并未在/etc/config中显示,而是保存在/var/state下,可以用-P选项查看:

UCI命令使用案例
修改ip
#1:设置或者删除选项 uci set network.lan.ipaddr=192.168.0.50 uci delete network.lan.ipaddr #2:提交修改 uci commit network 3:重启程序生效 /etc/init.d/network restart #注:可以看到我们可以通过network.lan.ipaddr 即[配置文件名字].[name].[optionname] 来找到对应的option,进而对其修改。 #访问option的方法还有如下两种: #方法2 uci get network.@interface[0].ipaddr # 值为127.0.0.1 uci get network.@interface[1].ipaddr # 值为192.168.0.50 #方法3 uci get network.@interface[-2].ipaddr # 值为127.0.0.1 uci get network.@interface[-1].ipaddr # 值为192.168.0.50

当文件文件中option为链表的时候如下图

增加一个list的方法如下:
uci add_list system.ntp.server='ntp.dongshao.net' uci commit system
刪除一個链表
uci del_list system.ntp.server='ntp.dongshao.net' uci commit system
新增一个配置文件
先创建一个文件
/etc/config/hello,新增内容如下:uci set hello.globe=system uci set hello.globe.agent=dongshao //用户代理属性 uci set hello.globe.url='https://blog.csdn.net/qq_41453285' //访问URL uci set hello.globe.delay=100 //启动延迟时间 uci commit //提交配置修改
修改后内如如下

UCI脚本
uci.sh
脚本介绍:UCI模块提供了一个 shell 脚本(/lib/config/uci.sh)并封装了UCI命令行工具的功能, 这样方便了其他软件包在将UCI配置文件转换为自己格式的配置文件时使用。主要的函数如下所示:
uci_load:从UCI文件中加载配置并设置到环境变量中,可以通过env命令来查看。该命令需要和 functions.sh 中的定义共同使用。uci_get:从配置文件中获取值。至少需要一个参数,指明要获取的配置信息。例如获取系统主机名称:uci_get system.@system[0].hostname。uci_get_state:指定从/var/state中获取状态值。
如果自己的脚本需要使用这些函数的时候,需要自行导入该/lib/config/uci.sh脚本,最好一起导入/lib/functions.sh脚本,因为/lib/config/uci.sh里面有些函数依赖/lib/functions.sh。
functions.sh
/lib/functions.sh主要原理是将配置文件中的配置选项设置到环境变量中,然后提供接口函数在环境变量中获取。调用uci_load函数将配置设置到环境变量中时,uci_load函数又调用了 functions.sh 定义的 config()、option()、list()等函数。在使用这些函数时,以点开头来将这些函数加载到执行空间中,注意点和执行文件中间有 一个空格。例如:
. /lib/functions.sh # 装载函数 以 . 开头 注意点后面有个空格
config:供uci.sh调用,将配置节设置到环境变量中。option:供uci.sh调用,将配置节中的选项设置到环境变量中。list:供uci.sh调用,将配置节中的链表配置设置到环境变量中。config_load:调用uci_load函数来从配置文件中读取配置选项,并设置到环境变量中。config_get:从当前设置环境变量中获取配置值。config_get_bool:从当前设置的环境变量中获取布尔值,并将它进行格式转换,如果为真,转换为1,否则转换为0。因为 UCI 的布尔值有多种类型均支持。on、true、enabled 和 1 表示真
off、false、disable 和0表示假。
config_set:将变量设置到环境变量中以便后续读取。注意:仅设置到环境变量中并没有设置到配置文件中。
config_foreach:对于未命名的配置进行遍历调用函数。共两个参数,第一个参数为回调函数,第 二个参数为配置节类型,这个函数适用于匿名配置节的转换处理。
配置文件选项的写入与读取
functions.sh的主要原理是将配置文件中的配置选项设置到环境变量中,然后提供接口函数在环境变量中获取,下面我们以config_load与config_get函数为例,介绍一下执行过程:
首先通过调用
config_load函数将UCI配置读入当前环境变量中,默认从/etc/config目录下读取配置,并设置到环境变量中。然后使用
config_get等函数进行读取和转换配置。我们以
config_get函数为例来说明执行流程。config_get函数从环境变量中读取配置值并赋值给变量。该函数至少要3个参数。第1个参数为存储返回值的变量。
第2个参数为所要读取的配置节的名称。
第3个参数是所有读取的选项名称。
第4个参数是为默认值,如果配置文件没有该选项则返回该默认值,是一个可选的参数。
uci_和config_函数区别
uci_开头的函数是/lib/config/uci.sh脚本提供的,config_开头得函数是/lib/functions.sh脚本提供的。
相同点
以
uci_开头的函数和以config_开头的函数大多数功能完全相同。不同点
uci_get等函数直接从文件中获取,而config_get函数从环境变量中读取。性能差异:
config_get函数使用config_load一次从配置文件中读取设置到环境变量中,以后均不再进行磁盘操作。而uci_get每次均从文件中读取。如果调用多次,两者性能差距就会显现,实际测试中两者相差10倍以上。因此在OpenWrt中大多使用以
config_开头的config_get等函数进行配置文件转换。
UCI C接口API
uci c接口的api需要依赖Libubox库,Libubox是OpenWrt的一个必备的基础库,包含大小端转换、链表、MD5等实用工具基础库,采用Cmake来编译。库的交叉编译省略,自行查资料。
C接口常用的函数
UCI接口命名非常规范,统一以小写的uci开头并放在
uci.h头文件中。 大多数函数的第一个参数均为uci_context的指针变量。这个变量在程序初始化时调用uci_alloc_context函数分配空间并设置初始值。在程序执行结束时调用uci_free_context函数释放空间。UCI接口有设置函数
uci_set,但没有相应的获取函数uci_get,UCI使用uci_lookup_ptr来提供查询功能,如果查到则通过获取ptr变量的值来获取配置的值。
函 数 |
含 义 |
|---|---|
|
分配UCI上下文环境对象。 |
|
释放UCI上下文环境对象。 |
|
解析UCI配置文件,并存储到UCI对象中。@name:配置文件名,相对于配置目录。@package:在这个变量中存储装载的配置包。 |
|
从UCI上下文环境对象中unload配置文件。 |
|
分割字符串并查找。@ptr:查找的结果。@str:待查找的字符串,但 str 不能为常量, 因为将被修改赋值,在 ptr 变量内部会被使用到,因此 str 的寿命必须至少和 ptr 一样长。@extended 是否允许扩展查找。 |
|
设置元素值,如果必要将创建一个元素。更新或创建的元素将存储在ptr-> last中。 |
|
删除一个元素,配置节或选项。 |
|
保存一个package修改的delta。 |
|
提交更改package,提交将重新加载整个uci_package。 |
|
修改UCI配置文件的存储位置,默认为/etc/config。 |
分割字符串查找
配置文件
/etc/config/hello内如config globals 'globe' option agent 'openwrt' option url 'http://192.168.43.20:8080' option delay '10'
struct Hello { char agent[50]; char url[256]; int delay; }; static int _getValue(struct uci_context *ctx, char *key, char*value, int n) { char strKey[100]; struct uci_ptr ptr; //将hello.globe.agent/url/delay 写入strKey中 snprintf(strKey, sizeof(strKey), "hello.globe.%s",key); //参数1:UCI上下文环境对象 //参数2:查找的结果保存到ptr中 //参数3:待查找的字符串 //参数4:是否允许扩展查找 //从/etc/config/hello配置文件中找(备注:默认从/etc/config目录下读取) //uci_lookup_ptr:分割字符串并查找 if (uci_lookup_ptr(ctx, &ptr, strKey, true) == UCI_OK){ printf("%s\n", ptr.o->v.string); strncpy(value, ptr.o->v.string, n-1); //将查找的字符串保存到value中 } return 0; } int read_conf( struct Hello *hello) { char delay[20]; struct uci_context *ctx = uci_alloc_context(); //分配UCI上下文环境对象 if (!ctx){ fprintf(stderr, "No memory\n"); return 1; } //将配置文件中agent的值读取到hello->agent中 _getValue(ctx, "agent", hello->agent, sizeof(hello->agent)); //将配置文件中url的值读取到hello->url中 _getValue(ctx, "url", hello->url, sizeof(hello->url)); //将配置文件中delay的值读取到hello->delay中 _getValue(ctx, "delay", delay, sizeof(delay)); hello->delay = atoi(delay); //字符串转换为整型 uci_free_context(ctx); //释放UCI上下文环境对象 return 0; }
第二种查找方式
// UCI上下文: struct uci_context * // 包(Package): 一个包对应一个UCI格式的文件.类型是 struct uci_package * // 节(Section): 一个配置文件的节点. 类型是 struct uci_list * // 值(Value):一个节下面可能包含多个值 一个值具有一个名字. #include <unistd.h> #include <stdio.h> #include <string.h> #include <uci.h> // 引入uci的头文件 static struct uci_context * ctx = NULL; //定义一个UCI上下文的静态变量. /********************************************* * 载入配置文件,并遍历Section. */ bool load_config() { struct uci_package * pkg = NULL; struct uci_element *e; ctx = uci_alloc_context(); // 申请一个UCI上下文. if (UCI_OK != uci_load(ctx, UCI_CONFIG_FILE, &pkg)) goto cleanup; //如果打开UCI文件失败,则跳到末尾 清理 UCI 上下文. /*遍历UCI的每一个节*/ uci_foreach_element(&pkg->sections, e) { struct uci_section *s = uci_to_section(e); // 将一个 element 转换为 section类型, 如果节点有名字,则 s->anonymous 为 false. // 此时通过 s->e->name 来获取. // 此时 您可以通过 uci_lookup_option()来获取 当前节下的一个值. if (NULL != (value = uci_lookup_option_string(ctx, s, "ipaddr"))) { //如果您想持有该变量值,一定要拷贝一份。当 pkg销毁后value的内存会被释放。 ip = strdup(value) } // 如果您不确定是 string类型 可以先使用 uci_lookup_option() 函数得到Option 然后再判断. // Option 的类型有 UCI_TYPE_STRING 和 UCI_TYPE_LIST 两种. } uci_unload(ctx, pkg); // 释放 pkg cleanup: uci_free_context(ctx); ctx = NULL; }
遍历一个UCI_TYPE_LIST 类型,假如现在有一个如下的配置文件:
config "server" "webserver" list "index" "index.html" list "index" "index.php" list "index" "default.html"
代码片:
// s 为 section. struct uci_option * o = uci_lookup_option(ctx, s, "index"); if ((NULL != o) && (UCI_TYPE_LIST == o->type)) //o存在 且 类型是 UCI_TYPE_LIST则可以继续. { struct uci_element *e; uci_foreach_element(&o->v.list, e) { //这里会循环遍历 list // e->name 的值依次是 index.html, index.php, default.html } }
写配置 UCI提供了一个简洁的办法来操作配置信息,例如有一个配置文件
#文件名: testconfig config 'servver' option 'value' '123' # 我们想修改 'value' 的值为 '456'
代码如下:
struct uci_context * ctx = uci_alloc_context(); //申请上下文 struct uci_ptr ptr ={ .package = "config", .section = "servver", .option = "value", .value = "256", }; uci_set(_ctx,&ptr); //写入配置 uci_commit(_ctx, &ptr.p, false); //提交保存更改 uci_unload(_ctx,ptr.p); //卸载包 uci_free_context(ctx); //释放上下文
依照上面的例子,我们可以举一反三,
uci_ptr用来指定信息,而是用uci_set则是写入信息.同类的函数有如下几个: 针对list的操作:uci_add_list() // 添加一个list 值 uci_del_list() // 删除一个list 值 uci_delete() // 删除一个option值
UCI Lua接口API
cursor的几种初始化方式
不带参数初始化,默认/etc/config
x = uci.cursor()
包含状态值
x = uci.cursor(nil, "/var/state")
指定配置的目录
x = uci.cursor("/etc/mypackage/config", "/tmp/mypackage/.uci")
Lua常用的uci接口
lua接口
说明
x:get("config", "sectionname", "option")返回字符串 或 nil(没找到数据时)
x:set("config", "sectionname", "option", "value")设置简单的字符串变量
x:set("config", "sectionname", "option", { "foo", "bar" })设置列表变量
x:delete("config", "section", "option")删除选项
x:delete("config", "section")删除段
x:add("config", "type")添加一个匿名section(段)
x:set("config", "name", "type")添加一个类型为 “type”的section(段),名称为”name”
x:foreach("config", "type", function(s) ... end)遍历所有类型为的”type”段,并以每个”s”为参数调用回调函数. s 是一个包含所有选型和两个特有属性的列表
s[‘.type’] 段类型
s[‘.name’]段名称
如果回调函数返回 false [NB: not nil!], foreach()在这个点会终止,不再继续遍历任何剩余的段. 如果至少存在一个段且回调函数没有产生错误,foreach() 会返回 true; 否则返回false.x:revert("config")取消修改的值
x:commit("config")保存修改的值到配置目录文件,默认目录/etc/config
Lua操作uci示例
#!/usr/bin/lua
require("uci")
x = uci.cursor()
function show_test_config()
name = x:get("test", "global", "name")
id = x:get("test", "global", "id")
addr_list = x:get("test", "global", "address")
print("name="..name)
print("id="..id)
for key, value in ipairs(addr_list)
do
print("address["..key.."]----"..value)
end
end
print("==========before change=========")
show_test_config()
x:set("test", "global", "name", "xxxxxxx")
x:set("test", "global", "id", "8888888")
x:set("test","global","address",{"1111","2222","33333","4444","5555"})
x:commit("test")
print("==========after change=========")
show_test_config()
#!/usr/bin/lua
require("uci")
x=uci.cursor()
conf=x:get_all("test", "global")
print(conf["name"]);
print(conf["id"]);
for key, value in ipairs(conf["address"])
do
print(value);
end