C Representation of an CDL-config-instance and API

Appreviations:
-------------
   CDL - Configuration Description Language
   CD  - a concrete Configuration Description written in CDL

Introduction:
-------------
A CDL-config-instance is a runtime C structure that holds
configuration information according to the active CD.
This configuration information can be queried and set
using a C-API. Usually the code should "know" already
under which key to find what, since the CD is a static
construct, i.e. it is not allowed to change the CD dynamically.

That means also, we will not do run-time type validation while
settings a value. This must be done explicitely.
Maybe during debug run's we do have such a thing like implicit
type validation.

The container to hold a single CDL-config-instance is a so called
profile.

Structure Overview:
-------------------
There are certain Objects or pseudo objects (singletons, 
that are represented by an API of a lib in C)

Profile:
   - structure holding a particular CD-Instance
   - reference counted to allow caching, and carelessly them passing around
   - no specific methods except some general ones like
     - create
     - destroy 
     - duplicate
     - union
     - clone

LocalProfileManager:
   - manages the following locally stored Profiles
     (i.e. creates, caches und updates them):
     - TaggedProfile
     - LocalProfile
     - DefaultProfile
     - CodeProfile (contains hard coded defaults)
   - provides API to access them (see below)

LDAPProfileManager:
   - manages LDAP Profile (Device and User)
     (i.e. queries, caches and requeries them, 
     implements the PEMX-LDAP-Update-Protokoll)
   - API to retrieve the Profile but not to access it

LayerProfile Manager:
   - manages the stack of profiles
   - replaces the general access fkts of the LocalProfileManager when 
     linked
   - the currently fixed priority is as follows:
     1. UserLdapProfile
     2. DevLdapProfile
     3. TaggedProfile
     4. TaggedProfile
     5. LocalProfile
     6. DefaultProfile
     7. CodeProfile

LocalProfileManager API:
--------------------------
The variable argument lists of the following functins will 
be type validated by gcc using the printf rules.

- LocalProfileManager:  for the LocalProfile
- LayerdProfileManager: for the EffectiveProfile

/**
 * Get the string value of a config key
 * @param out parameter for value, if *value == NULL, key was not found
 * @param key the config path to a specific property, inclusive
 *            format flags for the vector index
 *            %d - int
 *            %s - string
 * @arglist   paramters for format flags
 * @return    error code or
 *            0 for success
 * @example   if (err = pp_get_config(*buttonkey, 
 *                                    "User[%s].KVMPort[%d].ButtonKey",
 *                                    "ronald", 4)) {
 *                pp_log("pp_get_config failed: %s", pp_error_string(err));
 *                return 0;
 *            }
 */
int pp_cfg_get(char** value, const char* key,...);

/**
 * Set the string value of a config key.
 * Works on PP_PROFILE_LOCAL.
 * @see pp_cfg_get
 */
int pp_cfg_set(const char* value, const char* key,...);
  
/**
 * Get the integer value of a config key
 * @see pp_cfg_get
 */
int pp_cfg_get_number(int* number, char* key,...);

/**
 * Set the integer value of a config key
 * Works on PP_PROFILE_LOCAL.
 * @see pp_cfg_get
 */
int pp_cfg_set_number(const int number, const char* key,...);

/** 
 * Get the boolean value of a config key
 * @param is_enabled will be set to true, if the property value
 *        is "yes", otherwise, even if key can't be found, it will
 *        be set to false
 * @see pp_cfg_get
 */
int pp_cfg_is_enabled(int* is_enabled, const char* key,...);

/** 
 * Set the boolean value of a config key
 * Works on PP_PROFILE_LOCAL.
 * @see pp_cfg_is_enabled
 */
int pp_cfg_set_enabled(int* is_enabled, const char* key,...);

/**
 * Compacts a vector in the sense, that the current size
 * of the vector will be shrinked to the smalles possible size
 * still holding all existing elements, i.e. not existing indexes
 * will be removed from the vector. The length member of the vector
 * will be set accordingly
 */
int pp_cfg_compact_vector(char* vectorkey,...);

accessing a particular Layer:
------------------------------
typedef enum { PP_PROFILE_NONE,       // invalid type, used to indicate errors
               PP_PROFILE_LOCAL_CODE, // hard coded default prof, not writeable
               PP_PROFILE_LOCAL_DEF,  // default profile in config fs
               PP_PROFILE_LOCAL,      // standard profile in config fs
               PP_PROFILE_LOCAL_TAG,  // current tagged profile in config fs
               PP_PROFILE_LDAP,       // ldap profile
               PP_PROFILE_EFFECTIVE   // effective profile
} pp_profile_type_t;

int pp_cfg_get_at_layer(pp_profile_type_t type, char** value, 
                                char* key,...);
int pp_cfg_set_at_layer(pp_profile_type_t type, char* value, 
                                char* key,...);
int pp_cfg_get_number_at_layer(pp_profile_type_t type, int* number, 
                                char* key,...);
int pp_cfg_set_number_at_layer(pp_profile_type_t type, int number, 
                                 char* key,...);
int pp_cfg_is_enabled_at_layer(pp_profile_type_t type, int* is_enabled, 
                                char* key,...);
int pp_cfg_set_enabled_at_layer(pp_profile_type_t type, int* is_enabled, 
                                 char* key,...);
int pp_cfg_compact_vector_at_layer(pp_profile_type_t type, 
                                    char* vectorkey,...);

Layered Profiles:
------------------
The concept of Layered profiles means, that profiles of different types
can be stacked one above another and entirely or partly coverup each 
other. A higher ranked profile will cover a lower ranked profile where
a config key is defined or where its config description is not defined
to be 'transparent'. The default for a single config description is 
'transparent'.

Users/Grouos and user/group specific configs and ACLs:
-----------------------------------------------------------------
Users/Group specific configs are stored in a config instance
under a predefined associative array with the user-id/group-id as 
the key. The name of those associative arrays are commonly known as 
    'User'
and 
    'Group'

Although relations between users and groups will be stored in 
the config instance, the C-represention of such things is done
in the User lib.

ACL (Access Control Lists):
---------------------------
ACLs are stored in a config-instance as normal associative array.
However in order to access them more conviniently and efficiently
they are translated into a separete C-representation having a 
seperate API

ACLs are never transparent. The highest layered profile of a 
profile stack having ACL entries will be used for the following
API functions:

API to calculate a permission:

/** 
 * @param has_perm out parameter indicating whether the requested 
 *        permission can be found in the current profiles ACL or not
 * @return    error code or
 *            0 for success
 */
int pp_acl_has_permission(int* has_perm, const char* uid, 
                          const char* perm, const char* permvalue);

API to access ACL-Entries:
... TODO

API to access ACL-Object database:
... TODO

How to store a User ACL in Config:
... TODO

How to store the global predefined ACL Objects in Config:
... TODO

Profile class organization and caching:
-----------------------------------------
A Profile is a simple structure containing
hirarich Red-Black-Trees (they are more memory efficient 
than hashtables and for our number of config vars they 
offer reasable lookup speed) similar to the Hirarchie of the CD.

A vector config Property consists stores its vector element 
defaults in a tree and the vector elements itself in a another tree 
that can be found in the default tree under a predefined key named
'_elements_'

Caching of Profiles must be provided by the Profile Manager,
which creates the profile.

Profiles contain only 'configurations', i.e. there are
ACLs but they remain in the config format (probably an 
associative array (ACL[<perm-obj>]=<perm-value>)).
When principal objects are created and somebody tries to 
access permission information, the ACL is loaded into a 
more efficient internal representation.
When a ldap user is authenticated we create an temporaer 
principal object, so we can still operate on principals
and ACLs in the usual way

Config Notifications and Transactions:
--------------------------------------
ProfileManagers have an additional API to notify interested
parties of config changes and to put multiple changes 
that depend on each other in effect at once what we refer to
as Transactions.

Concurrent Transactions and Locking:
Transactions can be opened concurrently by mutliple threads,
the following rules apply:
- an open transaction will not lock the other config ops
- operations on an transaction will be cached in the profile
  manager, i.e. they are not visible using a get operation 
  unless the transaction will have been commited.
- config operations on a single transaction context are not thread safe
- the same config keys may be changed in different open transactions.
  the transaction on which commit is called last will 'win', i.e. 
  its value will remain in the profile.
- get operations are always a 'pass-through', i.e. 
  config options may change during an open transaction.
- a transaction commit will lock the entire config profile 
  for get, set, tx_set, tx_begin and other tx_commit operations
  until the commit has been completed

API outline:

/**
 * type of the callback function
 */
typedef void (*)(void) pp_cfg_chg_cb;

/**
 * Add an notification callback for a certain property.
 * 
 * The callback will not tell which config has been changed
 * so the listener is responsible for keeping in mind what he's 
 * registered for. There is also no way to figure out the old 
 * value before the change happened. In case this is important
 * for the listener, he must keep that config value in it's state,
 * somehow.
 * 
 * Generally it is intended to register the same callback not 
 * only for a single option but for a bunch of related config options.
 * Once the callback gets fired, the listener is expected to figure 
 * out whether and how a particular option has changed.
 * 
 * The callback gets directly fired after a set operation in case
 * of executing the set without transaction context or after the commit 
 * operation in case of an active transaction
 */
pp_cfg_add_change_listener(pp_profile_type_t type, const char* cfgkey,
                           pp_cfg_chg_cb cfg_chg_db);

/**
 * type of transaction context
 */
typedef struct pp_cfg_tx_t;

/**
 * Start a config transaction.
 * @return the transaction context
 */
pp_cfg_tx_t* pp_cfg_tx_begin();

/**
 * Commits a config transaction
 * 
 * The transaction context will be destroyed.
 *
 * in case there are listeners registered for any of the config keys
 * that was set during the transaction, their callback will be fired
 * exactly once per commit
 */
void pp_cfg_tx_commit(pp_cfg_tx_t* tx);

/**
 * Discards all changes since the begin of this transaction,
 * i.e. they will not be written through to the profile
 * 
 * The transaction context will be destroyed.
 */
void pp_cfg_tx_rollback(pp_cfg_tx_t* tx);

/*
 * the following functions are the transaction aware 
 * config set operations
 */
int pp_cfg_tx_set(pp_cfg_tx_t* tx, const char* value, const char* key,...);
int pp_cfg_tx_set_number(pp_cfg_tx_t* tx, const int number, 
                         const char* key,...);
int pp_cfg_tx_set_enabled(pp_cfg_tx_t* tx, int* is_enabled, 
                          const char* key,...);
int pp_cfg_tx_compact_vector(pp_cfg_tx_t* tx, char* vectorkey,...);

Packaging:
-------------

libpp_cfg: 
   provides:
      definitions for pp_profile, pp_acl, pp_setting
      Config Description Browser API
      Local Profile Manager, i.e.
        Config Access API simple (pp_cfg_get(), pp_cfg_set(), etc.) (WEAK)
        Config Access API at layer (pp_cfg_get_option_at_layer(), etc.)
        File-serialization/deserialization of profiles
   depends on:
      libpp_base

libpp_ldap:
   provides:
      LDAP-deserialization of profiles
   depends on:
      libpp_cfg
      libpp_base

libpp_cfgx: (cfgx = config etended)
   provides:
      Profile Layering / Merging
      Layered Profile Manager, i.e.
        Config Access API simple (STRONG, overwrites API of libpp_cfg)
   depends on:
      libpp_cfg
      libpp_ldap
      libpp_base

libpp_um:
   provides:
      Principal management
      Principal load/save from/to profile
      Decoding and Encoding of ACL from / to Profile
      Some convinient fkts for set/get-Principal-options
   depends on:
      libpp_cfgx
      libpp_cfg
      libpp_ldap
      libpp_base
   
Note:
libpp_cfgx is layered above libpp_cfg that is transparent for the 
users of the libpp_cfg API. This is achieved using a method:

pp_profile_t* pp_cfg_get_profile(...)

defined in libpp_cfg that returns the LOCAL_PROFILE loaded
from a file system.

Once libpp_cfgx is initialiced it will overwrite

pp_profile_t* pp_cfg_get_profile(...) 

to return the EFFECTIVE_PROFILE
instead of the LOCAL_PROFILE, hence enabling LDAP stuff
and such things for the calling functions. 