Libucw contains a parser for configuration files. The syntax of the configuration files is described in [config], here we explain the interface of the parser.

Basically, you write a description of the configuration file syntax, which maps configuration items to variables of your program. Then Then you run the parser and it fills your variables with the values from the configuration file.

The descriptions are modular. The configuration can be split to sections, each section declared at a separate place. You can also define your own data types.

Example

If you want to just load simple configuration, this is the part you want to read. This simple example should give you the overview. Look at the convenience macros section to see list of supported data types, sections, etc.

Suppose you have configuration file with the following content and you want to load it:

HelloWorld {
  Text        "Hello planet"
  Count       3
}

The structure

First, you declare the structure and let the configuration parser know it exists.

#include <ucw/lib.h>
#include <ucw/conf.h>
static char *hw_text = "Hello world";
static int hw_count = 1;
static int hw_wait_answer = 0;
static struct cf_section hw_config = {
  CF_ITEMS {
    CF_STRING("Text", &hw_text),
    CF_INT("Count", &hw_count),
    CF_INT("WaitAnswer", &hw_wait_answer),
    CF_END
  }
};
static void CONSTRUCTOR hw_init(void) {
  cf_declare_section("HelloWorld", &hw_config, 0);
}

The variables are used to store the loaded values. Their initial values work as defaults, if nothing else is loaded. The hw_config() structure assigns the variables to configuration names. The hw_init() function (because of the CONSTRUCTOR macro) is run before main() is called and it tells the parser that the section exists (alternatively, you can call cf_declare_section() at the start of your main()).

You can plug in as many configuration sections as you like, from various places across your code.

Loading configuration

You can load the configuration explicitly by calling cf_load(). That can be convenient when writing a library, but in normal programs, you can ask the option parser to handle it for you.

A typical example follows, please see the interface between conf and opt for details.

#include <ucw/lib.h>
#include <ucw/opt.h>
static struct opt_section options = {
  OPT_ITEMS {
    // More options can be specified here
    OPT_HELP("Configuration options:"),
    OPT_CONF_OPTIONS,
    OPT_END
  }
};
int main(int argc, char **argv)
{
  cf_def_file = "default.cf";
  opt_parse(&options, argv+1);
  // Configuration file is already loaded here
  return 0;
}

Getting deeper

Since the configuration system is somehow complicated, this part gives you a little overview of what you can find and where.

Arrays and lists

It is sometime needed to have multiple items of the same type. There are three ways to do that:

Static arrays

An array with fixed maximum length. You provide the length and already allocated array which is filled with items. The configuration may contain less than the maximum length items.

For example, you can have an static array of five unsigned integers:

static uint array[] = { 1, 2, 3, 4, 5 };
static struct cf_section section = {
  CF_ITEMS {
    CF_UINT_ARY("array", array, 5),
    CF_END
  }
};
Dynamic arrays

Similar to static array, but you provide pointer to pointer to the given item (eg. if you want dynamic array of integers, you give **int). The parser allocates a growing array of the required size.

If you want dynamic array of strings, you would use:

static char *array[];
static struct cf_section section = {
  CF_ITEMS {
    CF_STRING_DYN("array", &array, CF_ANY_NUM),
    CF_END
  }
};
Lists

Linked lists based on clists. You provide description of single node and pointer to the struct clist variable. All the nodes will be created dynamically and put there.

First element of your structure must be cnode.

The first example is list of strings and uses simple lists:

static struct clist list;
static struct cf_section section = {
  CF_ITEMS {
    CF_LIST("list", &list, &cf_string_list_config),
    CF_END
  }
};

Another example, describing how to create more complicated list node than just a string can be found at the CF_TYPE macro.

Reloading configuration

The configuration system allows you to reload configuration at runtime. The new config changes the values against the default values. It means, if the default value for variable A is 10, the currently loaded config sets it to 42 and the new config does not talk about this variable, A will have a value of 10 after a successful load.

Furthermore, if the loading of a new configuration fails, the current configuration is preserved.

All this is done with config journalling. The load of the first config creates a journal entry. If you try to load some new configuration, it is partially rolled back to defaults (the rollback happens, but instead of removing the journal entry, another journal entry is added for the rollback). If the loading succeeds, the two journal entries are removed and a new one, for the new configuration, is added. If it fails, the first one is replayed and the rollback entry is removed.

Creating custom parsers

If you need to parse some data type the configuration system can’t handle, you can write your own extended type and use CF_XTYPE macro to declare a new option.

There is also an obsolete way to write a custom parser. Before you start, you should know a few things.

The parser needs to support journalling. To accomplish that, you have to use the configuration mempool for memory allocation.

Now, you need a function with the same signature as cf_parser1. Parse the first parameter (the string) and store the data in the second parameter. You may want to write a dumper function, with signature of cf_dumper1 (needed for debug dumps).

Fill in a structure cf_user_type and use the new data type in your configuration description with CF_USER macro as its t parameter.

You do not need to call cf_journal_block() on the variable you store the result. It is true you change it, but it was stored to journal before your parser function was called.

Hooks

The configuration system supports hooks. They are used to initialize the configuration (if simple default value of variable is not enough) and to check the sanity of loaded data.

Each hook is of type cf_hook and you can include them in configuration description using CF_INIT and CF_COMMIT macros.

The hooks should follow similar guidelines as custom parsers (well, init hooks do not need to call cf_journal_block()) to support journalling. If you change nothing in the commit hook, you do not need to care about the journalling either.

You may use the return value to inform about errors. Just return the error message, or NULL if everything went well.

Another similar function is a copy function. It is very similar to a hook and is used when the item is copied and is too complicated to use simple memcpy(). Its type is cf_copier and is specified by the CF_COPY macro. It’s return value is the same as the one of a hook.

ucw/conf.h

This header file contains the public interface of the configuration module.

Configuration contexts

The state of the configuration parser is stored within a configuration context. One such context is automatically created during initialization of the library and you need not care about more, as long as you use a single configuration file.

In full generality, you can define as many contexts as you wish and switch between them. Each thread has its own pointer to the current context, which must not be shared with other threads.


struct cf_context *cf_new_context(void);

Create a new configuration context.


void cf_delete_context(struct cf_context *cc);

Free a configuration context. The context must not be set as current for any thread, nor can it be the default context.

All configuration settings made within the context are rolled back (except when journalling is turned off). All memory allocated on behalf of the context is freed, which includes memory obtained by calls to cf_malloc().


struct cf_context *cf_switch_context(struct cf_context *cc);

Make the given configuration context current and return the previously active context. Both the new and the old context may be NULL.

Safe configuration loading

These functions can be used to to safely load or reload configuration.


int cf_load(const char *file);

Load configuration from file. Returns a non-zero value upon error. In that case, all changes to the configuration specified in the file are undone.


int cf_reload(const char *file);

Reload configuration from file, replace the old one. If file is NULL, reload all loaded configuration files and re-apply bits of configuration passed to cf_set(). Returns a non-zero value upon error. In that case, all configuration settings are rolled back to the state before calling this function.


int cf_set(const char *string);

Parse some part of configuration passed in string. The syntax is the same as in the configuration file. Returns a non-zero value upon error. In that case, all changes to the configuration specified by the already executed parts of the string are undone.


void cf_open_group(void);

Sometimes, the configuration is split to multiple files and when only some of the are loaded, the settings are not consistent — for example, they might have been rejected by a commit hook, because a mandatory setting is missing.

This function opens a configuration group, in which multiple files can be loaded and all commit hooks are deferred until the group is closed.


int cf_close_group(void);

Close a group opened by cf_open_group(). Returns a non-zero value upon error, which usually means that a commit hook has failed.


void cf_revert(void);

Return all configuration items to their initial state before loading the configuration file. If journalling is disabled, it does nothing.

Data types


enum cf_class {
  CC_END,                               // end of list
  CC_STATIC,                            // single variable or static array
  CC_DYNAMIC,                           // dynamically allocated array
  CC_PARSER,                            // arbitrary parser function
  CC_SECTION,                           // section appears exactly once
  CC_LIST,                              // list with 0..many nodes
  CC_BITMAP                             // of up to 32 items
};

Class of the configuration item.


enum cf_type {
  CT_INT, CT_U64, CT_DOUBLE,            // number types
  CT_IP,                                // IP address
  CT_STRING,                            // string type
  CT_LOOKUP,                            // in a string table
  CT_USER,                              // user-defined type (obsolete)
  CT_XTYPE                              // extended type
};

Type of a single value.


typedef char *cf_parser(uint number, char **pars, void *ptr);

A parser function gets an array of (strdup’ed) strings and a pointer with the customized information (most likely the target address). It can store the parsed value anywhere in any way it likes, however it must first call cf_journal_block() on the overwritten memory block. It returns an error message or NULL if everything is all right.


typedef char *cf_parser1(char *string, void *ptr);

A parser function for user-defined types gets a string and a pointer to the destination variable. It must store the value within [ptr,ptr+size), where size is fixed for each type. It should not call cf_journal_block().


typedef char *cf_hook(void *ptr);

An init- or commit-hook gets a pointer to the section or NULL if this is the global section. It returns an error message or NULL if everything is all right. The init-hook should fill in default values (needed for dynamically allocated nodes of link lists or for filling global variables that are run-time dependent). The commit-hook should perform sanity checks and postprocess the parsed values. Commit-hooks must call cf_journal_block() too. Caveat! init-hooks for static sections must not use cf_malloc() but normal xmalloc().


typedef void cf_dumper1(struct fastbuf *fb, void *ptr);

Dumps the contents of a variable of a user-defined type.


typedef char *cf_copier(void *dest, void *src);

Similar to init-hook, but it copies attributes from another list node instead of setting the attributes to default values. You have to provide it if your node contains parsed values and/or sub-lists.


struct cf_user_type {
  uint size;                            // of the parsed attribute
  char *name;                           // name of the type (for dumping)
  cf_parser1 *parser;                   // how to parse it
  cf_dumper1 *dumper;                   // how to dump the type
};

Structure to store information about user-defined variable type.


struct cf_item {
  const char *name;                     // case insensitive
  int number;                           // length of an array or #parameters of a parser (negative means at most)
  void *ptr;                            // pointer to a global variable or an offset in a section
  union cf_union {
    struct cf_section *sec;             // declaration of a section or a list
    cf_parser *par;                     // parser function
    const char * const *lookup;         // NULL-terminated sequence of allowed strings for lookups
    struct cf_user_type *utype;         // specification of the user-defined type (obsolete)
    const struct xtype *xtype;          // specification of the extended type
  } u;
  enum cf_class cls:16;                 // attribute class
  enum cf_type type:16;                 // type of a static or dynamic attribute
};

Single configuration item.


struct cf_section {
  uint size;                            // 0 for a global block, sizeof(struct) for a section
  cf_hook *init;                        // fills in default values (no need to bzero)
  cf_hook *commit;                      // verifies parsed data (optional)
  cf_copier *copy;                      // copies values from another instance (optional, no need to copy basic attributes)
  struct cf_item *cfg;                  // CC_END-terminated array of items
  uint flags;                           // for internal use only
};

A section.

Convenience macros

You could create the structures manually, but you can use these macros to save some typing.

Declaration of cf_section

These macros can be used to configure the cf_section structure.


#define CF_TYPE(s)      .size = sizeof(s)

Data type of a section. If you store the section into a structure, use this macro.

Storing a section into a structure is useful mostly when you may have multiple instances of the section (eg. array or list).

Example:

struct list_node {
  cnode n;            // This one is for the list itself
  char *name;
  uint value;
};
static struct clist nodes;
static struct cf_section node = {
  CF_TYPE(struct list_node),
  CF_ITEMS {
    CF_STRING("name", PTR_TO(struct list_node, name)),
    CF_UINT("value", PTR_TO(struct list_node, value)),
    CF_END
  }
};
static struct cf_section section = {
  CF_LIST("node", &nodes, &node),
  CF_END
};

You could use CF_STATIC or CF_DYNAMIC macros to create arrays.


#define CF_INIT(f)      .init = (cf_hook*) f

An init hook. You can use this to initialize dynamically allocated items (for a dynamic array or list). The hook returns an error message or NULL if everything was OK.


#define CF_COMMIT(f)    .commit = (cf_hook*) f

A commit hook. You can use this one to check sanity of loaded data and postprocess them. You must call cf_journal_block() if you change anything.

Return error message or NULL if everything went OK.


#define CF_COPY(f)      .copy = (cf_copier*) f

A copy function. You need to provide one for too complicated sections where a memcpy is not enough to copy it properly. It happens, for example, when you have a dynamically allocated section containing a list of other sections.

You return an error message or NULL if you succeed.


#define CF_ITEMS        .cfg = ( struct cf_item[] )

List of sub-items.


#define CF_END          { .cls = CC_END }

End of the structure.

Declaration of a configuration item

Each of these describe single configuration item. They are mostly for internal use, do not use them directly unless you really know what you are doing.


#define CF_STATIC(n,p,T,t,c)    { .cls = CC_STATIC, .type = CT_##T, .name = n, .number = c, .ptr = CHECK_PTR_TYPE(p,t*) }

Static array of items. Expects you to allocate the memory and provide pointer to it.


#define CF_DYNAMIC(n,p,T,t,c)   { .cls = CC_DYNAMIC, .type = CT_##T, .name = n, .number = c, .ptr = CHECK_PTR_TYPE(p,t**) }

Dynamic array of items. Expects you to provide pointer to your pointer to data and it will allocate new memory for it and set your pointer to it.


#define CF_PARSER(n,p,f,c)      { .cls = CC_PARSER, .name = n, .number = c, .ptr = p, .u.par = (cf_parser*) f }

A low-level parser.


#define CF_SECTION(n,p,s)       { .cls = CC_SECTION, .name = n, .number = 1, .ptr = p, .u.sec = s }

A sub-section.


#define CF_LIST(n,p,s)          { .cls = CC_LIST, .name = n, .number = 1, .ptr = CHECK_PTR_TYPE(p,clist*), .u.sec = s }

A list with sub-items.


#define CF_BITMAP_INT(n,p)      { .cls = CC_BITMAP, .type = CT_INT, .name = n, .number = 1, .ptr = CHECK_PTR_TYPE(p,u32*) }

A bitmap.


#define CF_BITMAP_LOOKUP(n,p,t) { .cls = CC_BITMAP, .type = CT_LOOKUP, .name = n, .number = 1, .ptr = CHECK_PTR_TYPE(p,u32*), .u.lookup = t }

A bitmap with named bits.

Basic configuration items

They describe basic data types used in the configuration. This should be enough for most real-life purposes.

The parameters are as follows:

  • n — name of the item.

  • p — pointer to the variable where it shall be stored.

  • c — count.


#define CF_INT(n,p)             CF_STATIC(n,p,INT,int,1)

Single int value.


#define CF_INT_ARY(n,p,c)       CF_STATIC(n,p,INT,int,c)

Static array of integers.


#define CF_INT_DYN(n,p,c)       CF_DYNAMIC(n,p,INT,int,c)

Dynamic array of integers.


#define CF_UINT(n,p)            CF_STATIC(n,p,INT,uint,1)

Single uint (unsigned) value.


#define CF_UINT_ARY(n,p,c)      CF_STATIC(n,p,INT,uint,c)

Static array of unsigned integers.


#define CF_UINT_DYN(n,p,c)      CF_DYNAMIC(n,p,INT,uint,c)

Dynamic array of unsigned integers.


#define CF_U64(n,p)             CF_STATIC(n,p,U64,u64,1)

Single unsigned 64bit integer (u64).


#define CF_U64_ARY(n,p,c)       CF_STATIC(n,p,U64,u64,c)

Static array of u64s.


#define CF_U64_DYN(n,p,c)       CF_DYNAMIC(n,p,U64,u64,c)

Dynamic array of u64s.


#define CF_DOUBLE(n,p)          CF_STATIC(n,p,DOUBLE,double,1)

Single instance of double.


#define CF_DOUBLE_ARY(n,p,c)    CF_STATIC(n,p,DOUBLE,double,c)

Static array of doubles.


#define CF_DOUBLE_DYN(n,p,c)    CF_DYNAMIC(n,p,DOUBLE,double,c)

Dynamic array of doubles.


#define CF_IP(n,p)              CF_STATIC(n,p,IP,u32,1)

Single IPv4 address.


#define CF_IP_ARY(n,p,c)        CF_STATIC(n,p,IP,u32,c)

Static array of IP addresses.


#define CF_IP_DYN(n,p,c)        CF_DYNAMIC(n,p,IP,u32,c)

Dynamic array of IP addresses.


#define CF_STRING(n,p)          CF_STATIC(n,p,STRING,char*,1)

A string. You provide a pointer to a char * variable and it will fill it with dynamically allocated string. For example:

static char *string = "Default string";
static struct cf_section section = {
  CF_ITEMS {
    CF_STRING("string", &string),
    CF_END
  }
};

#define CF_STRING_ARY(n,p,c)    CF_STATIC(n,p,STRING,char*,c)

Static array of strings.


#define CF_STRING_DYN(n,p,c)    CF_DYNAMIC(n,p,STRING,char*,c)

Dynamic array of strings.


#define CF_LOOKUP(n,p,t)        { .cls = CC_STATIC, .type = CT_LOOKUP, .name = n, .number = 1, .ptr = CHECK_PTR_TYPE(p,int*), .u.lookup = t }

One string out of a predefined set. You provide the set as an array of strings terminated by NULL (similar to argv argument of main()) as the t parameter.

The configured variable (pointer to int) is set to index of the string. So, it works this way:

static *strings[] = { "First", "Second", "Third", NULL };
static int variable;
static struct cf_section section = {
  CF_ITEMS {
    CF_LOOKUP("choice", &variable, strings),
    CF_END
  }
};

Now, if the configuration contains choice "Second", variable will be set to 1.


#define CF_LOOKUP_ARY(n,p,t,c)  { .cls = CC_STATIC, .type = CT_LOOKUP, .name = n, .number = c, .ptr = CHECK_PTR_TYPE(p,int*), .u.lookup = t }

Static array of strings out of predefined set.


#define CF_LOOKUP_DYN(n,p,t,c)  { .cls = CC_DYNAMIC, .type = CT_LOOKUP, .name = n, .number = c, .ptr = CHECK_PTR_TYPE(p,int**), .u.lookup = t }

Dynamic array of strings out of predefined set.


#define CF_USER(n,p,t)          { .cls = CC_STATIC, .type = CT_USER, .name = n, .number = 1, .ptr = p, .u.utype = t }

A user-defined type. See creating custom parsers section if you want to know more.


#define CF_USER_ARY(n,p,t,c)    { .cls = CC_STATIC, .type = CT_USER, .name = n, .number = c, .ptr = p, .u.utype = t }

Static array of user-defined types (all of the same type). See creating custom parsers section.


#define CF_USER_DYN(n,p,t,c)    { .cls = CC_DYNAMIC, .type = CT_USER, .name = n, .number = c, .ptr = p, .u.utype = t }

Dynamic array of user-defined types. See creating custom parsers section.


#define CF_XTYPE(n,p,t)         { .cls = CC_STATIC, .type = CT_XTYPE, .name = n, .number = 1, .ptr = p, .u.xtype = t }

An extended type. See extended types if you want to know more.


#define CF_XTYPE_ARY(n,p,t,c)   { .cls = CC_STATIC, .type = CT_XTYPE, .name = n, .number = c, .ptr = p, .u.xtype = t }

Static array of extended types (all of the same type). See extended types.


#define CF_XTYPE_DYN(n,p,t,c)   { .cls = CC_DYNAMIC, .type = CT_XTYPE, .name = n, .number = c, .ptr = p, .u.xtype = t }

Dynamic array of extended types. See extended types.


#define CF_ANY_NUM              -0x7fffffff

Any number of dynamic array elements


#define DARY_LEN(a) GARY_SIZE(a)

Length of an dynamic array. An alias for GARY_SIZE.

Memory allocation

Each configuration context has one or more memory pools, where all data related to the configuration are stored.

The following set of functions allocate from these pools. The allocated memory is valid as long as the current configuration (when the configuration file is reloaded or rolled back, or the context is deleted, it gets lost).

Memory allocated from within custom parsers should be allocated from the pools.

Please note that the pool is not guaranteed to exist before you call cf_load(), cf_set(), or cf_getopt() on the particular context.


struct mempool *cf_get_pool(void);

Return a pointer to the current configuration pool.


void *cf_malloc(uint size);

Returns size bytes of memory allocated from the current configuration pool.


void *cf_malloc_zero(uint size);

Like cf_malloc(), but zeroes the memory.


char *cf_strdup(const char *s);

Copy a string into cf_malloc()ed memory.


char *cf_printf(const char *fmt, ...) FORMAT_CHECK(printf,1,2);

printf() into cf_malloc()ed memory.

Undo journal

The configuration system uses a simple journaling mechanism, which makes it possible to undo changes to configuration. A typical example is loading of configuration by cf_load(): internally, it creates a transaction, applies all changes specified by the configuration and if one of them fails, the whole journal is replayed to restore the whole original state. Similarly, cf_reload() uses the journal to switch between configurations.

In most cases, you need not care about the journal, except when you need to change some data from a hook, or if you want to call cf_modify_item() and then undo the changes.


void cf_set_journalling(int enable);

This function can be used to disable the whole journalling mechanism. It saves some memory, but it makes undoing of configuration changes impossible, which breaks for example cf_reload().


void cf_journal_block(void *ptr, uint len);

When a block of memory is about to be changed, put the old value into journal with this function. You need to call it from a commit hook if you change anything. It is used internally by low-level parsers. Custom parsers do not need to call it, it is called before them.


struct cf_journal_item;

Opaque identifier of the journal state.


struct cf_journal_item *cf_journal_new_transaction(uint new_pool);

Starts a new transaction. It returns the current state so you can get back to it. The new_pool parameter tells if a new memory pool should be created and used from now.


void cf_journal_commit_transaction(uint new_pool, struct cf_journal_item *oldj);

Marks current state as a complete transaction. The new_pool parameter tells if the transaction was created with new memory pool (the parameter must be the same as the one with cf_journal_new_transaction() was called with). The oldj parameter is the journal state returned from last cf_journal_new_transaction() call.


void cf_journal_rollback_transaction(uint new_pool, struct cf_journal_item *oldj);

Returns to an old journal state, reverting anything the current transaction did. The new_pool parameter must be the same as the one you used when you created the transaction. The oldj parameter is the journal state you got from cf_journal_new_transaction() — it is the state to return to.

Section declaration


void cf_declare_section(const char *name, struct cf_section *sec, uint allow_unknown);

Plug another top-level section into the configuration system. name is the name in the configuration file, sec is pointer to the section description. If allow_unknown is set to 0 and a variable not described in sec is found in the configuration file, it produces an error. If you set it to 1, all such variables are ignored.

Please note that a single section definition cannot be used in multiple configuration contexts simultaneously.


void cf_declare_rel_section(const char *name, struct cf_section *sec, void *ptr, uint allow_unknown);

Like cf_declare_section(), but instead of item pointers, the section contains offsets relative to ptr. In other words, it does the same as CF_SECTION, but for top-level sections.


void cf_init_section(const char *name, struct cf_section *sec, void *ptr, uint do_bzero);

If you have a section in a structure and you want to initialize it (eg. if you want a copy of default values outside the configuration), you can use this. It initializes it recursively.

This is used mostly internally. You probably do not need it.

Parsers for basic types

Each of them gets a string to parse and pointer to store the value. It returns either NULL or error message.

The parsers support units. See their list.


char *cf_parse_int(const char *str, int *ptr);

Parser for integers.


char *cf_parse_u64(const char *str, u64 *ptr);

Parser for 64 unsigned integers.


char *cf_parse_double(const char *str, double *ptr);

Parser for doubles.


char *cf_parse_ip(const char *p, u32 *varp);

Parser for IP addresses.

Direct access

Direct access to configuration items. You probably should not need this, but in your do, you have to handle journalling yourself.


#define CF_OPERATIONS T(CLOSE) T(SET) T(CLEAR) T(ALL) \
  T(APPEND) T(PREPEND) T(REMOVE) T(EDIT) T(AFTER) T(BEFORE) T(COPY) T(RESET)

List of operations used on items. This macro is used to generate internal source code, but you may be interested in the list of operations it creates.

Each operation corresponds to the same-named operation described in configuration syntax.


enum cf_operation { CF_OPERATIONS };

Allowed operations on items. See CF_OPERATIONS for list (they have an OP_ prefix — it means you use OP_SET instead of just SET).


char *cf_find_item(const char *name, struct cf_item *item);

Searches for a configuration item called name. If it is found, it is copied into item and NULL is returned. Otherwise, an error is returned and item is zeroed.


char *cf_modify_item(struct cf_item *item, enum cf_operation op, int number, char **pars);

Performs a single operation on a given item.

Debug dumping


void cf_dump_sections(struct fastbuf *fb);

Write the current state of all configuration items into fb.

ucw/getopt.h

This header contains routines for parsing command line arguments and loading the default configuration.

In new programs, please consider using the new option parser instead. The getopt interface is already considered obsolete and may be removed in the future.

Loading by cf_getopt()


extern char *cf_def_file;

The default config (as set by CONFIG_UCW_DEFAULT_CONFIG) or NULL if already loaded. You can set it to something else manually.


extern char *cf_env_file;

Name of environment variable that can override what configuration is loaded. Defaults to CONFIG_UCW_ENV_VAR_CONFIG.


#define CF_SHORT_OPTS   "C:S:"

Short options for loading configuration by cf_getopt(). Prepend to your own options.


#define CF_LONG_OPTS    {"config",      1, 0, 'C'}, {"set",             1, 0, 'S'}, CF_LONG_OPTS_DEBUG

Long options for loading configuration by cf_getopt(). Prepend to your own options.


#define CF_NO_LONG_OPTS (const struct option []) { CF_LONG_OPTS { NULL, 0, 0, 0 } }

Use this constant as long_opts parameter of cf_getopt() if you do not have any long options in your program.


#define CF_USAGE        \
"-C, --config filename\t" CF_USAGE_TAB "Override the default configuration file\n\
-S, --set sec.item=val\t" CF_USAGE_TAB "Manual setting of a configuration item\n" CF_USAGE_DEBUG

This macro provides text describing usage of the configuration loading options. Concatenate with description of your options and write to the user, if he/she provides invalid options.


int cf_getopt(int argc, char * const argv[], const char *short_opts, const struct option *long_opts, int *long_index);

Takes care of parsing the command-line arguments, loading the default configuration file (cf_def_file) and processing configuration options. The calling convention is the same as with GNU getopt_long(), but you must prefix your own short/long options by the CF_LONG_OPTS or CF_SHORT_OPTS or pass CF_NO_LONG_OPTS if there are no long options.

The default configuration file can be overwritten by the --config options, which must come first. During parsing of all other options, the configuration is already available.


void reset_getopt(void);

If you want to start parsing of the arguments from the first one again.

Example

Typically, cf_getopt() is used as follows: it works like the traditional getopt_long() from the C library, but it also handles configuration files.

#include <ucw/lib.h>
#include <ucw/conf.h>
#include <ucw/getopt.h>
static char short_opts[] = CF_SHORT_OPTS "v";
static struct option long_opts[] = {
  CF_LONG_OPTS
  { "verbose", 0, 0, 'v' },
  { NULL, 0, 0, 0 }
};
static int verbose;
int main(int argc, char *argv[]) {
  cf_def_file = "default.cf";
  int opt;
  while((opt = cf_getopt(argc, argv, short_opts, long_opts, NULL)) >= 0)
    switch(opt) {
      case 'v': verbose = 1; break;
      default: fprintf("Unknown option %c\n", opt); return 1;
    }
}

The short_opts and long_opts variables describe the command line arguments. Notice the CF_SHORT_OPTS and CF_LONG_OPTS macros. They add the -S and -C options for the configuration parser as described in [config]. These options are handled internally by cf_getopt().

You can rely on the configuration files having been loaded before the first of your program’s options is parsed.