Our latest project delivers a new and robust UCI configuration library for OpenWrt.
The Unified Configuration Interface (UCI) is the primary configuration system for OpenWrt services. UCI defines a standardized configuration format while also centralizing configuration files for crucial system settings such as wireless, network, ﬁrewall, logging, and many more. OpenWrt stores all UCI configuration files inside the /etc/config/ directory, and users can edit them using a text editor or via the uci command line utility. All UCI configuration files are also accessible to programs through a shared C library (libuci.so) licensed under the LGPL-2.1 license.
Our long-term experience in delivering OpenWrt-based products to the market revealed several shortfalls of the legacy implementation of the UCI standard (also named UCI). In this post, we take a look at those shortfalls and how we resolved them through the UCI2 project.
Configuration File Syntax
Generally, UCI configuration files consist of one or more config statements with sections that contain option statements defining the actual values. Figure 1 shows an example of a UCI section.
Figure 1. Example UCI section
As evident from Figure 1, UCI configuration files are flat files consisting of simple line-based structures grouped by using keywords. Since these structures are not relational or hierarchical, UCI configuration files can only be read from and written linearly and sequentially.
UCI2, on the other hand, converts this flat file viewpoint to a tree-based structure known as Abstract Syntax Tree (AST). AST is an abstract tree representation of the syntactic structure of a language construct. The representation is 'abstract' because it only shows structural or content-related details and excludes syntax details such as rule nodes and parentheses. AST is widely used in compilers to represent the program code's structure.
Let's take a look at an example UCI configuration file /etc/config/system:
option hostname 'OpenWrt'
option timezone 'UTC'
option ttylogin '0'
option log_size '64'
option urandom_seed '0'
config timeserver 'ntp'
option enabled '1'
option enable_server '0'
list server '0.openwrt.pool.ntp.org'
list server '1.openwrt.pool.ntp.org'
list server '2.openwrt.pool.ntp.org'
list server '3.openwrt.pool.ntp.org'
UCI2 converts this format to an AST, after which it looks like this:
[O] hostname = OpenWrt
[O] timezone = UTC
[O] ttylogin = 0
[O] log_size = 64
[O] urandom_seed = 0
[O] enabled = 1
[O] enable_server = 0
Every UCI2 node type correlates to a particular UCI configuration file construct. For more information on AST nodes, consult the UCI2 project's README.
Once a configuration file is read from the filesystem, UCI2 parses it and converts it to AST parser context. The AST uses a reference counting system to keep track of all nodes and frees all nodes comprising the configuration file once the node-based structure is no longer needed. The context-based parsing present in UCI2 is a considerable improvement over UCI and its reliance on global variables.
Another vital design element for UCI2 was to be thread-safe by enabling reentrancy. As a result, OpenWrt users can finally work on multiple files simultaneously.
Unlike UCI, which uses a non-standard, manually-written parser, UCI2 leverages widely-used, industry-standard technologies such as Bison and Flex to perform configuration file parsing. This approach improves the overall robustness of the parser, and our fuzz testing procedures found considerably fewer bugs in UCI2 than in the legacy UCI code base.
UCI2 also provides improvements regarding the licensing model. The UCI2 source code is licensed under a BSD-3-Clause license to effectively deliver a more permissive environment for managing the UCI file format while also driving commercial and corporate adoption.