APP_CONFIG(3)

NAME
app_config - application configuration system

LIBRARY
PDEL Library (libpdel, -lpdel)

SYNOPSIS
#include <sys/types.h>
#include <pdel/config/app_config.h>
struct app_config_ctx *
app_config_init(struct pevent_ctx *ctx, const struct app_config *info, void *cookie);
int
app_config_uninit(struct app_config_ctx **ctxp);
void *
app_config_get_cookie(struct app_config_ctx *ctx);
int
app_config_load(struct app_config_ctx *ctx, const char *path, int allow_writeback);
int
app_config_reload(struct app_config_ctx *ctx);
void *
app_config_new(struct app_config_ctx *ctx);
int
app_config_set(struct app_config_ctx *ctx, const void *config, u_long delay, char *ebuf, int emax);
void *
app_config_get(struct app_config_ctx *ctx, int pending);
const struct structs_type *
app_config_get_type(struct app_config_ctx *ctx);
void *
app_config_copy(struct app_config_ctx *ctx, const void *config);
void
app_config_free(struct app_config_ctx *ctx, void **configp);
extern const struct app_subsystem app_config_alog_subsystem;
extern const struct app_subsystem app_config_curconf_subsystem;
extern const struct app_subsystem app_config_directory_subsystem;
extern const struct app_subsystem app_config_pidfile_subsystem;

DESCRIPTION
These functions implement an application configuration framework.

  Application model
The app_config model assumes that the application's configuration is stored in a single configuration object, which can be any data structure that is describable by a structs(3) type. The configuration can be stored in an XML file which is automatically updated.

The application itself consists of one or more subsystems. A subsystem is an abstract activity that can be started or stopped and whose behavior depends on (some part of) the configuration object. When the configuration object is changed, those subsystems that require it are automatically stopped and then restarted with the new configuration.

The application may provide methods for:

•   Creating a new, default configuration object
•   Initializing a new configuration object for the current system
•   Checking a configuration object for overall validity
•   Normalizing a configuration object
•   Upgrading a configuration object from an old version

Each subsystem may provide methods for:

•   Starting the subsystem
•   Stopping the subsystem
•   Determining if the subsystem will run
•   Determining if the subsystem needs to be restarted

In the steady state, there is a single currently active configuration object. Only those subsystems that are supposed to be running are. All running subsystems are running with their configurations based on (i.e., consistent with) the active configuration object.

When there needs to be a configuration change, a new configuration object is constructed and the app_config library is told to apply the new configuration object. After a configurable delay (for hysteresis), those subsystems affected by the change are stopped, the new configuration object is installed in place of the old one, and the stopped subsystems are restarted.

The app_config library guarantees that only "valid" configurations may be applied, where the meaning of "valid" is determined by the application.

  Subsystems
A subsystem is defined as anything that can be started and/or stopped. A subsystem typically depends on some portion of the configuration object such that it must be stopped and restarted when that portion changes.

A subsystem is defined by a struct app_subsystem:

    struct app_subsystem {
        const char          *name;      /* name, null to end list */
        void                *arg;       /* opaque subsystem argument */
        app_ss_startup_t    *start;     /* start subsystem */
        app_ss_shutdown_t   *stop;      /* stop subsystem */
        app_ss_willrun_t    *willrun;   /* will subsystem run? */
        app_ss_changed_t    *changed;   /* subsystem config changed? */
        const char          **deplist;  /* config items dependent on */
    };

The name is currently used only for debugging, but must be NULL to terminate the list of subsystems (see below). The arg is ignored and is for the application's private use. The start, stop, willrun, and changed fields must be pointers to functions having these types:

    typedef int  app_ss_startup_t(struct app_config_ctx *ctx,
                    const struct app_subsystem *ss, const void *config);
    typedef void app_ss_shutdown_t(struct app_config_ctx *ctx,
                    const struct app_subsystem *ss, const void *config);
    typedef int  app_ss_willrun_t(struct app_config_ctx *ctx,
                    const struct app_subsystem *ss, const void *config);
    typedef int  app_ss_changed_t(struct app_config_ctx *ctx,
                    const struct app_subsystem *ss,
                    const void *config1, const void *config2);

start() starts the subsystem; config points to a copy of the current (i.e., new) configuration object. start() should return zero if successful, or else -1 with errno set on failure. In the latter case, the stop() method will not be called.

stop() stops the subsystem; config points to a copy of the current configuration object.

willrun() should return non-zero if the subsystem needs to run at all. config points to a copy of the current (i.e., new) configuration object.

changed() determines if the subsystem needs to be restarted during a configuration change from config1 to config2. It should return 1 if so, zero otherwise.

In the above methods, the configuration object argument(s) become invalid after the method returns.

Alternately, or in conjunction with the changed() method, the deplist may point to a NULL terminated list of structs(3) names of fields in the configuration object on which this subsystem depends. The subsystem will automatically be restarted if any of the named fields differ between config1 and config2, as determined by structs_equal(3).

All four of the above subsystem methods are optional and may be specified as NULL. In the case of startup() and shutdown(), NULL means "do nothing". willrun() being NULL is equivalent to it always returning 1. changed() being NULL is equivalent to it always returning 0. deplist being NULL is equivalent to an empty list.

  Application
An application itself is described by a struct app_config:

    struct app_config {
        u_int                       version;    /* current version # */
        const struct structs_type   **types;    /* all version types */
        const struct app_subsystem  **slist;    /* list of subsystems */
        app_config_init_t           *init;      /* initialize defaults */
        app_config_getnew_t         *getnew;    /* generate new config */
        app_config_checker_t        *checker;   /* validate a config */
        app_config_normalize_t      *normalize; /* normalize a config */
        app_config_upgrade_t        *upgrade;   /* upgrade a config */
    };

The list of subsystems supported by the application is pointed to by slist. This list must be terminated with an entry whose name is NULL.

Subsystems are always started in the order they are listed in slist, and they are always shutdown in the reverse order.

The version is the configuration object version number (the first version is zero), and types points to an array of version + 1 pointers to structs(3) types for the configuration object, where types[i] is the structs(3) type for version i of the configuration object.

The remaining fields are pointers to functions having these types:

    typedef int  app_config_init_t(struct app_config_ctx *ctx,
                     void *config);
    typedef int  app_config_getnew_t(struct app_config_ctx *ctx,
                     void *config);
    typedef int  app_config_checker_t(struct app_config_ctx *ctx,
                     const void *config, char *errbuf, size_t ebufsize);
    typedef void app_config_normalize_t(struct app_config_ctx *ctx,
                     void *config);
    typedef int  app_config_upgrade_t(struct app_config_ctx *ctx,
                     const void *old_conf, u_int old_version,
    		 void *new_conf);

If the default configuration object is not equal to what is provided by structs_init(3), then init() may be implemented. It should further modify the config as appropriate to get the generic default configuration. init() returns zero on success, or -1 on error with errno set appropriately.

getnew() is invoked when no existing configuration is found by app_config_load() (see below). The config is as returned by init(). getnew() should apply any further initialization required for this particular system. getnew() returns zero on success, or -1 on error with errno set appropriately.

The distinction between init() and getnew() is somewhat subtle: init() simply initializes a new configuration object. It may be invoked many times during the normal operation of the application as configuration objects are needed. getnew() is only invoked once, at the beginning of application startup, when there is no previously saved configuration found. Therefore, the behavior of init() should not be affected by the "environment", while the behavior of getnew() often is.

checker() determines whether the config is valid, returning 1 if so or 0 if not. In the latter case, it may print an error message (including '\0') into the buffer errbuf, which has size ebufsize (see snprintf(3)).

normalize() gives the application a chance to normalize an otherwise valid configuration object. This is useful when the configuration object contains redundant information, or information that can be represented in more than one way.

All configurations that are applied by app_config are guaranteed to have been checked and normalized. All configurations passed to checker() are guaranteed to have been passed through normalize() first.

Note: the configurations returned by init() and getnew() must be valid according to checker.

upgrade() is invoked when an older version of the configuration object is read in from an XML file. The configuration version number is stored as the "version" attribute of the XML document element. old_conf is the old object, which has version old_version, and new_conf is a newly initialized configuration object of the current version. upgrade() should copy the configuration information from old_conf to new_conf.

A quick and dirty way to do this when most of the fields are the same is to use structs_traverse(3) to list the fields in the old configuration object, structs_get_string(3) to get their ASCII values, and structs_set_string(3) to set the same values in the new configuration object.

  API
app_config_init() should be called at application startup time to initialize app_config for the application described by info. A pevent(3) context ctx must be supplied. app_config_init() returns an application context, with which all configuration and subsystems are associated. Multiple independent application contexts may exist at the same time. The cookie is saved along with the context but is otherwise ignored.

app_config_uninit() should be called at application shutdown time to release resources allocated by app_config. It may only be called when all subsystems are shutdown (i.e., the current configuration object pointer is NULL). This enables app_config_init() to be called again, if so desired.

Upon return from app_config_uninit(), *ctxp will be set to NULL. If *ctxp is already equal to NULL when app_config_uninit() is invoked, nothing happens.

app_config_get_cookie() retrieves the application cookie provided to app_config_init().

app_config_load() reads in an application configuration object from the XML file at path and applies it, making it the current configuration. If path is empty or non-existent, a new configuration object is created using the application's getnew() method.

If the file contains an old version of the configuration object, it is automatically upgraded to the current version. If allow_writeback is non-zero, then path is remembered and the file is updated (i.e., rewritten) every time the application configuration object changes. Updates are done atomically by creating a temporary file with the suffix ".new" and renaming it (see rename(2)).

In theory, one call to app_config_load() in an application's main() routine is all that is required to get things going.

app_config_reload() reloads the configuration file previously specified to app_config_load() and applies it. This would be the typical response to receiving a SIGHUP signal.

app_config_new() creates a new configuration object with the application's default values as specified by the application's init() method. The returned pointer should be cast to the appropriate type. The caller is responsible for eventually freeing the returned configuration object by calling app_config_free().

app_config_set() changes the application's current configuration to be a copy of the configuration pointed to by config. If this configuration is invalid, -1 is returned with errno set to EINVAL, and if ebuf is not NULL, the buffer pointed to by ebuf and having size emax is filled in with a '\0'-terminated error message. app_config_set() may also return -1 with errno set to other values in the case of system errors.

If config is NULL, all running subsystems will be shut down. Any configurations passed to app_config_set() subsequent to passing a NULL configuration, but before the shutdown operation has completed, are ignored. This guarantees that a NULL configuration does actually shutdown the application.

The new configuration (or shutdown) takes effect after a delay of delay milliseconds after app_config_set() has successfully returned zero. The appropriate subsystem stop(), and then start() methods are invoked serially from a new thread.

app_config_get() returns a copy of the current or pending configuration object. The returned pointer should be cast to the appropriate type. If pending is zero, then the configuration object currently in use is copied. Otherwise, the configuration object most recently applied via app_config_set() is copied. These will be different when there is a pending, but not yet applied, configuration. The caller is responsible for eventually freeing the returned configuration object by calling app_config_free().

app_config_get_type() returns the structs(3) for the application configuration object.

app_config_copy() copies a configuration object. The returned pointer should be cast to the appropriate type. The caller is responsible for eventually freeing the returned configuration object by calling app_config_free().

app_config_free() destroys the configuration object pointed to by *configp. Upon return, *configp will be set to NULL. If *configp is already NULL when app_config_free() is invoked, nothing happens.

  Pre-defined subsystems
The app_config library comes with some predefined subsystem templates.

app_config_alog_subsystem handles configuring error logging for an application. To use app_config_alog_subsystem, copy the structure and set the arg field to point to a struct app_config_alog_info:

    struct app_config_alog_info {
        const char  *name;      /* field name */
        int         channel;    /* alog channel */
    };

The name should be the structs(3) field name of the field in the configuration object that configures logging for the alog(3) logging channel channel. This field should be a struct alog_config.

app_config_curconf_subsystem is useful when the application needs efficient access to the currently active configuration. This subsystem assumes that there is a global pointer variable (call it curconf) which by definition always points to a read-only copy of the currently active configuration. For example, if the application's configuration object is a struct my_config, then curconf would be defined as:

    const struct my_config *const curconf;

Then the function of app_config_curconf_subsystem is to automatically keep this variable up to date. (The const keywords reflect the application's point of view: the first is because the structure is read-only, while the second is because the pointer itself is read-only.)

To use app_config_curconf_subsystem, copy the structure and set the arg field to point to the application's curconf pointer variable. Typically the app_config_curconf_subsystem will be first in the list of subsystems, so that curconf is always updated before any other subsystem starts. Then at any time *curconf can be examined for the currently active configuration.

app_config_directory_subsystem handles configuring the current working directory for the process. To use app_config_directory_subsystem, copy the structure and set the arg field to point to a string containing the structs(3) name of the field in the configuration object that contains the directory name. If this name is not the empty string, then the current working directory will be set according to the value of this field.

app_config_pidfile_subsystem handles "PID files", i.e., exclusive application lock files into which the process ID is written. These guard against two instances of the same application running at the same time. To use app_config_pidfile_subsystem, copy the structure and set the arg field to point to a string containing the structs(3) name of the field in the configuration object that contains the PID file pathname.

RETURN VALUES
All of the app_config functions return NULL or -1 to indicate an error and set errno appropriately.

SEE ALSO
alog(3), libpdel(3), pevent(3), structs(3), typed_mem(3)

HISTORY
The PDEL library was developed at Packet Design, LLC. http://www.packetdesign.com/

AUTHORS
Archie Cobbs <archie@packetdesign.com>

BUGS
There should be explicit support for subsystems that require other subsystems to be running before they may run. As it stands now, such dependencies must be implicitly encoded into the willrun() and changed() methods. Even so, the dependent subsystem cannot detect if the other subsystem fails to start.

Subsystems should be defined more like objects using dynamically allocated structures that can be added and removed from the subsystem list at any time, without having to shutdown and restart the whole application.

It should be possible to start and shutdown subsystems individually.