# [Legato] Config Tree
> App/System Configuration:https://docs.legato.io/latest/conceptsConfig.html
> Config Tree:https://docs.legato.io/latest/legatoServicesConfigTree.html
> Config Tree API:https://docs.legato.io/latest/c_config.html
> `le_cfg` API Reference:https://docs.legato.io/latest/group__le__cfg.html
> Config Tree Admin API:https://docs.legato.io/latest/c_configAdmin.html
> `le_cfgAdmin` API Reference:https://docs.legato.io/latest/group__le__cfgAdmin.html
> `config` Target Tool:https://docs.legato.io/latest/toolsTarget_config.html
> The `configTree` subsection of the `requires` section in `.adef`:https://docs.legato.io/latest/defFilesAdef.html#defFilesAdef_requiresConfigTree
## Intro to Config Tree
Config tree 在 Legato 框架是用來儲存 App/System configuration 的資訊,它是用 non-volatile noSQL database 來儲存的。Config tree 的每個 node 有可能是 stem 或 leaf。Stem 含有 child node,leaf 則會有 value 或者是 empty。
我們在 target machine 可以使用 `config` 這個工具來查看或設定 config tree(詳細說明可參閱官網 https://docs.legato.io/latest/toolsTarget_config.html )。
以下的例子,是在 TelAF simulation 使用 `config` 查看 config tree,其中的 `/`、`users`、`bindings`、`DeviceManagerTool`、`telaf`、`sdirTool` 就是 stem,而 `user`、`interface` 則是 leaf。
```bash
[TelAF Simulation] :~ # config get /
/
users/
root/
bindings/
DeviceManagerTool/
user<string> == root
interface<string> == DeviceManagerTool
telaf/
bindings/
DeviceManager/
user<string> == root
interface<string> == DeviceManager
sdirTool/
user<string> == root
interface<string> == sdirTool
```
Config tree 的 node 的路徑跟 Unix 的 filesystem 的路徑寫法一樣。像是從以上例子,若要查看 `telaf` 這個使用者的 `sdirTool` 的 binding 的 interface,那就用 `config get /users/telaf/bindings/sdirTool/interface` 來查看。
系統內可能有多棵 config tree。在 TelAF simulation,這些 config tree 的檔案會被放在 `/legato/systems/current/config/` 裡面,這些 config tree 的副檔名是 `.rock`、`.paper`、和 `.scissors`。
從以下的截圖可以看到 TelAF simulation 裡面有四棵 config tree:`positioningService.rock`、`resource3s.paper`、`system.paper`、和 `tafSMSSvc.scissors`。
我們可以用以下指令查看指定的 config tree 的內容:
```bash
config get <configTree_name>:/
```

若沒有指定 config tree 的檔名,且 `config` 是執行在 root user,則 `config get /` 預設上會得到的是 `system.paper` 這棵 config tree 的內容,如下例就是在 TelAF simulation 執行 `config get /` 得到的內容:
```bash
[TelAF Simulation] :~ # config get /
/
users/
root/
bindings/
DeviceManagerTool/
user<string> == root
interface<string> == DeviceManagerTool
telaf/
bindings/
DeviceManager/
user<string> == root
interface<string> == DeviceManager
sdirTool/
user<string> == root
interface<string> == sdirTool
apps/
gnssTool/
sandboxed<bool> == false
startManual<bool> == true
maxSecureStorageBytes<int> == 8192
maxThreads<int> == 600
startGroup<int> == 0
maxMQueueBytes<int> == 512
maxQueuedSignals<int> == 100
maxMemoryBytes<int> == 40960000
cpuShare<int> == 1024
requires/
files<empty>
dirs<empty>
devices<empty>
kernelModules<empty>
bundles/
files<empty>
dirs<empty>
procs<empty>
bindings/
LogClient/
user<string> == root
interface<string> == LogClient
gnss.gnss.le_gnss/
app<string> == tafLocationSvc
interface<string> == taf_gnss
gnss.gnss.le_pos/
app<string> == tafLocationSvc
interface<string> == taf_pos
gnss.gnss.le_posCtrl/
app<string> == tafLocationSvc
interface<string> == taf_posCtrl
configLimits/
acl<empty>
tafDataCallSvc/
// 省略
tafKeyStoreSvc/
// 省略
tafLocationSvc/
// 省略
tafLocationUnitTest/
// 省略
tafMngdConnSvc/
// 省略
tafMngdConnUnitTest/
// 省略
tafPMSvc/
// 省略
tafRadioSvc/
// 省略
tafSMSSvc/
// 省略
tafSMSUnitTest/
// 省略
tafSimCardSvc/
// 省略
tafSimUnitTest/
// 省略
tafSomeipClntUnitTest/
// 省略
tafSomeipGWSvc/
// 省略
tafSomeipSvrUnitTest/
// 省略
tafmodule/
// 省略
modules<empty>
framework<empty>
```

## Config Tree API
與 config tree 相關的 API 有以下兩種:
| API Guide | API Reference | File Name | Description |
| -------- | -------- | -------- | --- |
| [Config Tree API](https://docs.legato.io/latest/c_config.html) | [`le_cfg` API Reference](https://docs.legato.io/latest/group__le__cfg.html) | `le_cfg.api` | 主要用來讀寫 config tree 的 node |
| [Config Tree Admin API](https://docs.legato.io/latest/c_configAdmin.html) | [`le_cfgadmin` API Reference](https://docs.legato.io/latest/group__le__cfgAdmin.html) | `le_cfgAdmin.api` | 用來管理 config tree,像是輸入/輸出、刪除 config tree |
### bindings
由於 config tree 是個 core daemon,因此若想使用 config tree 相關的 API,必須要連到 config tree 提供的 API 界面才可以,所以 `.adef` 和 `Component.cdef` 需做以下設定:
#### `Component.cdef`
```cdef
requires:
{
api:
{
le_cfg.api // 使用 Config Tree API
le_cfgAdmin.api // 使用 Config Tree Admin API
}
}
```
#### `.adef`
```adef
sandboxed: false
bindings:
{
execName.compName.le_cfgAdmin -> <root>.le_cfgAdmin
execName.compName.le_cfg -> <root>.le_cfg
}
```
:::info
為什麼要關掉 sandbox 機制?
其實有沒有啟用 sandbox 對於能否順利使用 config tree 相關的 API,目前測試起來並沒有差別,都可以使用 config tree 的 API。只是若啟動 sandbox 的話,會出現以下 warning 的訊息:`Process[1099] is not a TelAF process`

此時若使用 `sdir list` 可看到此 App 的 user 是 `default`:

若關掉 sandbox 再執行一次 `sdir list` 則可看到 `<root>`:

:::
:::info
為什麼是 bind 到 `<root>.le_cfg` 的 `<root>`?
使用 `sdir list` 指令可以查看:
1. 目前系統中的 App 有提供哪些 service
2. 以及目前系統中的哪些 App 有 bind 到哪些 service

從 `sdir list` 指令可以看到 config tree 提供 service 的 interface 分別是 `<root>.le_cfg` 和 `<root>.le_cfgAdmin`,所以才會這樣設定。
做好 binding 的設定後,在 `sdir list` 的 `BINDINGS` 就可以看到我們的 App 有 bind 到 config tree API 的 interface:

:::
### 注意事項
使用 [`le_cfg_CreateReadTxn`](https://docs.legato.io/latest/group__le__cfg.html#gae70663c8b4bd50327bb3771acb694c18)、[`le_cfg_CreateWriteTxn`](https://docs.legato.io/latest/group__le__cfg.html#ga5a263340159319b01bb64e32341a7786) 取得 `le_cfg_IteratorRef_t` 後,記得呼叫 [`le_cfg_CancelTxn`](https://docs.legato.io/latest/group__le__cfg.html#gaf5f10497ed85d2e647b41ca460228483) 或 [`le_cfg_CommitTxn`](https://docs.legato.io/latest/group__le__cfg.html#ga9825af4d2e007d82f4385ee444a348ef) 釋放資源。
同樣地,使用 [`le_cfgAdmin_CreateTreeIterator`](https://docs.legato.io/latest/group__le__cfgAdmin.html#ga3c6027ce6449d80b4136397109f174f8) 取得 `le_cfgAdmin_IteratorRef_t` 後,也要呼叫 [`le_cfgAdmin_ReleaseTreeIterator`](https://docs.legato.io/latest/group__le__cfgAdmin.html#ga9077c6fe37d2bff7414f7a4f49c17158) 釋放資源。
## Example 1
以下簡單的例子示範使用 config tree API 將自己這個 App 的 config tree 內容印出來,並將 `/apps/configTreeApp/maxThreads` 的值改成 4,以及將 `/apps/configTreeApp/procs/maxFileBytes` 的值改成 204800。
### Project Structure
```
~/configTreeApp$ tree
.
├── configTreeApp.adef
└── configTreeComp
├── Component.cdef
└── configTree.c
```
### `configTreeApp.adef`
```adef
sandboxed: false
executables:
{
configTreeExec = ( configTreeComp )
}
bindings:
{
configTreeExec.configTreeComp.le_cfg -> <root>.le_cfg
}
processes:
{
run:
{
configTreeProc = ( configTreeExec )
}
}
```
### `Component.cdef`
```cdef
requires:
{
api:
{
le_cfg.api
}
}
sources:
{
configTree.c
}
```
### `configTree.c`
```cpp
#include "legato.h"
#include "interfaces.h"
#define INDENT_NUM (1)
static char *path = "/apps/configTreeApp";
static le_thread_Ref_t threadRef = NULL;
static le_cfg_ChangeHandlerRef_t handlerRef = NULL;
static le_cfg_IteratorRef_t readIteratorRef = NULL;
static le_cfg_IteratorRef_t writeIteratorRef = NULL;
static le_result_t res;
/*
* 用 DFS 去 travser config tree
*/
void traverseConfigTree(uint8_t indentLevel, le_cfg_IteratorRef_t readIteratorRef)
{
char curPathBuffer[1024] = "";
char curNameBuffer[LE_CFG_NAME_LEN_BYTES] = "";
char leafValueString[1024] = "";
bool leafValueBool = false;
int32_t leafValueInt = 0;
double leafValueDouble = 0.0;
le_result_t hasChild, hasSibling;
le_cfg_IteratorRef_t firstChildIterRef = NULL,
nxtSiblingIterRef = NULL;
le_cfg_GetPath(readIteratorRef, ".", curPathBuffer, sizeof(curPathBuffer));
res = le_cfg_GetNodeName(readIteratorRef, ".", curNameBuffer, LE_CFG_NAME_LEN_BYTES);
char curName[LE_CFG_NAME_LEN_BYTES + (indentLevel << INDENT_NUM)];
uint8_t i = 0;
for (; i < (indentLevel << INDENT_NUM); ++i)
{
curName[i] = '-';
}
uint8_t j = 0;
while (curNameBuffer[j] != '\0')
{
curName[i++] = curNameBuffer[j++];
}
curName[i] = '\0';
le_cfg_nodeType_t nodeType = le_cfg_GetNodeType(readIteratorRef, curPathBuffer);
switch (nodeType)
{
// if this node is a stem, then we traverse its child first
case LE_CFG_TYPE_STEM:
LE_INFO("%s/", curName);
goto GO_TO_FIRST_CHILD;
break;
// below node types are leaf
// after printing their name and value, we iterate their siblings
case LE_CFG_TYPE_EMPTY:
LE_INFO("%s (empty leaf)", curName);
goto GO_TO_SIBLING;
break;
case LE_CFG_TYPE_STRING:
le_cfg_GetString(readIteratorRef, ".", leafValueString, 1024, "default");
LE_INFO("%s: %s", curName, leafValueString);
goto GO_TO_SIBLING;
break;
case LE_CFG_TYPE_BOOL:
leafValueBool = le_cfg_GetBool(readIteratorRef, ".", leafValueBool);
LE_INFO("%s: %s", curName, (leafValueBool == true) ? "TRUE" : "FALSE");
goto GO_TO_SIBLING;
break;
case LE_CFG_TYPE_INT:
leafValueInt = le_cfg_GetInt(readIteratorRef, ".", leafValueInt);
LE_INFO("%s: %d", curName, leafValueInt);
goto GO_TO_SIBLING;
break;
case LE_CFG_TYPE_FLOAT:
leafValueDouble = le_cfg_GetFloat(readIteratorRef, ".", leafValueDouble);
LE_INFO("%s: %f", curName, leafValueDouble);
goto GO_TO_SIBLING;
break;
case LE_CFG_TYPE_DOESNT_EXIST:
LE_INFO("(this node doesn't exist)");
break;
default:
LE_INFO("ERROR - Unknown node type");
break;
}
GO_TO_FIRST_CHILD:
firstChildIterRef = le_cfg_CreateReadTxn(curPathBuffer);
hasChild = le_cfg_GoToFirstChild(firstChildIterRef);
if (hasChild == LE_OK)
{
traverseConfigTree(indentLevel + 1, firstChildIterRef);
}
le_cfg_CancelTxn(firstChildIterRef);
if (indentLevel == 0)
{
return ;
}
GO_TO_SIBLING:
nxtSiblingIterRef = le_cfg_CreateReadTxn(curPathBuffer);
hasSibling = le_cfg_GoToNextSibling(nxtSiblingIterRef);
if (hasSibling == LE_OK)
{
traverseConfigTree(indentLevel, nxtSiblingIterRef);
}
le_cfg_CancelTxn(nxtSiblingIterRef);
}
// typedef void(* le_cfg_ChangeHandlerFunc_t) (void *contextPtr)
void configTreeChangeHandler(void *contextPtr)
{
LE_INFO("%s", (char *) contextPtr);
}
void writeMaxFileBytes(void *param1Ptr, void *param2Ptr)
{
writeIteratorRef = le_cfg_CreateWriteTxn(path);
le_cfg_SetInt(writeIteratorRef, "procs/configTreeProc/maxFileBytes", 204800);
le_cfg_CommitTxn(writeIteratorRef);
}
void writeMaxThreads(void *param1Ptr, void *param2Ptr)
{
le_cfg_IteratorRef_t writeIteratorRef = (le_cfg_IteratorRef_t) param1Ptr;
int32_t *newValue = (int32_t *) param2Ptr;
le_cfg_SetInt(writeIteratorRef, "maxThreads", *newValue);
le_cfg_CommitTxn(writeIteratorRef);
}
void deferredFunc(void *param1Ptr, void *param2Ptr)
{
char *handlerContext = "Inside config tree change handler function";
handlerRef = le_cfg_AddChangeHandler(path, configTreeChangeHandler, handlerContext);
readIteratorRef = le_cfg_CreateReadTxn(path);
traverseConfigTree(0, readIteratorRef);
le_cfg_CancelTxn(readIteratorRef);
LE_INFO("Canceled the read iterator reference");
}
void *threadMainFunc(void *context)
{
le_cfg_ConnectService();
le_event_QueueFunction(deferredFunc, NULL, NULL);
writeIteratorRef = le_cfg_CreateWriteTxn(path);
int32_t newValue = 4;
le_event_QueueFunction(writeMaxThreads, (void *) writeIteratorRef, (void *) &newValue);
le_event_QueueFunction(writeMaxFileBytes, NULL, NULL);
le_event_RunLoop();
return NULL;
}
COMPONENT_INIT
{
threadRef = le_thread_Create("MyThread", threadMainFunc, NULL);
le_thread_Start(threadRef);
}
```
### Result

從以上截圖可以看到,一開始 traverse config tree 印出來的 `maxThreads` 的值是 20,`maxFileBytes` 的值是 102400。但之後用 `config get /apps/configTreeApp/maxThreads` 和 `config get /apps/configTreeApp/procs/configTreeProc/maxFileBytes` 得到的值是 4 和 204800,所以有順利修改 config tree 的值。
:::warning
以上截圖可以看到 config tree change handler function 被觸發了兩次。

[官網的說明](https://docs.legato.io/latest/group__le__cfg.html#gaf84bb98f1aee5d47692a0b776c89729b) 說 `read` 也會觸發 handler function,但前面的例子,如果 `read` 也會觸發 handler function 的話,應該總共會執行超過兩次 handler function。我自己另外測試若只有 read 的話並不會觸發 handler function,一定要有 `write` 才會觸發。
:::
## Example 2
以下例子是修改自[官網的 sample code](https://docs.legato.io/latest/c_configAdmin.html)。此例子會先列出系統目前有哪些 config tree,之後再將 `system` tree 的 `users` 這個 node 的內容輸出至 `myConfigTree.cfg`。
### Project Structure
```
cpt1020@Ubuntu1804:~/configTreeAdminApp$ tree
.
├── configTreeAdminApp.adef
└── configTreeAdminComp
├── Component.cdef
└── configTree.c
```
### `configTreeAdminApp.adef`
```adef
sandboxed: false
executables:
{
configTreeAdminExec = ( configTreeAdminComp )
}
bindings:
{
configTreeAdminExec.configTreeAdminComp.le_cfgAdmin -> <root>.le_cfgAdmin
configTreeAdminExec.configTreeAdminComp.le_cfg -> <root>.le_cfg
}
processes:
{
run:
{
configTreeAdminProc = ( configTreeAdminExec )
}
}
```
### `Component.cdef`
```cdef
requires:
{
api:
{
le_cfgAdmin.api
le_cfg.api
}
}
sources:
{
configTree.c
}
```
### `configTree.c`
```cpp
// 改編自官網的 sample code:https://docs.legato.io/latest/c_configAdmin.html
#include "legato.h"
#include "interfaces.h"
#define MAX_TREE_NAME_BYTES (1024)
void ListTrees(void)
{
// 建立一個 le_cfgAdmin 的 tree iterator
le_cfgAdmin_IteratorRef_t iteratorRef = le_cfgAdmin_CreateTreeIterator();
LE_INFO("Listing configuration Trees in the current system...");
// le_cfgAdmin iterator 從 item -1 開始,所以記得要先呼叫 le_cfgAdmin_NextTree
while (le_cfgAdmin_NextTree(iteratorRef) == LE_OK)
{
char treeName[MAX_TREE_NAME_BYTES] = "";
if (le_cfgAdmin_GetTreeName(iteratorRef, treeName, sizeof(treeName)) == LE_OK)
{
LE_INFO("Tree: '%s'", treeName);
}
}
// 最後記得要 release tree iterator
le_cfgAdmin_ReleaseTreeIterator(iteratorRef);
}
void ExportMyData(const char* filePath)
{
le_cfg_IteratorRef_t iteratorRef = le_cfg_CreateReadTxn("/users");
// Our iterator is currently on /myData, so everything under that node is exported. If we
// want to export the whole tree we could supply a "/" here instead of using the iterator's
// current location. Alternativly, we could have opened or moved the iterator to "/" in the
// first place.
LE_FATAL_IF(le_cfgAdmin_ExportTree(iteratorRef, filePath, "") != LE_OK,
"Error occured while writing config data.");
// Close up the iterator and free it's resources.
le_cfg_CancelTxn(iteratorRef);
}
COMPONENT_INIT
{
ListTrees();
// 路徑必須是絕對路徑
// 若該檔案原本不存在沒關係,會直接產生新的檔案
ExportMyData("/root/simulation/myConfigTree.cfg");
}
```
### Result

從截圖可以看到,一開始沒有 `myConfigTree.cfg` 這個檔案,且系統目前有 `resources`、`system`、`tafSMSSvc` 這三棵 config tree。
安裝此 App 後就有印出這三棵 config tree 的名字,且也產生了 `myConfigTree.cfg` 這檔案。而 `myConfigTree.cfg` 的內容就是將 `config get /users/` 的內容以類似 JSON 的格式儲存起來。