parsing config file

A

Aaron Walker

At the beginning of my program, I open a config file and load the contents
into a structure (please disregard the non-portable sockaddr_in struct as it is
irrelevant to the problem):

struct conf_data {
char *config_file;
char *root; /* SERVER_ROOT */
char *pid_file; /* PID_FILE */
char *log_dir; /* LOG_DIR */
struct sockaddr_in local_addr;
unsigned int port; /* PORT */
unsigned short child_min; /* CHILD_MIN */
unsigned short child_max; /* CHILD_MAX */
};
typedef struct conf_data Config;

My config file is in the format of:

KEY1 value1
KEY2 value2
....

Once I have the key and value (per line) I do:

if(!strcmp(key, "SERVER_ROOT")) {
conf->root = ec_malloc(strlen(value) + 1);
strcpy(conf->root, value);
}
else if(!strcmp(key, "PID_FILE")) {
conf->pid_file = ec_malloc(strlen(value) + 1);
strcpy(conf->pid_file, value);
}
....

is there a better way to do this other than a whole sleu of if...else
if's?

I thought about doing:

char *possible_values[] = {
"SERVER_ROOT",
"PID_FILE",
...
NULL
};

for(i = 0; possible_values != NULL; i++) {
if(!strcmp(key, possible_values)) {

but this is where I am uncertain of what to do, because how do I know
which Config structure variable to strcpy value to (for example
SERVER_ROOT needs to reside in conf->root, PID_FILE in conf->pid_file,
etc)?

Sorry for the long post.
Thanks,
Aaron
 
E

Eric Sosman

Aaron said:
At the beginning of my program, I open a config file and load the contents
into a structure (please disregard the non-portable sockaddr_in struct as it is
irrelevant to the problem):

struct conf_data {
char *config_file;
char *root; /* SERVER_ROOT */
char *pid_file; /* PID_FILE */
char *log_dir; /* LOG_DIR */
struct sockaddr_in local_addr;
unsigned int port; /* PORT */
unsigned short child_min; /* CHILD_MIN */
unsigned short child_max; /* CHILD_MAX */
};
typedef struct conf_data Config;

My config file is in the format of:

KEY1 value1
KEY2 value2
...

Once I have the key and value (per line) I do:

if(!strcmp(key, "SERVER_ROOT")) {
conf->root = ec_malloc(strlen(value) + 1);
strcpy(conf->root, value);
}
else if(!strcmp(key, "PID_FILE")) {
conf->pid_file = ec_malloc(strlen(value) + 1);
strcpy(conf->pid_file, value);
}
...

is there a better way to do this other than a whole sleu of if...else
if's?

I thought about doing:

char *possible_values[] = {
"SERVER_ROOT",
"PID_FILE",
...
NULL
};

for(i = 0; possible_values != NULL; i++) {
if(!strcmp(key, possible_values)) {

but this is where I am uncertain of what to do, because how do I know
which Config structure variable to strcpy value to (for example
SERVER_ROOT needs to reside in conf->root, PID_FILE in conf->pid_file,
etc)?


You're on the right track with the table idea, but you
need to store a little more information in the table. Here's
one way:

#include <stddef.h>

static const struct {
const char *conf_key;
size_t conf_off;
} conf_content[] = {
{ "SERVER_ROOT", offsetof(struct conf_data, root) },
{ "PID_FILE", offsetof(struct conf_data, pid_file) },
...
{ NULL, 0 }
};

int i;
char **p;

for (i = 0; conf_content.conf_key != NULL; ++i) {
if (strcmp(key, conf_content.conf_key) == 0) {
p = (char**)((char*)conf + conf_content.conf_off);
if (*p == NULL) {
*p = malloc(strlen(value) + 1);
/* check for malloc() failure here */
strcpy (*p, value);
}
else {
/* we've seen this key before; error? */
}
break;
}
}

Another approach is to use an array with "named" indices
instead of individually-named struct members:

enum conf_keys { SERVER_ROOT = 0, PID_FILE, ..., KEY_COUNT };

static const char *conf_key_strings[KEY_COUNT] = {
"SERVER_ROOT", "PID_FILE", ...
};

struct conf_data {
char *values[KEY_COUNT];
struct sockaddr_in local_addr;
unsigned int port;
...
};

.... and now the index `i' designates both the key value you're
trying to match and the corresponding slot in the `values' array.

In both approaches, you may be wondering what to do with
values that aren't to be stored as strings but need conversion
to `struct sockaddr_in' or `int' or whatever. There are several
ways to handle this, of which the simplest is probably to store
*everything* as a string while scanning the file, and then go
back afterwards to clean up the special cases (this is a good
opportunity, too, to check that nothing's missing).
 
C

Chris Torek

At the beginning of my program, I open a config file ...
[and want to fill in parts of a structure that include]
struct conf_data {
char *config_file;
char *root; /* SERVER_ROOT */
char *pid_file; /* PID_FILE */
char *log_dir; /* LOG_DIR */ [line snipped]
unsigned int port; /* PORT */
unsigned short child_min; /* CHILD_MIN */
unsigned short child_max; /* CHILD_MAX */
Once I have [a] key and value (per line) I do:

if(!strcmp(key, "SERVER_ROOT")) {
conf->root = ec_malloc(strlen(value) + 1);
strcpy(conf->root, value);
}

A side note on style: I much prefer strcmp(a, b) == 0, as the
strcmp() function really returns a string relational result:

result means
----------------- ------
strcmp(a, b) < 0 a < b
strcmp(a, b) == 0 a == b
strcmp(a, b) > 0 a > b

Of course, !strcmp(a,b) means the same thing as strcmp(a,b)==0,
so either way works.
else if(!strcmp(key, "PID_FILE")) {
conf->pid_file = ec_malloc(strlen(value) + 1);
strcpy(conf->pid_file, value);
}
...

is there a better way to do this other than a whole sleu of if...else
if's?

Yes; there are probably dozens of ways, in fact.
I thought about doing:

char *possible_values[] = {
"SERVER_ROOT",
"PID_FILE",
...
NULL
};

for(i = 0; possible_values != NULL; i++) {
if(!strcmp(key, possible_values)) {

but this is where I am uncertain of what to do, because how do I know
which Config structure variable to strcpy value to (for example
SERVER_ROOT needs to reside in conf->root, PID_FILE in conf->pid_file,
etc)?


If *all* results *always* have the same processing requirements
-- in this case, "call ec_malloc to obtain space for the value,
copy the value to the resulting space, and assign that to a variable
of type "char *" -- then this is probably the way to go:

struct string_key_value_pair {
const char *sk_key;
FILL_ME_IN sk_value;
};
/* static const -- if possible */
struct string_key_value_pair possible_values[] = {
{ "SERVER_ROOT", FILL_ME_IN },
{ "PID_FILE", FILL_ME_IN },
...
{ NULL /* , 0 -- if needed/desired */ }
};

Now the problem comes with the "fill me in" parts. Ideally we
would like to have possible_values[0].sk_value be &conf->root,
but this may not be a valid initializer, particularly if the
possible_values[] table is to be "const" (readonly).

There are a number of different answers. One is to put in only
the offset of each field in a "struct conf_data", using offsetof()
and ptrdiff_t values:

struct string_key_value_pair {
const char *sk_key;
ptrdiff_t sk_value;
};

static const struct string_key_value_pair possible_values[] = {
{ "SERVER_ROOT", offsetof(struct conf_data, root) },
{ "PID_FILE", offsetof(struct conf_data, pid_file) },
...
{ NULL, 0 }
};

The code to fill one in then looks like:

for (sk = possible_values; sk->sk_key != NULL; sk++)
if (strcmp(key, sk->sk_key) == 0)
goto found; /* oh no, a goto -- but it gets a lot worse */
error(...);
return FAILED;

found:
s = ec_malloc(strlen(value) + 1);
strcpy(s, value);
*(char **)((char *)conf + sk->sk_value) = s;
return OK;

This last line is seriously ugly. It assumes that sk_value (which
probably should be renamed) is the offset, in bytes, from "conf"
to a field of type "char *". Thus, we convert the value in "conf"
to "char *", and add sk->sk_value to reach the desired field. This
is the (converted) address of an object of type "char *", i.e.,
this address should have had type "char **" in the first place,
except that we have to use byte offset arithmetic to deal with
offsetof(). So next we convert it to "char **", giving us the
address of conf->root (i.e., &conf->root), or the address of
conf->pid_file, or whatever. Finally, we use unary "*" to name
the object itself, cancelling out the "&" in &conf->root or
&conf->pid_file or whatever it is -- which lets us assign "s", the
ec_malloc() result, to that object.

The "goto" can be eliminated, but the ugliness will remain.

Another way to handle this is to build up a single "conf" variable,
whose address can be found at compile-time. If you need multiple
"conf"s you can simply copy a built-up conf to its ultimate
destination after it is built. Here we might have:

static struct conf_data conf; /* the single conf used while parsing */
static const struct string_key_value_pair {
const char *sk_key;
char **sk_value;
} possible_values[] = {
{ "SERVER_ROOT", &conf.root },
{ "PID_FILE", &conf.pid_file },
...
{ NULL, NULL }
};

The "fill one in" code is now just:

if (strcmp(key, sk->sk_key) == 0) {
s = ec_malloc(...); ...
*sk->sk_value = s;
return OK;
}

Yet another way is to parse the configuration file to an intermediate
table (array) of strings, then "manually" decode/assign the strings.
If all but two configuration entries are strings, and one is a
number and another a yes/no flag, this might lead to the simplest
code. Alternatively, a string-to-value table like the above might
contain a keyword, a type-code value, and then a union of pointers
-- but unions can only be initialized sensibly in C99.
 
R

Robert Stankowic

Chris Torek said:
A side note on style: I much prefer strcmp(a, b) == 0, as the
strcmp() function really returns a string relational result:

result means
----------------- ------
strcmp(a, b) < 0 a < b
strcmp(a, b) == 0 a == b
strcmp(a, b) > 0 a > b

Of course, !strcmp(a,b) means the same thing as strcmp(a,b)==0,
so either way works.

[RANT]
This annoys me since some fifteen years. Both !strcmp(a, b); and strcmp(a,
b) == 0 suggest "failing to compare equal" and it is inconsistent with other
string functions like strstr(), strchr() etc. which return NULL if the
substring is _not_ found.
Yes, I know, qsort(), bsearch()... :)
But couldn't K&R have named it strdiff() ?
Just ranting...
[/RANT]

kind regards
Robert
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,598
Members
45,152
Latest member
LorettaGur
Top