Configuration Handlers
Modules can be used to extend the functionality of the server; often that
additional functionality is configurable, and can behave differently according
to the server administrator's needs. In order to allow the administrator
the ability to tweak the module functionality behavior, the module often
needs to add its own configuration directives. Thus, one of the common
tasks of the module developer is the writing of a function that handles
a configuration directive, checking it for format, syntax, and sanity.
The typical directive handler, depending on its complexity, will allocate any
necessary resources and perform any needed computations on the directive
arguments. Last, a configuration record containing the processed information
will be created, and stored for later retrieval.
The steps taken during the handling of a configuration directive are straightforward:
Useful functions and macros for configuration handlers
To help facilitate checking the number of parameters given for the
configuration directive there is the
CHECK_ARGS
macro.
This macro takes the cmd_rec *
passed to the configuration
directive handler function, and the number of arguments that directive
should have. Note, however, that the current macro is inadequate, as
it only checks that there are at least the requested number of
arguments but does not produce an error if there are more than the
requested number of arguments used. Other, less frequently used macros
for the checking of number of parameters are
CHECK_HASARGS
and
CHECK_VARARGS
.
Thus, for example, to check that your directive is used with at least
two parameters:
CHECK_ARGS(cmd, 2);
As with checking the number of parameters, there is a convenience macro
used to check the context in which your directive was used against a list
of legal contexts:
CHECK_CONF
. This
macro takes two arguments: the cmd_rec *
passed to the
configuration directive handler, and an OR
'd list of context
values (see the Internals page on
contexts). For example, if your
directive should only be used in the <VirtualHost>
and
<Directory>
contexts:
CHECK_CONF(cmd, CONF_VIRTUAL|CONF_DIR);
Now we come to the substance of the configuration handler: parsing the parameters given for proper format, syntax, range, and suitability. What the proper format, syntax, range, and suitability are depends on the purpose of your directive, what kind of parameters it should be accepting, and how those parameters will be used later by the module code.
First, there's the matter of accessing the parameters that appeared in the
configuration file. A configuration directive handler accepts a single
argument, a cmd_rec *
. The parameter values for the directive
are stored in this structure in a manner similar to what is passed by the
operating system to any C program's main()
function: an
int argc
denoting the number of arguments, and a
char **argv
containing the array of parameters as strings. In
the given cmd_rec *
, then, cmd->argc
is the number
of parameters used including the directive name itself, and
cmd->argv
are those parameters, also including the directive
name itself (cmd->argv[0]
).
cmd->argv[cmd->argc]
is always NULL
.
One common type of configuration directive is a simple on/off Boolean
switch. For parsing a parameter that should be a Boolean value, there is the
get_boolean()
function. Many places in the server's core configuration directives will
have:
int bool = -1; ... bool = get_boolean(cmd, 1); if (bool == -1) CONF_ERROR(cmd, "expected Boolean value");If the parameter should be a numeric value, make sure that given parameter is indeed a number (use
strtol()
or strtoul()
for their
range-checking abilities as well as string-to-number conversion ability), and
that the number is within the proper range: is it negative or zero when it
should only be positive? Is it smaller than the largest possible value in
the parameter range? If the parameter value should be a path, check that
the given string is an absolute path, if required, and/or that the path
exists, and leads to a file of the correct type (eg directory,
regular file, symbolic link, whatever). Try to verify and validate as
much as possible at this stage, for it will save you time, debugging, and
error-handling code elsewhere in the module code.
If, during the handling of this directive, a fatal or irrecoverable error
is detected in the directive parameters, use the
CONF_ERROR
macro
to error out of the configuration handler.
Storing configuration parameters
Once the parameters have been processed, the handler is ready to create
and populate a
config_rec
for
storing the parsed data. The parsing of the configuration data occurs when
the server starts up, and when it is restarting (as when signalled with a
SIGHUP
). How then to store configuration parameters in a
persistent place, to be looked up later when a command handler, or an
authentication handler, is called? That persistent place turns out to be a
bag of configuration records, called config_rec
s, stored in
hierarchical sets. This structure allows for easy retrieval of a record using
a string as the key. So, the module configuration handlers simply need to
know how to add their configuration data to this structure.
The basic function to use is
add_config_param()
. This function creates a config_rec
and stores it in the
structure according to the server and context in which the directive occurred.
This function stores void pointers, so any arbitrary data can be stored, via
pointer, in the created config_rec
. If the parameter data to
be stored will be all strings, as is common, then a similar storage function,
add_config_param_str()
should be used. This second function calls
pstrdup()
on each
of its arguments, assuming they are strings (and that assumption had better
be a valid assumption!). This way you don't have to worry about
figuring out which pool to use for allocating space for those strings.
Knowing which pools to use, and their various lifetimes, is discussed
here.
In much of the current code, the first argument to either of these functions
is a string literal, usually the name of the configuration directive that
trigged the use of the configuration handler. However, another way (and
slightly more efficient, as it uses less stack memory) is to use
cmd->argv[0]
, which points to the same string.
Configuration flags
One thing to bear in mind when storing configuration parameters is the
contexts in which they will be retrieved. Is the directive allowed only
in the server contexts of "server config",
<VirtualHost>
, or <Global>
? Or will it
be useful in the <Anonymous>
context as well? Maybe it
pertains to directories, and so will be in <Directory>
and .ftpacces
contexts? By adding the
CF_MERGEDOWN
flag to the config_rec
returned by
add_config_param()
or add_config_param_str()
, we
can tell the server that this config_rec
should be copied and
"merged" into all lower contexts until it either hits a
configuration record with the same name or bottoms out.
Example of what the configuration tree looks like, without
CF_MERGEDOWN
, for a fictitious FooBarDirective
,
assuming that directive appeared within an <Anonymous>
context in the configuration file:
<VirtualHost> |----------\ <Anonymous> | - FooBarDirective <------- configuration placed here |-----------\ <Directory> <------- configuration doesn't apply here |-----------\ <Limit> <--- or here....
If, on the other hand, the CF_MERGEDOWN
flag is set by the
configuration handler, like so:
config_rec *c = NULL; ... c = add_config_param("FooBarDirective", 1, (void *) foo); c->flags |= CF_MERGEDOWN;then the configuration tree would look like:
<VirtualHost> |----------\ <Anonymous> | - FooBarDirective <------- configuration placed here |-----------\ <Directory> <------- mergedown means it is also placed here | - FooBarDirective |-------------\ <Limit> <-------- and here... | - FooBarDirective
Multiple configuration records
There is nothing to prevent a given configuration directive from occurring
multiple times in the configuration file. Indeed, for some configuration
directives this is the desired behavior. Each time a configuration directive
is encountered, the configuration handler for that directive is called.
Most configuration handlers do not need to do anything special to handle
multiply configured directives; should that directive be only used once,
however, the configuration handler should enforce this.
As most configuration handler functions call add_config_param()
or add_config_param_str()
, a developer might think that a
directive that occurs multiple times might overwrite the previously set
config_rec
. However, a new config_rec
is generated
and inserted into a xaset_t
, which can handle configuration records
of the same, each time these parameter-storing functions are called.
Finally, if everything is in order, the configuration handler should signal
to the parsing engine that the directive was successfully processed, using
the
HANDLED
macro.
Example handler
To demonstrate all of this in action, here's the configuration handler for the
RootLogin
directive, a configuration directive that accepts only
a single Boolean parameter, as it currently exists in the core code:
MODRET set_rootlogin(cmd_rec *cmd) {
int bool;
config_rec *c;
/* Make sure there's at least one parameter */
CHECK_ARGS(cmd, 1);
/* Check that the directive occurs in a legal context */
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_ANON|CONF_GLOBAL);
/* Make sure a correct Boolean parameter was given, and return
* an informative error message if not.
*/
bool = get_boolean(cmd, 1);
if (bool == -1)
CONF_ERROR(cmd, "expected Boolean parameter");
/* Allocate a config_rec
. The NULL is just a temporary placeholder. */
c = add_config_param(cmd->argv[0], 1, NULL);
/* Rather than casting the Boolean variable, an int, into the space of a void pointer,
* allocate the space for the Boolean manually, from the config_rec's own pool.
*/
c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
/* Now store the Boolean parameter. */
*((unsigned char *) c->argv[0]) = (unsigned char) bool;
/* Since RootLogin is allowed in the <Anonymous> context, we
* need to set CF_MERGEDOWN.
*/
c->flags |= CF_MERGEDOWN;
/* This directive has been successfully handled */
return HANDLED(cmd);
}
Parameter retrieval
While discussion of parameter retrieval does not properly belong in a
discussion of configuration handlers, it does follow the discussion of
parameter storage functions. The way in which these records are retrieved
needs to be handled carefully. The retrieval/lookup functions are:
find_config()
find_config_next()
get_param_int()
get_param_int_next()
get_param_ptr()
get_param_ptr_next()
So how does the module developer know when to use which retrieval functions?
It depends on what kind of information is stored in the record being retrieved.
If that record only holds one parameter, a number or a string or other type
of pointer, then the get_param
functions can be used. The
get_param_int()
function, despite its name, returns as a
long
value the first parameter in the matched record (available
as config_rec->argv[0]
). get_param_int_next()
can then be used to find and return the value of the next matching record.
Similarly with the get_param_ptr()
and
get_param_ptr_next()
, excepting that they return
void *
s rather than long
s. The
get_param_ptr()
function is often used to retrieve configuration
data stored as a string.
For more complicated config_rec
s, such as those that hold multiple
pointers and data such as ACLs, the find_config()
and
find_config_next()
are necessary. These functions return pointers
to the matched configuration record itself, rather than just the first
argument of that record. Any config_rec
stored with more than
one parameter will need to be accessed via these functions.
The next consideration is use of the correct configuration set in which to
look for the desired records. Which configuration set to use depends on the
directive and the configuration contexts in which it is allowed to appear.
For example, there are some configuration directives (e.g.
DefaultRoot
) that are only allowed to appear in the server
contexts of "server config", <VirtualHost>
, and
<Global>
. This means that that configuration record will
always be in main_server->conf
, the top-level set for the
server handling the current session. If the directive may appear in the
<Anonymous>
context, use the CF_MERGEDOWN
flag and the
TOPLEVEL_CONF
macro as the search set, as it will expand appropriately to the correct set
to use. The mergedown flag, in this case, is necessary to have a directive
that appears in a context "higher" than
<Anonymous>
appear in that context in the configuration
tree as well. Confused yet? For those directives that should cascade down
through every set, use the CF_MERGEDOWN
flag, and use the
CURRENT_CONF
macro for the search set.
Failure to use the CF_MERGEDOWN
flag when necessary, or searching
in the wrong set, leads to "mergedown" bugs, and settings not
taking effect when they should.
Example retrieval code
Here's an example of a retrieval loop than can be used to deal with directives
that can be used multiple times:
config_rec *c = NULL; ... /* See if the DefaultRoot directive has been used */ c = find_config(main_server->conf, CONF_PARAM, "DefaultRoot", FALSE); while (c) { /* Do some processing of the retrieved record here */ ... /* Search for the next occurrence */ c = find_config_next(c, c->next, CONF_PARAM, "DefaultRoot", FALSE); }
Obligations
The last duty of the module developer, and the most important duty (from a
user's point of view), is to document what has been developed. The existing
configuration directive documentation should suffice as a template.
Please do everyone a favor and distribute documentation with your
module, so that it can be used without requiring users to browse and decipher
the source code.