Dev:Configuration API

= Introduction = Modules can add their own configuration blocks or set::something variables and make full use of UnrealIRCd's parser to acquire this information. You are then responsible for storing this information in variables (eg: some_option = 1) or allocate structs for it (eg: if your module adds multiple xyz { } blocks).

We start by a little introduction to the ConfigEntry * type, quickly move to the testing&running configuration functions with example code and finally explain the various Configuration API functions you can (and should) use, for example to communicate errors or to make parsing easier.

= Structures = In the "config test" and "config run" functions you will, each time, receive some structures which you have to deal with. They are explained below:

ConfigEntry
In your function you receive an ConfigEntry *ce. Below we explain what the members of these structs are. We will also use the following (unusual) configuration block to illustrate: set { this-is-a-variable 1234 { fantastic; superb; }; }

Don't worry if this doesn't make full sense yet. In the sections below you will see example code.

= Flow =

Testing configuration
The HOOKTYPE_CONFIGTEST hook is called when the configuration file 'test' runs. You return an integer to indicate if the configuration file may be loaded or if there was some fatal error, in which case the boot or /REHASH will fail.

You must hook this one from MOD_TEST (if you hook it from MOD_INIT/MOD_LOAD it will never be called): HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, mymod_config_test);

Hook function + example: DLLFUNC int mymod_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs) { int errors = 0; ConfigEntry *cep, *cep2;

/* We filter on CONFIG_SET here, which means we will only get set::something. * You can also filter on CONFIG_MAIN which is to be used if you introduce your * own something { } block outside of a set { } block. */   if (type != CONFIG_SET) return 0;

/* We are only interrested in set::mymod... */   if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "mymod")) return 0; // return 0 to indicate: we don't know / we don't handle this!

for (cep = ce->ce_entries; cep; cep = cep->ce_next) {       if (!cep->ce_varname) {           config_error("%s:%i: blank set::mymod item",                cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; continue; }       else if (!strcmp(cep->ce_varname, "option1")) {           /* set::mymod::option1 */ // do some useful check here... option1_specified = 1; }       else if (!strcmp(cep->ce_varname, "option2")) {           /* set::mymod::option2 */ // do some useful check here... option2_specified = 1; }       else { config_error("%s:%i: unknown directive set::mymod::%s",               cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname); errors++; }   }    *errs = errors; return errors ? -1 : 1; /* we return 1 to say: yup this belongs to us, it's handled/good, and return -1 to indicate we encountered some error */

Checking for missing configuration items
The HOOKTYPE_CONFIGPOSTTEST hook is called after all HOOKTYPE_CONFIGTEST hooks have been called. If you require a specific (mandatory) configuration block or variable then this allows you, with the help of HOOKTYPE_CONFIGTEST earlier, to see if some option was never specified.

Hook it from MOD_TEST: HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, mymod_config_posttest);

Function & example code: DLLFUNC int mymod_config_posttest(int *errs) { int errors = 0; if (!option1_specified) { config_error("set::mymod::option1 missing"); errors++; } if (!option2_specified) { config_error("set::mymod::option2 missing"); errors++; }

Running the configuration
Once the configuration file has passed testing (see previous two hooks) UnrealIRCd will "run" the configuration file. At this point you can no longer "cancel" processing of the configuration file so any options specified are assumed to be valid at this point and you must deal with it.

You add this HOOKTYPE_CONFIGRUN hook from MOD_INIT: HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, mymod_config_run);

Function syntax & continueing with the example from HOOKTYPE_CONFIGTEST and HOOKTYPE_CONFIGPOSTTEST: DLLFUNC int mymod_config_run(ConfigFile *cf, ConfigEntry *ce, int type) { ConfigEntry *cep;

/* We filter on CONFIG_SET here, which means we will only get set::something. * You can also filter on CONFIG_MAIN which is to be used if you introduce your * own something { } block outside of a set { } block. */   if (type != CONFIG_SET) return 0;

/* We are only interrested in set::mymod... */   if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "mymod")) return 0; // if it's anything else: we don't care

for (cep = ce->ce_entries; cep; cep = cep->ce_next) {       if (!strcmp(cep->ce_varname, "option1")) option1 = config_checkval(cep->ce_vardata, CFG_YESNO); else if (!strcmp(cep->ce_varname, "option2")) option2 = atoi(cep->ce_vardata); }   return 1; // we handled it }

= Available functions = The UnrealIRCd configuration API exposes a number of functions which you are recommended to use for parsing, displaying errors, etc.

Parsing functions
To convert a string to an integer you normally use atoi or atol, however in some cases you may want to use a little bit more advanced parsing...

config_checkval
Allows you to convert time, size and boolean values to a 'long': long t = config_checkval(cep->ce_vardata, CFG_TIME); long s = config_checkval(cep->ce_vardata, CFG_SIZE); int bool = config_checkval(cep->ce_vardata, CFG_YESNO);

Logging and error functions
These functions will output the warnings/errors to the ircd.log and send notices to any IRC Operators that are on your server. Therefore, you should use these functions to signal any warnings or errors during configuration parsing.

Note that you should normally only send warnings/errors from the HOOKTYPE_CONFIGTEST and HOOKTYPE_CONFIGPOSTTEST callbacks and never from HOOKTYPE_CONFIGRUN.

config_warn
A configuration warning. Informs the user about something important during parsing. No fatal error, configuration may still be loaded.

Example: config_warn("Missing value XYZ. Assuming default 123.");

config_error
A fatal error which prevents the configuration file from being loaded. Example:

config_error("%s:%i: set::something should be a value between 1 and 20.",   cep->ce_fileptr->cf_filename, cep->ce_varlinenum); Note that you still need to increases the 'errors' variable (errors++) and set it, as pointed out in the example code earlier.

config_error_unknown
If you provide configuration blocks and you encounter an unknown variable then you can use this.

Example, deny version { hdfssfdhsdhds; } will error on the "hdfssfdhsdhds" which is in cep->ce_varname: config_error_unknown(cep->ce_fileptr->cf_filename,       cep->ce_varlinenum, "deny version", cep->ce_varname);

config_is_blankorempty
Returns true if a ConfigEntry is blank or empty (has no variable name or no value).

Example configuration block: me { something; // variable name but no value. };

Example code: for (cep = ce->ce_entries; cep; cep = cep->ce_next) {   if (config_is_blankorempty(cep, "me")) {       errors++; continue; }

Of course sometimes you may want to allow a variable without value because it looks nicer, then simply don't use this function.