Config Tree API
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 exceptionsint 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);{LE_WARN("Configuration not found.");le_cfg_CancelTxn(iteratorRef);return LE_NOT_FOUND;}// Returns the IP Address value stored in the Config Tree.if (result != LE_OK){le_cfg_CancelTxn(iteratorRef);return result;}// Returns the NetMask value stored in the Config Tree.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 representationvoid 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 4le_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 addresssnprintf(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 masksnprintf(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);le_cfg_QuickDeleteNode(pathBuffer);}
- 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.
Copyright (C) Sierra Wireless Inc.