==============================================================================
C-Scene Issue #3
Portable Program Configuration Files
Kerry D. Mathews
==============================================================================
Have you ever looked at the initialization files from other programs and said: "I wish my Linux app could read init files like that?"

Well, hold your breath no longer.

In this short article, I'll go over some code that will make it possible. I've made a single function that will perform reading the configuration file, but not write. The reason is i felt it was unneccesary overhead to launch a program just to modify its configuration file. In my approach, I can use any ordinary text editor (e.g. vim), after all why re-invent the wheel.

The code, for this function, will essentially search a file for a KEYWORD and then grab corresponding VALUE.

Requirements: A function that takes two parameters, a filename, keyword, and retrieves a corresponding value. Returning an appropriate execution status (error value) is also needed.

First ponder the reasons for having to read values from a file. (hmm..)

Well, it:

  1. Avoids passing parameters on the command line.
  2. It also avoids having to hardcode values in the executable.
  3. And thirdly, it is very ergonomic to the administrator of that program.

For the rest of this article, I'll refer to the parameter file as: the Config File. But please remember it fills the role of an init file and a runtime parameter file.

Minor requirements are:

Enough requirements. We are now ready to examine the Config file.

Again, I do things as simple as possible. I only expect to get a string value from the Config file. You can take the code and modify it to your requirements. A single entry in the Config file cannot span more (or less) than 1 line. Entries into the Config file should look like "{keyword}={value}\n" ... in psuedo code of course.

Valid entries in a Config File should look like:

    scanner1=/dev/tty3a

    WATERHOSE=199.200.11.31

    dinner_time=11pm GMT

    Colonels_Secret_Ingredient=salt
That looks good. Easy enough for my seven year old daughter to maintain. :)

The returned value is a NULL terminated string containing all the characters after the first "=" and up to, but not including, the '\n'.

From the examples above we can say:

For keyword: [WATERHOSE] the value is: [199.200.11.31]
For keyword: [dinner_time] the value is: [11pm GMT]

We can conclude that spaces, tabs, and other characters will be included in the value variable.

I am satisfied with the Config File and the keyword/value synergy. Let's look at the code next.

The name and prototype of our new found functions is:


int read_config_var( char *values_file, char *keyword , char value[] );

The char *values_file is the full path name of the Config File.
The char *keyword is the keyword.
The char value[] is an array that is filled by read_config_var().

The return values are:

Value could be a char *, but i decided against that. I used an array, because of its fixed length nature.

The meat of our function is below. You can see that it uses fgets() to fill the variable str. (oh.. yuck, the length is hardcoded) You can make your own judgements on that call. Then a string comparison between the variables 'str' and 'keyword'. If the keyword is found in str, then sprintf() is used to firmly place that value in the variable 'value'.


 for(;;)
 {
 fgets(str, 80, _file);
 if( ferror(_file) || feof(_file) ) return(UGLY);
 len = strlen(str);
 if( strncmp(keyword, str, strlen(keyword)) == 0 )
 {
 if (str[len - 1] == '\n') str[--len] = 0;
 sprintf(value, "%s", &str[strlen(keyword)+1] ); break;
 }
 }


One glaring weak point is that hardcoded fgets parameter. If the value of that exceeds that of the dimension of value[], bad things will happen. If you don't need anything over 80 characters, then your safe. But, if you do ensure that value[] and str[] and the fgets(%,max_length,%) are all the same length. A worldly global can be useful in its place, like FILENAME_MAX or UCHAR_MAX.

OK. We are nearly done. The last topic is implementation. Below is an example of how our function can be used.



int main( int argc, char *argv[] )
{
char * v_file = "./Secret_Addresses";
char * keyword = "Kurt Cobain";
char value[80] ;

 switch( read_config_var(v_file, keyword, value) )
 {
 case 0:
 printf("\nValue for %s in %s is %s\n", keyword, v_file, value);
 break;
 case -1:
 printf("\nFile Error for [%s] \n", v_file); break;
 case -2:
 printf("\nBad User Parm for [%s] \n", keyword); break;
 default:
 printf("\nUnknown Error Occurred \n"); break;
 }
return 0;
}

A couple of quick notes on the Config File:
  1. There is no restriction on the name or location of the Config File.
  2. The attributes of the Config File, I suggest, should be world readable and only writable by the program administrator.
  3. For the location of the Config File, I suggest using paths like:
    /usr/local/bin/program_name/program_name.cfg
    or
    /etc/conf.program_name

Bonus Notes

In earlier articles, the topic of daemons came up. Well written daemons will have startup and shutdown functions. The startup functions, should, read from configuration files and implement those values. A smart approach to a well written daemon, is to re-read the configuration file when a certain signal is raised (i.e. SIGUSR1). That way, you proggie need not be stopped, just because you changed parameters. (nifty?)

Below is a sample snippet:


struct sigaction SignalAct;

SignalAct.sa_handler = SigCatch;
sigemptyset( &SignalAct.sa_mask );
SignalAct.sa_flags = 0;

void SigCatch(int sig)
{
char * funct_name = "SigCatch";

 if (sig == SIGUSR1) /* re - initialize daemon */
 {
 shutdown_gracefully();
 sleep(3);
 init_system();
 }

} /* end SigCatch */

There you have a very handy function for program configurability. It is so conveinent you may want to include it in your utility library. Another thought, you may want to develop a corresponding write function.

Additionally; I claim no ownership, rights, or responsibilities for this code.

Final Notes: The source code for this function and examples is in values.zip. Compile by using: cc -o main main.c values.c This was written for the 2.7.2.1 gcc compiler on the 2.0.27 Linux OS.


This page is Copyright © 1997 By C Scene. All Rights Reserved