Config Tree API

API Reference

The Config Tree is a non-volatile noSQL database that stores configuration values for apps. By default each app is given read access to a single tree that has the same name as the app. Any other access permissions (read access for any other tree, or write access to any tree at all) must be explicitly granted in the app's .adef file.

Trees are created automatically the first time that they are accessed by an app or a component. The apps tree will be named the same as the name of the app. It is possible for apps to be given access to other apps tree's or for multiple apps to share one tree.

Note
If your app is running as root then the configuration will get added to the System Tree by default. System utilities also use the Config Tree and store their configurations in the system tree.

Apps are able to search over the tree for data, although it's generally expected that the location will be known to the app and that the app will point to the right node to retrieve the value.

More on the Config Strategy for the Legato AF.

Overview

The tree is broken down into stems and leaves.

A stem is a node that has at least one child node. A leaf has no children and holds a value.

+tree
|
+--stem
| |
| +--leaf (value)
|
+-stem
|
+--leaf (value)
|
+--leaf (value)

Paths in the tree are traditional Unix style paths and each node is separated by a /:

/stem/stem/leaf

If no app is specified as the root of the tree, then the app will search in it's own tree. To get to another tree reference that tree followed by a colon.

/path/to/my/tree/value                   # references the default apps tree
secondTree:/path/to/secondTree/value    # references the second tree
Note
It's recommended to store anything complex using stems and leaves, this enhances readability and debugging. It also helps to sidestep nasty cross platform alignment issues.

Apps must explicitly give permissions to other apps before they can access their Config Tree data. This is done in the .adef file. Each transaction is only able to iterate over one tree at a time, each tree that you want to read or write to must be created as a separate transaction.

The Config Tree supports storing the following types of data and each has their own get/set function as well as a quick set/get function (see Quick Read/Writes):

  • string
  • binary (array of bytes)
  • signed integer
  • boolean
  • 64bit floating point
  • empty values.

Each transaction has a global timeout setting (default is 30s). The configuration is located in the System Tree and may be configured with the config target tool.

config /configTree/transactionTimeout 10 int         #changes the timeout to 10s

Transactions

Key Transaction Concepts

  • All transactions are sent to a queue and processed in a sequence.
  • Only one write transaction may be active at a time and subsequent writes are queued until the first is finished processing.
  • Transactions may contain multiple read or write requests within a single transaction.
  • Multiple read transactions may be processed while a write transaction is active.
  • Quick(implicit) read/writes can be created and are also sequentially queued.

Create Transactions

To make a change to the tree, you must Create a write transaction, call one or more Set functions, and Commit the transaction. If a write transaction is canceled instead of committed, then the changes will be discarded and the tree will remain unchanged.

To read from a tree without making any changes, you should:

  • create a read transaction,
  • call the Get functions,
  • cancel the transaction when finished.

You could also:

  • create a write transaction,
  • perform only Get operations,
  • cancel the transaction
Note
It's safer to use a read transaction when there is no intention to change the tree.

Transactions must not be kept open for extended periods of time. If a transaction is kept open for longer than the transaction time limit (default is 30 seconds), then the Config Tree will cancel the transaction and drop the connection to the offending client (most likely causing the client process to terminate).

Function Action
le_cfg_CreateReadTxn() Opens the transaction
le_cfg_CancelTxn() Closes a read/write transaction and does not write it to the tree
le_cfg_CommitTxn() Closes a write transaction and queues it for commit

Navigating the Tree

To move around within the Tree you can move directly to a specific node(leaf or stem) and then do your read or write from that point. Functions have been added to easily navigate through Tree. All nodes can be referenced either by their absolute or relative paths.

Function Action
le_cfg_GoToNode() Moves to the location specified
le_cfg_GoToParent() Moves to the parent of the current node (moves up the Tree)
le_cfg_GoToFirstChild() Moves to the first node from the current location (moves down the Tree)
le_cfg_GoToNextSibling() Moves to the next node on the same level as the current node (moves laterally)

Retrieving Node Information

The Config tree also contains functions to help you identify your current location in the tree, what node you are currently pointing at, and what type of data is contained in the current node.

Function Action
le_cfg_GetPath() Gets the location of where you are in the Tree
le_cfg_GetNodeType() Gets the data type of the node where you are currently located
le_cfg_GetNodeName() Gets the name of the node where you are in the Tree (does not include the path)

Read Transactions

Each data type has it's own get function to read a value from a node within the Tree.

Function Action
le_cfg_GetString() Reads the string's value
le_cfg_GetBinary() Reads the array of bytes
le_cfg_GetInt() Reads the integer's value
le_cfg_GetFloat() Reads the floating point value
le_cfg_GetBool() Reads the boolean value

To perform a read from a Tree, we need to open a transaction, move to the node that you want to read from, read the node and then cancel the transaction.

Sample read transaction (with error checking):

le_result_t GetIp4Static //reads the IP address values from the Config Tree
(
const char* interfaceNamePtr,
char* ipAddrPtr,
size_t ipAddrSize,
char* netMaskPtr,
size_t netMaskSize
)
{
// Change current tree position to the base ip4 node.
char nameBuffer[LE_CFG_STR_LEN_BYTES] = { 0 };
 
// Returns errors for out of bounds exceptions
int r = snprintf(nameBuffer, sizeof(nameBuffer), "/system/%s/ip4", interfaceNamePtr);
if (r < 0)
{
return LE_FAULT;
}
else if (r >= sizeof(nameBuffer))
{
return LE_OVERFLOW;
}
 
// Open up a read transaction on the Config Tree.
le_cfg_IteratorRef_t iteratorRef = le_cfg_CreateReadTxn(nameBuffer);
 
if (le_cfg_NodeExists(iteratorRef, "") == false)
{
LE_WARN("Configuration not found.");
le_cfg_CancelTxn(iteratorRef);
return LE_NOT_FOUND;
}
 
// Returns the IP Address value stored in the Config Tree.
le_result_t result = le_cfg_GetString(iteratorRef, "addr", ipAddrPtr, ipAddrSize, "");
if (result != LE_OK)
{
le_cfg_CancelTxn(iteratorRef);
return result;
}
 
// Returns the NetMask value stored in the Config Tree.
result = le_cfg_GetString(iteratorRef, "mask", netMaskPtr, netMaskSize, "");
if (result != LE_OK)
{
le_cfg_CancelTxn(iteratorRef);
return result;
}
 
// Close the transaction and return success.
le_cfg_CancelTxn(iteratorRef);
 
return LE_OK;
}
Note
Any writes done will be discarded at the end of the read transaction.

Write Transactions

Each data type has it's own set function, to write a value to a node within the Tree. Before you are able to write to a tree, permissions must be set in the apps .adef's requires section or with the config tool.

Function Action
le_cfg_SetString() Writes the string's value
le_cfg_SetBinary() Writes the array of bytes
le_cfg_SetInt() Writes the integer's value
le_cfg_SetFloat() Writes the floating point value
le_cfg_SetBool() Writes the boolean value

To perform a write to a Tree, we need to open a transaction, move to the node that you want to write to, write to the node and then commit the transaction.

Sample write transaction (with error checking):

// Store IPv4 address/mask in a string representation
void SetIp4Static
(
const char* interfaceNamePtr,
const char* ipAddrPtr,
const char* netMaskPtr
)
{
// Change current tree position to the base ip4 node.
char nameBuffer[LE_CFG_STR_LEN_BYTES] = { 0 };
 
int r = snprintf(nameBuffer, sizeof(nameBuffer), "/system/%s/ip4", interfaceNamePtr);
LE_ASSERT((r >= 0) && (r < sizeof(nameBuffer));
 
// Create a write transaction so that we can update the tree.
le_cfg_IteratorRef_t iteratorRef = le_cfg_CreateWriteTxn(nameBuffer);
 
le_cfg_SetString(iteratorRef, "addr", ipAddrPtr);
le_cfg_SetString(iteratorRef, "mask", netMaskPtr);
 
// Commit the transaction to make sure these new settings get written to the tree.
le_cfg_CommitTxn(iteratorRef);
}
 
// Store IPv4 address/mask in a binary representation (array of 4 bytes)
void SetIp4StaticAsBinary
(
const char* interfaceNamePtr,
const uint8_t* ipAddrPtr,
const uint8_t* netMaskPtr
)
{
// Change current tree position to the base ip4 node.
char nameBuffer[LE_CFG_STR_LEN_BYTES] = { 0 };
 
int r = snprintf(nameBuffer, sizeof(nameBuffer), "/system/%s/ip4", interfaceNamePtr);
LE_ASSERT((r >= 0) && (r < sizeof(nameBuffer));
 
// Create a write transaction so that we can update the tree.
le_cfg_IteratorRef_t iteratorRef = le_cfg_CreateWriteTxn(nameBuffer);
 
le_cfg_SetBinary(iteratorRef, "addr", ipAddrPtr, 4);
le_cfg_SetBinary(iteratorRef, "mask", netMaskPtr, 4);
 
// Commit the transaction to make sure these new settings get written to the tree.
le_cfg_CommitTxn(iteratorRef);
}
Note
Creating write transactions creates a temporary working copy of the tree for use within the transaction. All read transactions running in the meantime see the committed state, without any of the changes that have been made within the write transaction.

Deleting a Node

You can also delete a node from the tree. A word of caution as deleting a node will automatically delete all children nodes as well.

Function Action
le_cfg_DeleteNode() Deletes the node and all children

Quick Read/Writes

Another option is to perform quick read/write which implicitly wraps functions with in an internal transaction. This is ideal if all you need to do is read or write some simple values to the default app tree.

The quick reads and writes work almost identically to the transactional version except quick reads don't explicitly take an iterator object. The quick functions internally use an implicit transaction. This implicit transaction wraps one get or set, and does not protect your code from other activity in the system.

Because quick read/write functions don't get created within a transaction, there is no option to traverse to a specific node. All values that are read or written must be referenced from the root of the tree.

Example of a quick read of the binary data:

#define IPV4_ADDR_LEN 4
le_result_t QuickReadIpAddressAsBinary
(
const char* interfaceNamePtr,
const uint8_t* ipAddrPtr,
const uint8_t* netMaskPtr
)
{
char pathBuffer[MAX_CFG_STRING] = { 0 };
uint8_t defaultAddr[IPV4_ADDR_LEN] = { 0 };
le_result_t result = LE_OK;
size_t len = IPV4_ADDR_LEN;
 
// read the address
snprintf(pathBuffer, sizeof(pathBuffer), "/system/%s/ip4/addr", interfaceNamePtr);
result = le_cfg_QuickGetBinary(pathBuffer, ipAddrPtr, &len, defaultAddr, IPV4_ADDR_LEN);
if (LE_OK != result)
{
return result;
}
 
// read the mask
snprintf(pathBuffer, sizeof(pathBuffer), "/system/%s/ip4/mask", interfaceNamePtr);
len = IPV4_ADDR_LEN;
result = le_cfg_QuickGetBinary(pathBuffer, netMaskPtr, &len, defaultAddr, IPV4_ADDR_LEN);
if (LE_OK != result)
{
return result;
}
}

A quick delete example:

void ClearIpInfo
(
const char* interfaceNamePtr
)
{
char pathBuffer[MAX_CFG_STRING] = { 0 };
 
// Removes the node from the tree.
snprintf(pathBuffer, sizeof(pathBuffer), "/system/%s/ip4/", interfaceNamePtr);
}
Warning
Because each quick function is independent, there's no guarantee of consistency between them. If another process changes one of the values while you read/write the other, the two values could be read out of sync.