Tip of the day: The Security article gives hands-on tips on how to deal with drone attacks, flooding, spammers, (D)DoS and more. |
Dev:Configuration API
Introduction
Modules can make full use of UnrealIRCd's config parser, i.e. custom configuration blocks or set::something
variables can be created and their parameters can be acquired. 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 will start with an introduction to the ConfigEntry *
type, then quickly move to the testing and 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; }; }
Member | Type | Description | Example value |
---|---|---|---|
ce->name | char * | Variable name | "this-is-a-variable" |
ce->value | char * | Variable data | "1234" |
ce->items | ConfigEntry * | Pointer to a child item. (If any, otherwise it will return NULL) | ce->items->name will return "fantastic" |
ce->next | ConfigEntry * | Pointer to the next item. (If any, otherwise it will return NULL) | ce->next will return NULL,
ce->items->next->name will return "superb" |
ce->file | ConfigFile * | Pointer to the configuration file item.
Any file item other than filename (see next) is hardly ever used. |
- |
ce->file->filename | char * | Pointer to the filename containing the configuration block or variable. | "unrealircd.conf" |
ce->line_number | int | Number of line for variable / configuration block. | 2 |
Don't worry if this doesn't make full sense yet. In the sections below you will see example code.
Flow
Testing configuration
To add custom blocks to the configuration file and acquire their parameters, you must add the HOOKTYPE_CONFIGTEST
hook in MOD_TEST
of your module. (This feature will never be called if you hook it from MOD_INIT
or MOD_LOAD
.) An integer will be returned to indicate if the configuration file may be loaded or if there would be some fatal errors, in which the boot or /REHASH will fail.
HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, mymod_config_test);
Example code:
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->name || strcmp(ce->name, "mymod")) return 0; // return 0 to indicate: we don't know / we don't handle this! for (cep = ce->items; cep; cep = cep->next) { if (!cep->name) { config_error("%s:%i: blank set::mymod item", cep->file->filename, cep->line_number); errors++; continue; } else if (!strcmp(cep->name, "option1")) { /* set::mymod::option1 */ // do some useful check here... option1_specified = 1; } else if (!strcmp(cep->name, "option2")) { /* set::mymod::option2 */ // do some useful check here... option2_specified = 1; } else { config_error("%s:%i: unknown directive set::mymod::%s", cep->file->filename, cep->line_number, cep->name); 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
NOTE: This step is not required if your module always provides default values, in which case you simply don't use this hook.
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:
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:
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->name || strcmp(ce->name, "mymod")) return 0; // if it's anything else: we don't care for (cep = ce->items; cep; cep = cep->next) { if (!strcmp(cep->name, "option1")) option1 = config_checkval(cep->value, CFG_YESNO); else if (!strcmp(cep->name, "option2")) option2 = atoi(cep->value); } 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->value, CFG_TIME); long s = config_checkval(cep->value, CFG_SIZE); int bool = config_checkval(cep->value, CFG_YESNO);
Cfg type | What for | Example string | Example result |
---|---|---|---|
CFG_TIME | Time | "7d10h5s" | 7*86400 + 10*3600 + 5 |
CFG_SIZE, | Size | "1M" | 1048576 (or 1000000? did not check..) |
CFG_YESNO | Boolean | "yes", "no", "true", "false", "1", "0" | 1 or 0 |
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->file->filename, cep->line_number);
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->name:
config_error_unknown(cep->file->filename, cep->line_number, "deny version", cep->name);
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->items; cep; cep = cep->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.