==============================================================================
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:

   * The Config File should be flat ascii. That avoids user editing
     problems.
   * Any one keyword/value combination will take up only one line.
   * The maximum length of the keyword/value is set to 80 (and trailing
     '\n').
   * The connector between keyword and value will be the equals sign "=".

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:

   * 0 for Flawless Execution
   * -1 for File Error
   * -2 for Parameter Error

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
The C Scene logo was created by Enraptured Domain, a division of Nuclear
Winter Entertainment, and is Copyright  1997 By Nuclear Winter
Entertainment. All Rights Reserved.
