# [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>:/ ``` ![image](https://hackmd.io/_uploads/BJdT9a2CC.png) 若沒有指定 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> ``` ![image](https://hackmd.io/_uploads/SyuDxVF0A.png) ## 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` ![image](https://hackmd.io/_uploads/S1bDZb6RR.png) 此時若使用 `sdir list` 可看到此 App 的 user 是 `default`: ![image](https://hackmd.io/_uploads/H13Gf-6CC.png) 若關掉 sandbox 再執行一次 `sdir list` 則可看到 `<root>`: ![image](https://hackmd.io/_uploads/r1_KG-pA0.png) ::: :::info 為什麼是 bind 到 `<root>.le_cfg` 的 `<root>`? 使用 `sdir list` 指令可以查看: 1. 目前系統中的 App 有提供哪些 service 2. 以及目前系統中的哪些 App 有 bind 到哪些 service ![Screenshot from 2024-10-04 14-01-28_compressed](https://hackmd.io/_uploads/H1PlrZTR0.png) 從 `sdir list` 指令可以看到 config tree 提供 service 的 interface 分別是 `<root>.le_cfg` 和 `<root>.le_cfgAdmin`,所以才會這樣設定。 做好 binding 的設定後,在 `sdir list` 的 `BINDINGS` 就可以看到我們的 App 有 bind 到 config tree API 的 interface: ![image](https://hackmd.io/_uploads/ByHmLWpRA.png) ::: ### 注意事項 使用 [`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 ![Screenshot from 2024-10-07 14-17-41_compressed](https://hackmd.io/_uploads/rk5SpxWyJg.png) 從以上截圖可以看到,一開始 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 被觸發了兩次。 ![image](https://hackmd.io/_uploads/SkrC9pe1Jl.png) [官網的說明](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 ![image](https://hackmd.io/_uploads/ryP3YRxJJx.png) 從截圖可以看到,一開始沒有 `myConfigTree.cfg` 這個檔案,且系統目前有 `resources`、`system`、`tafSMSSvc` 這三棵 config tree。 安裝此 App 後就有印出這三棵 config tree 的名字,且也產生了 `myConfigTree.cfg` 這檔案。而 `myConfigTree.cfg` 的內容就是將 `config get /users/` 的內容以類似 JSON 的格式儲存起來。