qLibc
qaconf.c File Reference

Apache-style configuration file parser. More...

Go to the source code of this file.

Functions

qaconf_t * qaconf (void)
 Create a new configuration object.
static int addoptions (qaconf_t *qaconf, const qaconf_option_t *options)
 qaconf_t->addoptions(): Register option directives.
static void setdefhandler (qaconf_t *qaconf, qaconf_cb_t *callback)
 Set default callback function.
static void setuserdata (qaconf_t *qaconf, const void *userdata)
 qaconf_t->setuserdata(): Set userdata which will be provided on callback.
static int parse (qaconf_t *qaconf, const char *filepath, uint8_t flags)
 qaconf_t->parse(): Run parser.
static const char * errmsg (qaconf_t *qaconf)
 qaconf_t->errmsg(): Get last error message.
static void reseterror (qaconf_t *qaconf)
 qaconf_t->reseterror(): Clear error message.
static void free_ (qaconf_t *qaconf)
 qaconf_t->free(): Release resources.

Detailed Description

Apache-style configuration file parser.

Apache-style Configuration is a configuration file syntax and format originally introduced by Apache HTTPd project. This format is powerful, flexible and human friendly. Even though this code gets distributed as a part of qLibc project, the code is written not to have any external dependencies to make this single file stands alone for better portability. It is purely written from the ground up and dedicated to the public by Seungyoung Kim.

Sample Apache-style Configuration Syntax:

# Lines that begin with the hash character "#" are considered comments.
Listen 53
Protocols UDP TCP
IPSEC On
<Domain "qdecoder.org">
TTL 86400
MX 10 mail.qdecoder.org
<Host mail>
IPv4 192.168.10.1
TXT "US Rack-13D-18 \"San Jose's\""
</Host>
<Host www>
IPv4 192.168.10.2
TXT 'KR Rack-48H-31 "Seoul\'s"'
TTL 3600
</Host>
</Domain>
<Domain "ringfs.org">
<Host www>
CNAME www.qdecoder.org
</Host>
</Domain>
// THIS EXAMPLE CODE CAN BE FOUND IN EXAMPLES DIRECTORY.
// Define scope.
// QAC_SCOPE_ALL and QAC_SCOPE_ROOT are predefined.
// Custom scope should be defined from 2(1 << 1).
// Note) These values are ORed(bit operation), so the number should be
// 2(1<<1), 4(1<<2), 6(1<<3), 8(1<<4), ...
enum {
OPT_SECTION_ALL = QAC_SECTION_ALL, // pre-defined
OPT_SECTION_ROOT = QAC_SECTION_ROOT, // pre-defined
OPT_SECTION_DOMAIN = (1 << 1), // user-defined section
OPT_SECTION_HOST = (1 << 2), // user-defined section
};
// Define callback proto-types.
static QAC_CB(confcb_debug);
// Define options and callbacks.
static qaconf_option_t options[] = {
{"Listen", QAC_TAKE_INT, confcb_debug, 0, OPT_SECTION_ALL},
{"Protocols", QAC_TAKEALL, confcb_debug, 0, OPT_SECTION_ROOT},
{"IPSEC", QAC_TAKE_BOOL, confcb_debug, 0, OPT_SECTION_ROOT},
{"Domain", QAC_TAKE_STR, confcb_debug, OPT_SECTION_DOMAIN, OPT_SECTION_ROOT},
{ "TTL", QAC_TAKE_INT, confcb_debug, 0, OPT_SECTION_DOMAIN | OPT_SECTION_HOST},
{ "MX", QAC_TAKE2 | QAC_A1_INT, confcb_debug, 0, OPT_SECTION_DOMAIN},
{ "Host", QAC_TAKE_STR, confcb_debug, OPT_SECTION_HOST, OPT_SECTION_DOMAIN},
{ "IPv4", QAC_TAKE_STR, confcb_debug, 0, OPT_SECTION_HOST},
{ "TXT", QAC_TAKE_STR, confcb_debug, 0, OPT_SECTION_HOST},
{ "CNAME", QAC_TAKE_STR, confcb_debug, 0, OPT_SECTION_HOST},
QAC_OPTION_END
};
int user_main(void)
{
// Create a userdata structure.
struct MyConf myconf;
// Initialize and create a qaconf object.
qaconf_t *conf = qaconf();
if (conf == NULL) {
printf("Failed to open '" CONF_PATH "'.\n");
return -1;
}
// Register options.
conf->addoptions(conf, options);
// Set callback userdata
// This is a userdata which will be provided on callback
conf->setuserdata(conf, &myconf);
// Run parser.
int count = conf->parse(conf, CONF_PATH, QAC_CASEINSENSITIVE);
if (count < 0) {
printf("Error: %s\n", conf->errmsg(conf));
} else {
printf("Successfully loaded.\n");
}
// Verify userdata structure.
if (conf->errmsg(conf) == NULL) { // another way to check parsing error.
// codes here
}
// Release resources.
conf->free(conf);
}
static QAC_CB(confcb_debug)
{
int i;
for (i = 0; i < data->level; i++) {
printf (" ");
}
// Print option name
if (data->otype == QAC_OTYPE_SECTIONOPEN) {
printf("<%s>", data->argv[0]);
} else if (data->otype == QAC_OTYPE_SECTIONCLOSE) {
printf("</%s>", data->argv[0]);
} else { // This is QAC_OTYPE_OPTION type.
printf("%s", data->argv[0]);
}
// Print parent names
qaconf_cbdata_t *parent;
for (parent = data->parent; parent != NULL; parent = parent->parent) {
printf(" ::%s(%s)", parent->argv[0], parent->argv[1]);
}
// Print option arguments
for (i = 1; i < data->argc; i++) {
printf(" [%d:%s]", i, data->argv[i]);
}
printf("\n");
// Return OK
return NULL;
}
qaconf_t * qaconf(void)
Create a new configuration object.
Definition qaconf.c:245
[Output]
Listen [1:53]
Protocols [1:UDP] [2:TCP]
IPSEC [1:1]
<Domain> [1:qdecoder.org]
TTL ::Domain(qdecoder.org) [1:86400]
MX ::Domain(qdecoder.org) [1:10] [2:mail.qdecoder.org]
<Host> ::Domain(qdecoder.org) [1:mail]
IPv4 ::Host(mail) ::Domain(qdecoder.org) [1:192.168.10.1]
TXT ::Host(mail) ::Domain(qdecoder.org) [1:US Rack-13D-18 "San Jose's"]
</Host> ::Domain(qdecoder.org) [1:mail]
<Host> ::Domain(qdecoder.org) [1:www]
IPv4 ::Host(www) ::Domain(qdecoder.org) [1:192.168.10.2]
TXT ::Host(www) ::Domain(qdecoder.org) [1:KR Rack-48H-31 "Seoul's"]
TTL ::Host(www) ::Domain(qdecoder.org) [1:3600]
</Host> ::Domain(qdecoder.org) [1:www]
</Domain> [1:qdecoder.org]
<Domain> [1:ringfs.org]
<Host> ::Domain(ringfs.org) [1:www]
CNAME ::Host(www) ::Domain(ringfs.org) [1:www.qdecoder.org]
</Host> ::Domain(ringfs.org) [1:www]
</Domain> [1:ringfs.org]
Successfully loaded.

Definition in file qaconf.c.

Function Documentation

◆ qaconf()

qaconf_t * qaconf ( void )

Create a new configuration object.

Returns
pointer to new qaconf_t object.
qaconf_t *conf = qaconf();
if (conf == NULL) {
// Insufficient memory.
}

Definition at line 245 of file qaconf.c.

◆ addoptions()

int addoptions ( qaconf_t * qaconf,
const qaconf_option_t * options )
static

qaconf_t->addoptions(): Register option directives.

Parameters
qaconfqaconf_t object.
optionsarray pointer of qaconf_option_t.
Returns
a number of options registered(added).
qaconf_option_t options[] = {
{"Listen", QAC_TAKE_INT, confcb_debug, 0, OPT_SECTION_ALL},
{"Protocols", QAC_TAKEALL, confcb_debug, 0, OPT_SECTION_ROOT},
{"IPSEC", QAC_TAKE_BOOL, confcb_debug, 0, OPT_SECTION_ROOT},
{"Domain", QAC_TAKE_STR, confcb_debug, OPT_SECTION_DOMAIN, OPT_SECTION_ROOT},
{ "TTL", QAC_TAKE_INT, confcb_debug, 0, OPT_SECTION_DOMAIN | OPT_SECTION_HOST},
{ "MX", QAC_TAKE2 | QAC_A1_INT, confcb_debug, 0, OPT_SECTION_DOMAIN},
{ "Host", QAC_TAKE_STR, confcb_debug, OPT_SECTION_HOST, OPT_SECTION_DOMAIN},
{ "IPv4", QAC_TAKE_STR, confcb_debug, 0, OPT_SECTION_HOST},
{ "TXT", QAC_TAKE_STR, confcb_debug, 0, OPT_SECTION_HOST},
{ "CNAME", QAC_TAKE_STR, confcb_debug, 0, OPT_SECTION_HOST},
QAC_OPTION_END
};
// Register options.
qaconf_t *conf = qaconf();
conf->addoptions(conf, options);
(...codes goes...)

It takes an array of options as provided in the sample codes. Each option consists of 5 parameters as below

1st) Option Name : A option directive name.
2nd) Arguments : A number of arguments this option takes and their types.
3rd) Callback : A function pointer for callback.
4th) Section ID : Section ID if this option is a section like <Option>.
Otherwise 0 for regular option.
5th) Sections : ORed section IDs where this option can be specified.

Example:

{"TTL", QAC_TAKE_INT, confcb_debug, 0, OPT_SECTION_DOMAIN | OPT_SECTION_HOST}
1st) Option name is "TTL"
2nd) It takes 1 argument and its type must be integer.
3rd) Callback function, confcb_debug, will be called.
4th) This is a regular option and does not have section id.
5th) This option can be specified in OPT_SECTION_DOMAIN and OPT_SECTION_HOST.

OPTION NAME field:

Option name is a unique string. Even when an option is a section type like <option>, only the name part without brackets needs to be specified.

ARGUMENT field:

This field provides argument checking at the parser level, so the user's callback routine can stay simple. It checks the number of arguments this option can take and their argument types.

There are 4 argument types, as shown below. And first 5 arguments can be checked individually with different types.

STR type : any type
INT type : integer type. ex) 23, -12, 0
FLOAT type : integer + floating point type. ex) 1.32, -32.5, 23, -12, 0
BOOL type : bool type ex) 1/0, true/false, on/off, yes/no

When a BOOL type is specified, the argument passed to the callback will be replaced with "1" or "0" for convenience. For example, if "On" is specified as an argument and BOOL type checking is enabled, the actual argument passed to the callback will be "1". So we can simply determine it like "bool enabled = atoi(data->argv[1])".

If the original input argument needs to be passed to the callback, specify STR type.

Here are some examples of how to specify the "Arguments" field.

An option takes 1 argument.
QAC_TAKE_STR <= String(any) type
QAC_TAKE_INT <= Integer type
QAC_TAKE_FLOAT <= Float type
QAC_TAKE_BOOL <= Bool type
QAC_TAKE1 <= Equivalent to QAC_TAKE_STR
QAC_TAKE1 | QAC_A1_BOOL <= Equivalent to QAC_TAKE_BOOL
An option takes 2 arguments, bool and float.
QAC_TAKE2 | QAC_A1_BOOL | QAC_A2_FLOAT
An option takes any number of arguments in any type.
QAC_TAKEALL
An option takes any number of arguments but 1st one must be bool and
2nd one must be integer and rest of them must be float.
QAC_TAKEALL | QAC_A1_BOOL | QAC_A2_INT | QAC_AA_FLOAT

CALLBACK field:

User defined callback function. We provide a macro, QAC_CB, for function proto type. Always use QAC_CB macro.

QAC_CB(sample_cb) {
(...codes...)
}
is equivalent to
char *sample_cb(qaconf_cbdata_t *data, void *userdata) {
(...codes...)
}

Callback function will be called with 2 arguments. One is callback data and the other one is userdata. Userdata is the data pointer set by setuserdata().

Here is data structure. Arguments belong to the option can be accessed via argv variables like data->argv[1]. argv[0] is for the option name.

struct qaconf_cbdata_s {
enum qaconf_otype otype; // option type
uint64_t section; // current section where this option is located
uint64_t sections; // ORed all parent's sectionid(s) including current sections
uint8_t level; // number of parents(level), root level is 0
qaconf_cbdata_t *parent; // upper parent link
int argc; // number arguments. always equal or greater than 1.
char **argv; // argument pointers. argv[0] is option name.
}

SECTION ID field:

If an option is a section like <Option>, a section ID can be assigned. This section ID can be used to limit some other option directives so they are located only inside that section. This is optional. If you do not need to check directory scope, you can simply specify 0 here.

There are 2 predefined section IDs: QAC_SECTION_ALL and QAC_SECTION_ROOT. User-defined section IDs should start from 1 << 1, as shown below.

enum {
OPT_SECTION_ALL = QAC_SECTION_ALL, // pre-defined
OPT_SECTION_ROOT = QAC_SECTION_ROOT, // pre-defined
OPT_SECTION_DOMAIN = (1 << 1), // user-defined section
OPT_SECTION_HOST = (1 << 2), // user-defined section
};

Please note that these section IDs are ORed together. The values should be assigned as bit flags, such as 2 (1 << 1), 4 (1 << 2), 8 (1 << 3), ...

SECTION IDS field:

This field is to limit the scope where an option is allowed to be specified. Multiple section IDs can be ORed.

QAC_SECTION_ALL means an option can appear anywhere.

QAC_SECTION_ROOT means an option can appear only at the top level, not inside any section.

Definition at line 439 of file qaconf.c.

◆ setdefhandler()

void setdefhandler ( qaconf_t * qaconf,
qaconf_cb_t * callback )
static

Set default callback function.

Default callback function will be called for unregistered option directives. QAC_IGNOREUNKNOWN flag will be ignored when default callback has set.

Parameters
qaconfqaconf_t object.
callbackcallback function pointer

Definition at line 471 of file qaconf.c.

◆ setuserdata()

void setuserdata ( qaconf_t * qaconf,
const void * userdata )
static

qaconf_t->setuserdata(): Set userdata which will be provided on callback.

Parameters
qaconfqaconf_t object.
userdatapointer to userdata.
// Define an example userdata
struct MyConf {
int sample;
};
int user_main(void) {
struct MyConf myconf;
(...codes...)
// Set callback userdata.
conf->setuserdata(conf, &myconf);
(...codes...)
}
QAC_CB(confcb_callback_func) {
(...codes...)
// Type casting userdata for convenient use.
struct MyConf *myconf = (struct MyConf *)userdata;
myconf->sample++;
(...codes...)
return NULL;
}
static void setuserdata(qaconf_t *qaconf, const void *userdata)
qaconf_t->setuserdata(): Set userdata which will be provided on callback.
Definition qaconf.c:507

Definition at line 507 of file qaconf.c.

◆ parse()

int parse ( qaconf_t * qaconf,
const char * filepath,
uint8_t flags )
static

qaconf_t->parse(): Run parser.

Parameters
qaconfqaconf_t object.
filepathconfiguration file path.
flagsparser options. (0 for default)
Returns
A number of option directives parsed. -1 will be returned in case of error.

Here is a list of flags. Multiple flags can be ORed.

QAC_CASEINSENSITIVE: Option name is case-insensitive.

QAC_IGNOREUNKNOWN : Ignore unknown option directives. This flag will be ignored if setdefhandler() has set.

int c;
c = conf->parse(conf, "sm1.conf", 0);
c = conf->parse(conf, "sm2.conf", QAC_CASEINSENSITIVE);
c = conf->parse(conf, "sm3.conf", QAC_CASEINSENSITIVE | QAC_IGNOREUNKNOWN);

Definition at line 535 of file qaconf.c.

◆ errmsg()

const char * errmsg ( qaconf_t * qaconf)
static

qaconf_t->errmsg(): Get last error message.

Parameters
qaconfqaconf_t object.
Returns
A const pointer of error message string.
int c = conf->parse(conf, "sample.conf", 0);
if (c < 0) {
// ERROR
printf("%s\n", conf->errmsg(conf));
}

Definition at line 573 of file qaconf.c.

◆ reseterror()

void reseterror ( qaconf_t * qaconf)
static

qaconf_t->reseterror(): Clear error message.

Parameters
qaconfqaconf_t object.
conf->reseterror(conf);
conf->parse(conf, "sample.conf", 0);
if (conf->errmsg(conf) != NULL) {
// ERROR
}

Definition at line 590 of file qaconf.c.

◆ free_()

void free_ ( qaconf_t * qaconf)
static

qaconf_t->free(): Release resources.

Parameters
qaconfqaconf_t object.
conf->free(conf);

Definition at line 606 of file qaconf.c.