Configuration Tree API

Click here for the API reference documentation.


A Transactional Approach
Iterating the Tree
Writing Configuration Data, using a write transaction.
Reading configuration data with a read transaction.
Working without Transactions

The configuration tree API allows applications to read and write their specific configuration. Each application is given an isolated tree. The system utilities store their configuration in the "root" tree.

Paths in the tree look like traditional Unix style paths and take the form of:

 /path/to/my/value 

The path root is the root of the tree where the application has been given access. If the application has permission to access another tree, the path can also include the name of the other tree, followed by a colon.

 secondTree:/path/to/my/value 

In this case, a value named "value" is read from the tree named, "secondTree."

The tree is broken down into stems and leaves. A stem is a node that has at least one child node. While a leaf has no children, but may hold a value.

The configuration tree supports string, signed integer, boolean, floating point, and empty values. Storing anything more complex is encouraged to use stems and leafs to enhance readablity and debug-ablity. This also sidesteps nasty cross platform alignment issues.

TODO: Talk about the treeConfig, user limits, tree name, tree access. Global timeouts.

A Transactional Approach

The configuration tree makes use of simple transactions for working with its data. Both read and write transactions are supported. You want to use read transactions when to ensure you can atomically read multiple values from your configuration while keeping consistency with third parties trying to write data.

To prevent any single client from locking out other clients, read and write transactions have their own configurable timeout.

During a write transaction, both reading and writing are allowed. If you write a value during a transaction and read from that value again, you will get the same value you wrote. Third party clients will continue to see the old value. It's not until you commit your transaction that third parties will begin to see your updated value.

During read transactions, writes are not permitted and are thrown away.

Transactions are started by creating an iterator. Either a read or write iterator can be created. To end the transaction, you can either delete the iterator, cancelling the transaction. Or, in the case of write transactions, you can commit the iterator.

You can have multiple read transactions against the tree. They won't block other transactions from being creating. A read transaction won't block creating a write transaction either. A read transaction only blocks a write transaction from being comitted to the tree.

A write transaction in progress will also block creating another write transaction. If a write transaction is in progress when the request for another write transaction comes in, the secondary request will be blocked. This secondary request will remain blocked until the first transaction has been comitted or has timed out.

Iterating the Tree

This code example will iterate a given node and print it's contents:

  static void PrintNode(le_cfg_IteratorRef_t iteratorRef)
  {
      do
      {
          char stringBuffer[MAX_CFG_STRING] = { 0 };

          le_cfg_GetNodeName(iteratorRef, &stringBuffer, sizeof(stringBuffer));

          switch (le_cfg_GetNodeType(iteratorRef))
          {
              case LE_CFG_TYPE_STEM:
                  {
                      printf("%s/\n", stringBuffer);

                      if (le_cfg_GoToFirstChild(iteratorRef) == LE_OK)
                      {
                          PrintNode(iteratorRef);
                          le_cfg_GoToNode(iteratorRef, "..");
                      }
                  }
                  break;

              case LE_CFG_TYPE_EMPTY:
                  printf("%s = *empty*\n", stringBuffer);
                  break;

              case LE_CFG_TYPE_BOOL:
                  {
                      bool value = false;

                      le_cfg_GetBool(iteratorRef, stringBuffer, &value, false);
                      printf("%s = %s\n", stringBuffer, (value ? "true" : "false"));
                  }
                  break;

              case LE_CFG_TYPE_INT:
                  {
                      int32_t intValue = 0;

                      le_cfg_GetInt(iteratorRef, stringBuffer, &intValue, 0);
                      printf("%s = %d\n", stringBuffer, intValue);
                  }
                  break;

              case LE_CFG_TYPE_FLOAT:
                  {
                      double floatValue = 0.0;

                      le_cfg_GetFloat(iteratorRef, stringBuffer, &floatValue, 0.0);
                      printf("%s = %f\n", stringBuffer, floatValue);
                  }
                  break;

              case LE_CFG_TYPE_STRING:
                  printf("%s = ", stringBuffer);
                  le_cfg_GetString(iteratorRef, stringBuffer, stringBuffer, "");
                  printf("%s\n", stringBuffer);
                  break;

              case LE_CFG_TYPE_DOESNT_EXIST:
                  printf("%s = ** DENIED **\n", stringBuffer);
                  break;
          }
      }
      while (le_cfg_GoToNextSibling(iteratorRef) == LE_OK);
  }


  le_cfg_IteratorRef_t iteratorRef = le_cfg_CreateReadIterator("/path/to/my/location");

  PrintNode(iteratorRef);
  le_cfg_CancelTxn(iteratorRef);

Writing Configuration Data, using a write transaction.

Consider the following example, the caller wants to update the devices IP address. It does so in a transaction so that the data is written atomiclly.

  static le_result_t SetIp4Static
  (
      le_cfg_IteratorRef_t currentIterRef,
      const char* interfaceNamePtr,
      const char* ipAddrPtr,
      const char* netMaskPtr
  )
  {
      le_result_t result;

      LE_ASSERT(le_cfg_IsWriteable(currentIterRef));

      // Change current tree position to the base ip4 node.
      char nameBuffer[MAX_CFG_STRING] = { 0 };

      sprintf(nameBuffer, "/system/%s/ip4", interfaceNamePtr);

      le_cfg_GoToNode(iteratorRef, nameBuffer);

      le_cfg_SetString(iteratorRef, "addr", ipAddrPtr);
      le_cfg_SetString(iteratorRef, “mask”, netMaskPtr);

      result = le_cfg_CommitTxn(iteratorRef);

      if (result != LE_OK)
      {
          LE_CRIT("Failed to write IPv4 configuration for interface '%s'.  Error %s.",
                  interfaceNamePtr,
                  LE_RESULT_TXT(result));
      }

      return result;
  }

Reading configuration data with a read transaction.

  static le_result_t GetIp4Static
  (
      le_cfg_IteratorRef_t currentIterRef,
      const char* interfaceNamePtr,
      char* ipAddrPtr,
      char* netMaskPtr
  )
  {
      // Change current tree position to the base ip4 node.
      char nameBuffer[MAX_CFG_STRING] = { 0 };

      sprintf(nameBuffer, "/system/%s/ip4", interfaceNamePtr);
      le_cfg_GoToNode(iteratorRef, nameBuffer);
      if (le_cfg_NodeExists(iteratorRef, "") == false)
      {
          LE_WARN("Configuration not found.");
          return LE_NOTFOUND;
      }

      le_cfg_GetString(iteratorRef, "addr", ipAddrPtr, "");
      le_cfg_GetString(iteratorRef, "mask", netMaskPtr, "");

      return result;
  }

Working without Transactions

It's possible to ignore iterators and transactions entirely (e.g., if all you need to do is read or write some simple values in the tree).

The non-transactional reads and writes work almost identically to the transactional versions. They just don't explictly 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 these functions don't take an explicit transaction, they can't work with relative paths. If a relative path is given, the path will be considered relative to the tree's root.

Translating the last examples to their "quick" counterparts, you have the following code. Because each read is independant, there is 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.

  static void ClearIpInfo
  (
      const char* interfaceNamePtr
  )
  {
      char pathBuffer[MAX_CFG_STRING] = { 0 };

      sprintf(pathBuffer, "/system/%s/ip4/", interfaceNamePtr);
      le_cfg_QuickDeleteNode(pathBuffer);
  }

Copyright (C) Sierra Wireless, Inc. 2014. All rights reserved. Use of this work is subject to license.

 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Defines