Provided by: elektra-doc_0.8.14-5.1ubuntu2_all bug

NAME

       elektra-architecture - architecture of elektra

       In this document we start to explain the implementation of Elektra. There are several follow-up documents
       which explain all details of:

       •   error handling elektra-error-handling.md,

       •   data structures elektra-data-structures.md, and

       •   finally the core algorithm elektra-algorithm.md.

       We  discuss  problems  and  the  solution  space  so  that the reader can understand the rationale of how
       problems were solved.

       To help readers to understand the algorithm that glues together  the  plugins,  we  first  describe  some
       details   of   the   data   structures   elektra-data-structures.md.  Full  knowledge  of  the  algorithm
       elektra-algorithm.md is not presumed to be able to develop  most  plugins  (with  the  exception  of  the
       resolver /src/plugins/resolver/).

       Further important concepts are explained in:

       •   bootstrapping elektra-bootstrapping.md

       •   granularity elektra-granularity.md

       •   sync-flag elektra-sync-flag.md

API

       The  aim  of  the  Elektra  Project is to design and implement a powerful API for configuration. When the
       project started, we assumed that this goal was easy to achieve, but dealing with the semantics turned out
       to be a difficult problem. For the implementation, an ambitious  solution  is  required  because  of  the
       necessary  modularity  to  implement flexible backends as introduced in Elektra. But also the design of a
       good API has proved to be much more difficult than expected.

   Changes in the APIs
       From Elektra 0.7 to Elektra 0.8, we changed the API of Elektra  as  little  as  possible.  It  should  be
       mentioned  that  KeySet  is  now  always sorted by name. The function ksSort() is now depreciated and was
       removed. The handling of removed keys was modified. Additionally, the API for metadata has  fundamentally
       changed,  but  the  old  interface still works. These changes will be described in implementation of meta
       data elektra-meta-data.md. However, the implementation of  Elektra  changed  radically  as  discussed  in
       algorithm elektra-algorithm.md.

   API Design
       API Design presents a critical craft every programmer should be aware of. We will shortly present some of
       the main design issues that matter and show how Elektra has solved them.

       A  design  goal  is  to detect errors early. As easy as it sounds, as difficult it is to actually achieve
       this goal. Elektra tries to avoid the problem by checking  data  being  inserted  into  Key  and  KeySet.
       Elektra  catches  many  errors  like  invalid  key  names  soon.  Elektra  allows  plugins  to  check the
       configuration before it is written into the key database so that problematic values are never stored.

       ´´Hard to use it wrong´´ tends to be a more important design objective than ´´Easy  to  use  it  right´´.
       Searching  for  a stupid bug costs more time than falling into some standard traps which are explained in
       documentation. In Elektra, the data structures are robust and some efforts  were  taken  to  make  misuse
       unlikely.

       Another  fundamental  principle  is  that  the  API  must  hide  implementation details and should not be
       optimised towards speed. In Elektra, the actual process of making configuration permanent  is  completely
       hidden.

       ´´Off-by-one  confusion´´  is a topic of its own. The best is to stick to the conventions the programming
       language gives. For returning sizes of strings, it must be clear whether a terminating ´\0´  is  included
       or  not. All such decisions must be consistent. In Elektra the terminating null is always included in the
       size.

       The interface must be as small as possible to tackle problems addressed  by  the  library.  Internal  and
       external  APIs  must  be separated. Internal APIs in libraries shall be declared as static to prevent its
       export. In Elektra, internal names start with elektra opposed to the external names starting with key, ks
       or kdb.

       Elektra always passes user context pointers, but never passes or receives a full data structure by value.
       It is impossible to be ABI\footnote{We will read more about ABI in \secref{ABI}.}  compatible  otherwise.
       Elektra  is  restrictive  in what it returns (strong postconditions), but as liberal as possible for what
       comes in (preconditions are avoided where possible). In Elektra even null pointers are accepted  for  any
       argument.

       ´´Free  everything  you  allocate´´  is  a difficult topic in some cases. If Elektra cannot free space or
       other resources after every call, it provides a close() function. Everything  will  be  freed.  The  tool
       Valgrind  with  Memcheck helps us locate problems. The whole test suite runs without any memory problems.
       The user is responsible for deleting all created Key and KeySet objects and closing the KDB handle.

       As a final statement, we note that the UNIX philosophy should always be considered: ´´Do only one  thing,
       but do it in the best way. Write it that way that programs work together well.´´

Modules

       Elektra´s core can be compiled with a C compiler conforming to the ISO/IEC 9899:1999 standard:

       •   One line comments,

       •   inline functions,

       •   snprintf()

       •   inttypes.h and

       •   variable declaration at any place

       are  used  in  addition  to  what is already defined in the standard ISO/IEC 9899:1990, called C99 in the
       following text. Functions not conforming to C99 are considered to be not portable enough for Elektra  and
       are  separated  into  plugins.  But  there  is  one notable exception: it must be the core´s task to load
       plugins. Unfortunately, C99 does not know  anything  about  modules.  POSIX  (Portable  Operating  System
       Interface)  provides  dlopen(),  but  other operating systems have dissimilar APIs for that purpose. They
       sometimes behave differently, use other names for the libraries and  have  incompatible  error  reporting
       systems.  Because  of  these  requirements  Elektra  provides  a  small internal API to load such modules
       independently from the operating system. This API also  hides  the  fact  that  modules  must  be  loaded
       dynamically if they are not available statically.

       Plugins  are  usually  realised  with  modules.  Modules  and  libraries are technically the same in most
       systems. (One exception is Mac OS X.) After the module is loaded, the special function plugin factory  is
       searched for. This function returns a new plugin. With the plugin factory the actual plugins are created.

   Static loading
       For  the  static  loading  of  modules, the modules must be built-in. With dlopen(const char* file) POSIX
       provides a solution to look up such symbols by passing a null pointer for the parameter  file.  Non-POSIX
       operating  systems  may  not  support  this  kind  of  static  loading. Therefore, Elektra provides a C99
       conforming solution for that problem: a data structure stores the pointers to the plugin factory of every
       plugin. The build system generates the source file of this data structure because it depends on  built-in
       plugins as shown in Figure~\ref{fig:architecture}.

       Elektra  distinguishes  internally  between  modules and plugins. Several plugins can be created out of a
       single module. During the creation process of the plugin, dynamic information -- like  the  configuration
       or the data handle -- is added.

   API
       The API of \intro[libloader@\lstinline{libloader}]{libloader} consists of the following functions:

       Interface of Module System:

           elektraModulesInit (KeySet *modules, Key *error); elektraPluginFactory
           elektraModulesLoad (KeySet *modules,
                   const char *name, Key *error);
           int elektraModulesClose (KeySet *modules, Key *error); \end{lstlisting}

       elektraModulesInit()  initialises  the  module  cache  and calls necessary operating system facilities if
       needed. \method{elektraModulesLoad()} does the main work by either returning  a  pointer  to  the  plugin
       factory  from  cache  or loading it from the operating system. The plugin factory creates plugins that do
       not have references to the module anymore. elektraModulesClose() cleans up the cache  and  finalises  all
       connections with the operating system.

       Not  every  plugin  is  loaded  by  libloader.  For  example,  the  version plugin, which exports version
       information, is implemented internally.

Mount Point Configuration

       kdb mount creates a mount point configuration as shown in the example  below.  fstab  is  a  unique  name
       within the mount point configuration provided by the administrator.

       Example for a mount point configuration:

           system/elektra/mountpoints system/elektra/mountpoints/fstab
           system/elektra/mountpoints/fstab/config
           system/elektra/mountpoints/fstab/config/path=fstab
           system/elektra/mountpoints/fstab/config/struct=list FStab
           system/elektra/mountpoints/fstab/config/struct/FStab
           system/elektra/mountpoints/fstab/config/struct/FStab/device
           system/elektra/mountpoints/fstab/config/struct/FStab/dumpfreq
           system/elektra/mountpoints/fstab/config/struct/FStab/mpoint
           system/elektra/mountpoints/fstab/config/struct/FStab/options
           system/elektra/mountpoints/fstab/config/struct/FStab/passno
           system/elektra/mountpoints/fstab/config/struct/FStab/type
           system/elektra/mountpoints/fstab/errorplugins
           system/elektra/mountpoints/fstab/errorplugins/#5#resolver#resolver#
           system/elektra/mountpoints/fstab/getplugins
           system/elektra/mountpoints/fstab/getplugins/#0#resolver
           system/elektra/mountpoints/fstab/getplugins/#5#fstab#fstab#
           system/elektra/mountpoints/fstab/mountpoint /fstab
           system/elektra/mountpoints/fstab/setplugins
           system/elektra/mountpoints/fstab/setplugins/#0#resolver
           system/elektra/mountpoints/fstab/setplugins/#1#struct#struct#
           system/elektra/mountpoints/fstab/setplugins/#2#type#type#
           system/elektra/mountpoints/fstab/setplugins/#3#path#path#
           system/elektra/mountpoints/fstab/setplugins/#3#path#path#/config
           system/elektra/mountpoints/fstab/setplugins/#3#path#path#/config/path/allow=proc tmpfs none
           system/elektra/mountpoints/fstab/setplugins/#5#fstab
           system/elektra/mountpoints/fstab/setplugins/#7#resolver \end{lstlisting}

       Let us look at the subkeys below the key system/elektra/mountpoints/fstab:

       config Everything  below  \keyname{config}  is  the  system´s  configuration of the backend. Every plugin
              within  the  backend  will  find  this  configuration  directly  below  \keyname{system/}  in  its
              \intro{plugin configuration}. For example,

              system/elektra/mountpoints/fstab/config/struct/FStab/mpoint

       will be translated to

           system/struct/FStab/mpoint

       and inserted into the plugin configuration for all plugins in the fstab backend.

       It  is  the place where configuration can be provided for every plugin of a backend. The contract checker
       deduces this configuration to satisfy the contract for a plugin. Fstab, for example, claims in a contract
       that it needs ´´struct´´. But the struct plugin needs  a  configuration  to  work  properly.  Fstab  will
       provide  this  configuration.  The \empha{contract checker} writes out the configuration looking like the
       one in this example.

       config/path
              is a common setting needed by the resolver plugin. It is the relative path to a filename  that  is
              used by this backend. On UNIX systems, the resolver would determine the name /etc/fstab for system
              configuration.

       mountpoint
              is  a key that represents the mount point. Its value is the location where the backend is mounted.
              If a mount point has an  entry  for  both  the  user  and  the  system  hierarchy,  it  is  called
              \intro{cascading  mount  point}.  A  cascading  mount point differs from two separate mount points
              because internally only one backend is created. In the example, the mount point /fstab means  that
              the backend handles both user/fstab and system/fstab. If the mount point is /, the backend will be
              mounted to all namespaces except spec, including both user and system.

       errorplugins
              presents  a  list  of  all  plugins  to  be  executed  in the error case of kdbSet() which will be
              explained in \secref{error situation}.

       getplugins
              is a list of all plugins used when reading the configuration  from  the  key  database.  They  are
              executed in kdbGet().

       setplugins
              contains a list of all plugins used when storing configuration. They are executed in kdbSet().

       Each  of  the  plugins  inside  the  three lists may have the subkey config. The configuration below this
       subkey provides plugin specific configuration. This configuration appears in the user´s configuration  of
       the plugin. Configuration is renamed properly. For example, the key

           system/elektra/mountpoints/fstab/setplugins/#3#path#path#/config/path/allow

       is transformed to

           user/path/allow

       and appears in the plugin configuration of the path plugin inside the fstab backend.

   Referencing
       The  same  plugin often must occur in more than one place within a backend. The most common use case is a
       plugin that has to be executed for both kdbGet() and kdbSet(). It must be the same plugin if it preserves
       state between the executions.

       Other plugins additionally have to handle  error  or  success  situations.  One  example  of  exceptional
       intensive  use  is  the resolver plugin. It is executed twice in kdbSet(). In kdbGet() it is also used as
       shown in Listing~\ref{lst:mount point configuration}.

       \lstinline[language=]{#nname} introduces a new plugin from the module name  which  cannot  be  referenced
       later. The cypher n appoints the actual placement of the plugin. \lstinline[language=]{#n#name#

   Changing Mount Point Configuration
       When  the  user  changes  the  mount  point  configuration, without countermeasures, applications already
       started will continue to run with the old configuration. This could lead to a problem if backends in  use
       are  changed  or  removed.  It is necessary to restart all such programs. Notification is the best way to
       deal with the situation. Changes of the mount point configuration, however, do not occur often. For  some
       systems, the manual restart may also be appropriate.

       In  this  situation, applications can receive warning or error information if the configuration files are
       moved or removed. The most adverse situation occurs if the sequence of locking multiple files produces  a
       \empha{dead  lock}.  Under  normal  circumstances, the sequence of locking the files is deterministic, so
       either all locks can be requested or another program will be served  first.  But  several  programs  with
       different mount point configurations running at the same time can cause a disaster. The problem gets even
       worse,  because  kdb  mount is unable to detect such situations. Every specific mount point configuration
       for itself is trouble-free.

       But still a dead lock can arise when multiple programs run with  different  mount  point  configurations.
       Suppose  we  have  a program A which uses the backends B1 and B2 that requests locks for the files F1 and
       F2. Then the mount point configuration is changed. The user removes B1 and introduces  B3.  B3  is  in  a
       different path mounted after B2, but also accesses the same file F1. The program B starts after the mount
       point  configuration is changed. So it uses the backends B2 and B3. If the scheduler decides that first A
       and then B both successfully lock the files F1 and F2, a dead  lock  situation  happens  because  in  the
       afterwards the applications A and B try to lock F2 and F1.

       A  manual  solution  for  this  problem is to enable kdb to output a list of processes that still use old
       mount point configuration. The administrator can restart these processes. The preferred  solution  is  to
       use notification for mount point configuration changes or simply to use a lock-free resolver.

       Continue reading with the data structures elektra-data-structures.md.

                                                    July 2017                            ELEKTRA-ARCHITECTURE(7)