From: Andrew Tridgell Date: Sat, 9 May 1998 14:00:55 +0000 (+0000) Subject: configuration parsing and loading code for rsyncd. This is based X-Git-Tag: v2.0.0~35 X-Git-Url: http://git.samba.org/samba.git/?p=rsync.git;a=commitdiff_plain;h=0b76cd63ee0eacb95285dfb5d2cac2992e1794ef configuration parsing and loading code for rsyncd. This is based on the Samba config code, so you'll find that the config files will be eerily familiar if you have ever worked with Samba. --- diff --git a/loadparm.c b/loadparm.c new file mode 100644 index 00000000..6ac9abdc --- /dev/null +++ b/loadparm.c @@ -0,0 +1,607 @@ +/* This is based on loadparm.c from Samba, written by Andrew Tridgell + and Karl Auer */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* + * Load parameters. + * + * This module provides suitable callback functions for the params + * module. It builds the internal table of service details which is + * then used by the rest of the server. + * + * To add a parameter: + * + * 1) add it to the global or service structure definition + * 2) add it to the parm_table + * 3) add it to the list of available functions (eg: using FN_GLOBAL_STRING()) + * 4) If it's a global then initialise it in init_globals. If a local + * (ie. service) parameter then initialise it in the sDefault structure + * + * + * Notes: + * The configuration file is processed sequentially for speed. It is NOT + * accessed randomly as happens in 'real' Windows. For this reason, there + * is a fair bit of sequence-dependent code here - ie., code which assumes + * that certain things happen before others. In particular, the code which + * happens at the boundary between sections is delicately poised, so be + * careful! + * + */ + +#include "rsync.h" +#define BOOL int +#define False 0 +#define True 1 +#define Realloc realloc +#define PTR_DIFF(p1,p2) ((ptrdiff_t)(((char *)(p1)) - (char *)(p2))) +#define strequal(a,b) (strcasecmp(a,b)==0) +#define BOOLSTR(b) ((b) ? "Yes" : "No") +typedef char pstring[1024]; +#define pstrcpy(a,b) strcpy(a,b) + +/* the following are used by loadparm for option lists */ +typedef enum +{ + P_BOOL,P_BOOLREV,P_CHAR,P_INTEGER,P_OCTAL, + P_STRING,P_GSTRING,P_ENUM,P_SEP +} parm_type; + +typedef enum +{ + P_LOCAL,P_GLOBAL,P_SEPARATOR,P_NONE +} parm_class; + +struct enum_list { + int value; + char *name; +}; + +struct parm_struct +{ + char *label; + parm_type type; + parm_class class; + void *ptr; + struct enum_list *enum_list; + unsigned flags; +}; + +static BOOL bLoaded = False; + +#ifndef GLOBAL_NAME +#define GLOBAL_NAME "global" +#endif + +/* some helpful bits */ +#define pSERVICE(i) ServicePtrs[i] +#define iSERVICE(i) (*pSERVICE(i)) +#define LP_SNUM_OK(iService) (((iService) >= 0) && ((iService) < iNumServices)) + +/* + * This structure describes global (ie., server-wide) parameters. + */ +typedef struct +{ + char *motd_file; +} global; + +static global Globals; + + + +/* + * This structure describes a single service. + */ +typedef struct +{ + char *name; + char *path; + char *comment; + BOOL read_only; + BOOL list; + int uid; + int gid; +} service; + + +/* This is a default service used to prime a services structure */ +static service sDefault = +{ + NULL, /* name */ + NULL, /* path */ + NULL, /* comment */ + True, /* read only */ + True, /* list */ + -2, /* uid */ + -2, /* gid */ +}; + + + +/* local variables */ +static service **ServicePtrs = NULL; +static int iNumServices = 0; +static int iServiceIndex = 0; +static BOOL bInGlobalSection = True; + +#define NUMPARAMETERS (sizeof(parm_table) / sizeof(struct parm_struct)) + + +/* note that we do not initialise the defaults union - it is not allowed in ANSI C */ +static struct parm_struct parm_table[] = +{ + {"motd file", P_STRING, P_GLOBAL, &Globals.motd_file, NULL, 0}, + {"name", P_STRING, P_LOCAL, &sDefault.name, NULL, 0}, + {"comment", P_STRING, P_LOCAL, &sDefault.comment, NULL, 0}, + {"path", P_STRING, P_LOCAL, &sDefault.path, NULL, 0}, + {"read only", P_BOOL, P_LOCAL, &sDefault.read_only, NULL, 0}, + {"list", P_BOOL, P_LOCAL, &sDefault.list, NULL, 0}, + {"uid", P_INTEGER, P_LOCAL, &sDefault.uid, NULL, 0}, + {"gid", P_INTEGER, P_LOCAL, &sDefault.gid, NULL, 0}, + {NULL, P_BOOL, P_NONE, NULL, NULL, 0} +}; + + +/*************************************************************************** +Initialise the global parameter structure. +***************************************************************************/ +static void init_globals(void) +{ +} + +/*************************************************************************** +Initialise the sDefault parameter structure. +***************************************************************************/ +static void init_locals(void) +{ +} + + +/* + In this section all the functions that are used to access the + parameters from the rest of the program are defined +*/ + +#define FN_GLOBAL_STRING(fn_name,ptr) \ + char *fn_name(void) {return(*(char **)(ptr) ? *(char **)(ptr) : "");} +#define FN_GLOBAL_BOOL(fn_name,ptr) \ + BOOL fn_name(void) {return(*(BOOL *)(ptr));} +#define FN_GLOBAL_CHAR(fn_name,ptr) \ + char fn_name(void) {return(*(char *)(ptr));} +#define FN_GLOBAL_INTEGER(fn_name,ptr) \ + int fn_name(void) {return(*(int *)(ptr));} + +#define FN_LOCAL_STRING(fn_name,val) \ + char *fn_name(int i) {return((LP_SNUM_OK(i)&&pSERVICE(i)->val)?pSERVICE(i)->val : (sDefault.val?sDefault.val:""));} +#define FN_LOCAL_BOOL(fn_name,val) \ + BOOL fn_name(int i) {return(LP_SNUM_OK(i)? pSERVICE(i)->val : sDefault.val);} +#define FN_LOCAL_CHAR(fn_name,val) \ + char fn_name(int i) {return(LP_SNUM_OK(i)? pSERVICE(i)->val : sDefault.val);} +#define FN_LOCAL_INTEGER(fn_name,val) \ + int fn_name(int i) {return(LP_SNUM_OK(i)? pSERVICE(i)->val : sDefault.val);} + + +FN_GLOBAL_STRING(lp_motd_file, &Globals.motd_file) +FN_LOCAL_STRING(lp_name, name) +FN_LOCAL_STRING(lp_comment, comment) +FN_LOCAL_STRING(lp_path, path) +FN_LOCAL_BOOL(lp_read_only, read_only) +FN_LOCAL_BOOL(lp_list, list) +FN_LOCAL_INTEGER(lp_uid, uid) +FN_LOCAL_INTEGER(lp_gid, gid) + +/* local prototypes */ +static int strwicmp( char *psz1, char *psz2 ); +static int map_parameter( char *parmname); +static BOOL set_boolean( BOOL *pb, char *parmvalue ); +static int getservicebyname(char *name, service *pserviceDest); +static void copy_service( service *pserviceDest, + service *pserviceSource); +static BOOL do_parameter(char *parmname, char *parmvalue); +static BOOL do_section(char *sectionname); + + +/*************************************************************************** +initialise a service to the defaults +***************************************************************************/ +static void init_service(service *pservice) +{ + bzero((char *)pservice,sizeof(service)); + copy_service(pservice,&sDefault); +} + +static void string_set(char **s, char *v) +{ + if (*s) free(*s); + if (!v) { + *s = NULL; + return; + } + *s = strdup(v); + if (!*s) exit_cleanup(1); +} + + +/*************************************************************************** +add a new service to the services array initialising it with the given +service +***************************************************************************/ +static int add_a_service(service *pservice, char *name) +{ + int i; + service tservice; + int num_to_alloc = iNumServices+1; + + tservice = *pservice; + + /* it might already exist */ + if (name) + { + i = getservicebyname(name,NULL); + if (i >= 0) + return(i); + } + + i = iNumServices; + + ServicePtrs = (service **)Realloc(ServicePtrs,sizeof(service *)*num_to_alloc); + if (ServicePtrs) + pSERVICE(iNumServices) = (service *)malloc(sizeof(service)); + + if (!ServicePtrs || !pSERVICE(iNumServices)) + return(-1); + + iNumServices++; + + init_service(pSERVICE(i)); + copy_service(pSERVICE(i),&tservice); + if (name) + string_set(&iSERVICE(i).name,name); + + return(i); +} + +/*************************************************************************** +Do a case-insensitive, whitespace-ignoring string compare. +***************************************************************************/ +static int strwicmp(char *psz1, char *psz2) +{ + /* if BOTH strings are NULL, return TRUE, if ONE is NULL return */ + /* appropriate value. */ + if (psz1 == psz2) + return (0); + else + if (psz1 == NULL) + return (-1); + else + if (psz2 == NULL) + return (1); + + /* sync the strings on first non-whitespace */ + while (1) + { + while (isspace(*psz1)) + psz1++; + while (isspace(*psz2)) + psz2++; + if (toupper(*psz1) != toupper(*psz2) || *psz1 == '\0' || *psz2 == '\0') + break; + psz1++; + psz2++; + } + return (*psz1 - *psz2); +} + +/*************************************************************************** +Map a parameter's string representation to something we can use. +Returns False if the parameter string is not recognised, else TRUE. +***************************************************************************/ +static int map_parameter(char *parmname) +{ + int iIndex; + + if (*parmname == '-') + return(-1); + + for (iIndex = 0; parm_table[iIndex].label; iIndex++) + if (strwicmp(parm_table[iIndex].label, parmname) == 0) + return(iIndex); + + rprintf(FERROR, "Unknown parameter encountered: \"%s\"\n", parmname); + return(-1); +} + + +/*************************************************************************** +Set a boolean variable from the text value stored in the passed string. +Returns True in success, False if the passed string does not correctly +represent a boolean. +***************************************************************************/ +static BOOL set_boolean(BOOL *pb, char *parmvalue) +{ + BOOL bRetval; + + bRetval = True; + if (strwicmp(parmvalue, "yes") == 0 || + strwicmp(parmvalue, "true") == 0 || + strwicmp(parmvalue, "1") == 0) + *pb = True; + else + if (strwicmp(parmvalue, "no") == 0 || + strwicmp(parmvalue, "False") == 0 || + strwicmp(parmvalue, "0") == 0) + *pb = False; + else + { + rprintf(FERROR, "Badly formed boolean in configuration file: \"%s\".\n", + parmvalue); + bRetval = False; + } + return (bRetval); +} + +/*************************************************************************** +Find a service by name. Otherwise works like get_service. +***************************************************************************/ +static int getservicebyname(char *name, service *pserviceDest) +{ + int iService; + + for (iService = iNumServices - 1; iService >= 0; iService--) + if (strwicmp(iSERVICE(iService).name, name) == 0) + { + if (pserviceDest != NULL) + copy_service(pserviceDest, pSERVICE(iService)); + break; + } + + return (iService); +} + + + +/*************************************************************************** +Copy a service structure to another + +***************************************************************************/ +static void copy_service(service *pserviceDest, + service *pserviceSource) +{ + int i; + + for (i=0;parm_table[i].label;i++) + if (parm_table[i].ptr && parm_table[i].class == P_LOCAL) { + void *def_ptr = parm_table[i].ptr; + void *src_ptr = + ((char *)pserviceSource) + PTR_DIFF(def_ptr,&sDefault); + void *dest_ptr = + ((char *)pserviceDest) + PTR_DIFF(def_ptr,&sDefault); + + switch (parm_table[i].type) + { + case P_BOOL: + case P_BOOLREV: + *(BOOL *)dest_ptr = *(BOOL *)src_ptr; + break; + + case P_INTEGER: + case P_ENUM: + case P_OCTAL: + *(int *)dest_ptr = *(int *)src_ptr; + break; + + case P_CHAR: + *(char *)dest_ptr = *(char *)src_ptr; + break; + + case P_STRING: + string_set(dest_ptr,*(char **)src_ptr); + break; + + default: + break; + } + } +} + + +/*************************************************************************** +Process a parameter for a particular service number. If snum < 0 +then assume we are in the globals +***************************************************************************/ +static BOOL lp_do_parameter(int snum, char *parmname, char *parmvalue) +{ + int parmnum, i; + void *parm_ptr=NULL; /* where we are going to store the result */ + void *def_ptr=NULL; + + parmnum = map_parameter(parmname); + + if (parmnum < 0) + { + rprintf(FERROR, "Ignoring unknown parameter \"%s\"\n", parmname); + return(True); + } + + def_ptr = parm_table[parmnum].ptr; + + /* we might point at a service, the default service or a global */ + if (snum < 0) { + parm_ptr = def_ptr; + } else { + if (parm_table[parmnum].class == P_GLOBAL) { + rprintf(FERROR, "Global parameter %s found in service section!\n",parmname); + return(True); + } + parm_ptr = ((char *)pSERVICE(snum)) + PTR_DIFF(def_ptr,&sDefault); + } + + /* now switch on the type of variable it is */ + switch (parm_table[parmnum].type) + { + case P_BOOL: + set_boolean(parm_ptr,parmvalue); + break; + + case P_BOOLREV: + set_boolean(parm_ptr,parmvalue); + *(BOOL *)parm_ptr = ! *(BOOL *)parm_ptr; + break; + + case P_INTEGER: + *(int *)parm_ptr = atoi(parmvalue); + break; + + case P_CHAR: + *(char *)parm_ptr = *parmvalue; + break; + + case P_OCTAL: + sscanf(parmvalue,"%o",(int *)parm_ptr); + break; + + case P_STRING: + string_set(parm_ptr,parmvalue); + break; + + case P_GSTRING: + strcpy((char *)parm_ptr,parmvalue); + break; + + case P_ENUM: + for (i=0;parm_table[parmnum].enum_list[i].name;i++) { + if (strequal(parmvalue, parm_table[parmnum].enum_list[i].name)) { + *(int *)parm_ptr = parm_table[parmnum].enum_list[i].value; + break; + } + } + break; + case P_SEP: + break; + } + + return(True); +} + +/*************************************************************************** +Process a parameter. +***************************************************************************/ +static BOOL do_parameter(char *parmname, char *parmvalue) +{ + return lp_do_parameter(bInGlobalSection?-2:iServiceIndex, parmname, parmvalue); +} + +/*************************************************************************** +Process a new section (service). At this stage all sections are services. +Later we'll have special sections that permit server parameters to be set. +Returns True on success, False on failure. +***************************************************************************/ +static BOOL do_section(char *sectionname) +{ + BOOL bRetval; + BOOL isglobal = (strwicmp(sectionname, GLOBAL_NAME) == 0); + bRetval = False; + + /* if we were in a global section then do the local inits */ + if (bInGlobalSection && !isglobal) + init_locals(); + + /* if we've just struck a global section, note the fact. */ + bInGlobalSection = isglobal; + + /* check for multiple global sections */ + if (bInGlobalSection) + { + return(True); + } + + /* if we have a current service, tidy it up before moving on */ + bRetval = True; + + if (iServiceIndex >= 0) + bRetval = True; + + /* if all is still well, move to the next record in the services array */ + if (bRetval) + { + /* We put this here to avoid an odd message order if messages are */ + /* issued by the post-processing of a previous section. */ + + if ((iServiceIndex=add_a_service(&sDefault,sectionname)) < 0) + { + rprintf(FERROR,"Failed to add a new service\n"); + return(False); + } + } + + return (bRetval); +} + + +/*************************************************************************** +Load the services array from the services file. Return True on success, +False on failure. +***************************************************************************/ +BOOL lp_load(char *pszFname) +{ + pstring n2; + BOOL bRetval; + + bRetval = False; + + bInGlobalSection = True; + + init_globals(); + + pstrcpy(n2,pszFname); + + /* We get sections first, so have to start 'behind' to make up */ + iServiceIndex = -1; + bRetval = pm_process(n2, do_section, do_parameter); + + bLoaded = True; + + return (bRetval); +} + + +/*************************************************************************** +return the max number of services +***************************************************************************/ +int lp_numservices(void) +{ + return(iNumServices); +} + +/*************************************************************************** +Return the number of the service with the given name, or -1 if it doesn't +exist. Note that this is a DIFFERENT ANIMAL from the internal function +getservicebyname()! This works ONLY if all services have been loaded, and +does not copy the found service. +***************************************************************************/ +int lp_number(char *name) +{ + int iService; + + for (iService = iNumServices - 1; iService >= 0; iService--) + if (strequal(lp_name(iService), name)) + break; + + return (iService); +} + diff --git a/log.c b/log.c new file mode 100644 index 00000000..20dc702a --- /dev/null +++ b/log.c @@ -0,0 +1,96 @@ +/* + Copyright (C) Andrew Tridgell 1998 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* + logging and utility functions + + tridge, May 1998 + */ +#include "rsync.h" + +/* this is the rsync debugging function. Call it with FINFO or FERROR */ +void rprintf(int fd, const char *format, ...) +{ + va_list ap; + char buf[1024]; + int len; + FILE *f=NULL; + extern int am_daemon; + + if (am_daemon) { + static FILE *logf; + if (!logf) logf = fopen(RSYNCD_LOG, "a"); + f = logf; + if (!f) return; + } + + va_start(ap, format); + +#if HAVE_VSNPRINTF + len = vsnprintf(buf, sizeof(buf)-1, format, ap); +#else + len = vsprintf(buf, format, ap); +#endif + va_end(ap); + + if (len < 0) exit_cleanup(1); + + if (!am_daemon) { + if (fd == FERROR) { + f = stderr; + } + + if (fd == FINFO) { + extern int am_server; + if (am_server) + f = stderr; + else + f = stdout; + } + } + + if (!f) exit_cleanup(1); + + if (fwrite(buf, len, 1, f) != 1) exit_cleanup(1); +} + +void rflush(int fd) +{ + FILE *f = NULL; + extern int am_daemon; + + if (am_daemon) { + return; + } + + if (fd == FERROR) { + f = stderr; + } + + if (fd == FINFO) { + extern int am_server; + if (am_server) + f = stderr; + else + f = stdout; + } + + if (!f) exit_cleanup(1); + fflush(f); +} + diff --git a/params.c b/params.c new file mode 100644 index 00000000..b89d034c --- /dev/null +++ b/params.c @@ -0,0 +1,560 @@ +/* + This modules is based on the params.c module from Samba, written by Karl Auer + and much modifed by Christopher Hertel. + + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * -------------------------------------------------------------------------- ** + * + * Module name: params + * + * -------------------------------------------------------------------------- ** + * + * This module performs lexical analysis and initial parsing of a + * Windows-like parameter file. It recognizes and handles four token + * types: section-name, parameter-name, parameter-value, and + * end-of-file. Comments and line continuation are handled + * internally. + * + * The entry point to the module is function pm_process(). This + * function opens the source file, calls the Parse() function to parse + * the input, and then closes the file when either the EOF is reached + * or a fatal error is encountered. + * + * A sample parameter file might look like this: + * + * [section one] + * parameter one = value string + * parameter two = another value + * [section two] + * new parameter = some value or t'other + * + * The parameter file is divided into sections by section headers: + * section names enclosed in square brackets (eg. [section one]). + * Each section contains parameter lines, each of which consist of a + * parameter name and value delimited by an equal sign. Roughly, the + * syntax is: + * + * :== {
} EOF + * + *
:==
{ } + * + *
:== '[' NAME ']' + * + * :== NAME '=' VALUE '\n' + * + * Blank lines and comment lines are ignored. Comment lines are lines + * beginning with either a semicolon (';') or a pound sign ('#'). + * + * All whitespace in section names and parameter names is compressed + * to single spaces. Leading and trailing whitespace is stipped from + * both names and values. + * + * Only the first equals sign in a parameter line is significant. + * Parameter values may contain equals signs, square brackets and + * semicolons. Internal whitespace is retained in parameter values, + * with the exception of the '\r' character, which is stripped for + * historic reasons. Parameter names may not start with a left square + * bracket, an equal sign, a pound sign, or a semicolon, because these + * are used to identify other tokens. + * + * -------------------------------------------------------------------------- ** + */ + +#include "rsync.h" +#define BOOL int +#define False 0 +#define True 1 +#define Realloc realloc + +/* -------------------------------------------------------------------------- ** + * Constants... + */ + +#define BUFR_INC 1024 + + +/* -------------------------------------------------------------------------- ** + * Variables... + * + * bufr - pointer to a global buffer. This is probably a kludge, + * but it was the nicest kludge I could think of (for now). + * bSize - The size of the global buffer . + */ + +static char *bufr = NULL; +static int bSize = 0; + +/* -------------------------------------------------------------------------- ** + * Functions... + */ + +static int EatWhitespace( FILE *InFile ) + /* ------------------------------------------------------------------------ ** + * Scan past whitespace (see ctype(3C)) and return the first non-whitespace + * character, or newline, or EOF. + * + * Input: InFile - Input source. + * + * Output: The next non-whitespace character in the input stream. + * + * Notes: Because the config files use a line-oriented grammar, we + * explicitly exclude the newline character from the list of + * whitespace characters. + * - Note that both EOF (-1) and the nul character ('\0') are + * considered end-of-file markers. + * + * ------------------------------------------------------------------------ ** + */ + { + int c; + + for( c = getc( InFile ); isspace( c ) && ('\n' != c); c = getc( InFile ) ) + ; + return( c ); + } /* EatWhitespace */ + +static int EatComment( FILE *InFile ) + /* ------------------------------------------------------------------------ ** + * Scan to the end of a comment. + * + * Input: InFile - Input source. + * + * Output: The character that marks the end of the comment. Normally, + * this will be a newline, but it *might* be an EOF. + * + * Notes: Because the config files use a line-oriented grammar, we + * explicitly exclude the newline character from the list of + * whitespace characters. + * - Note that both EOF (-1) and the nul character ('\0') are + * considered end-of-file markers. + * + * ------------------------------------------------------------------------ ** + */ + { + int c; + + for( c = getc( InFile ); ('\n'!=c) && (EOF!=c) && (c>0); c = getc( InFile ) ) + ; + return( c ); + } /* EatComment */ + +static int Continuation( char *line, int pos ) + /* ------------------------------------------------------------------------ ** + * Scan backards within a string to discover if the last non-whitespace + * character is a line-continuation character ('\\'). + * + * Input: line - A pointer to a buffer containing the string to be + * scanned. + * pos - This is taken to be the offset of the end of the + * string. This position is *not* scanned. + * + * Output: The offset of the '\\' character if it was found, or -1 to + * indicate that it was not. + * + * ------------------------------------------------------------------------ ** + */ + { + pos--; + while( (pos >= 0) && isspace(line[pos]) ) + pos--; + + return( ((pos >= 0) && ('\\' == line[pos])) ? pos : -1 ); + } /* Continuation */ + + +static BOOL Section( FILE *InFile, BOOL (*sfunc)(char *) ) + /* ------------------------------------------------------------------------ ** + * Scan a section name, and pass the name to function sfunc(). + * + * Input: InFile - Input source. + * sfunc - Pointer to the function to be called if the section + * name is successfully read. + * + * Output: True if the section name was read and True was returned from + * . False if failed or if a lexical error was + * encountered. + * + * ------------------------------------------------------------------------ ** + */ + { + int c; + int i; + int end; + char *func = "params.c:Section() -"; + + i = 0; /* is the offset of the next free byte in bufr[] and */ + end = 0; /* is the current "end of string" offset. In most */ + /* cases these will be the same, but if the last */ + /* character written to bufr[] is a space, then */ + /* will be one less than . */ + + c = EatWhitespace( InFile ); /* We've already got the '['. Scan */ + /* past initial white space. */ + + while( (EOF != c) && (c > 0) ) + { + + /* Check that the buffer is big enough for the next character. */ + if( i > (bSize - 2) ) + { + bSize += BUFR_INC; + bufr = Realloc( bufr, bSize ); + if( NULL == bufr ) + { + rprintf(FERROR, "%s Memory re-allocation failure.", func); + return( False ); + } + } + + /* Handle a single character. */ + switch( c ) + { + case ']': /* Found the closing bracket. */ + bufr[end] = '\0'; + if( 0 == end ) /* Don't allow an empty name. */ + { + rprintf(FERROR, "%s Empty section name in configuration file.\n", func ); + return( False ); + } + if( !sfunc( bufr ) ) /* Got a valid name. Deal with it. */ + return( False ); + (void)EatComment( InFile ); /* Finish off the line. */ + return( True ); + + case '\n': /* Got newline before closing ']'. */ + i = Continuation( bufr, i ); /* Check for line continuation. */ + if( i < 0 ) + { + bufr[end] = '\0'; + rprintf(FERROR, "%s Badly formed line in configuration file: %s\n", + func, bufr ); + return( False ); + } + end = ( (i > 0) && (' ' == bufr[i - 1]) ) ? (i - 1) : (i); + c = getc( InFile ); /* Continue with next line. */ + break; + + default: /* All else are a valid name chars. */ + if( isspace( c ) ) /* One space per whitespace region. */ + { + bufr[end] = ' '; + i = end + 1; + c = EatWhitespace( InFile ); + } + else /* All others copy verbatim. */ + { + bufr[i++] = c; + end = i; + c = getc( InFile ); + } + } + } + + /* We arrive here if we've met the EOF before the closing bracket. */ + rprintf(FERROR, "%s Unexpected EOF in the configuration file: %s\n", func, bufr ); + return( False ); + } /* Section */ + +static BOOL Parameter( FILE *InFile, BOOL (*pfunc)(char *, char *), int c ) + /* ------------------------------------------------------------------------ ** + * Scan a parameter name and value, and pass these two fields to pfunc(). + * + * Input: InFile - The input source. + * pfunc - A pointer to the function that will be called to + * process the parameter, once it has been scanned. + * c - The first character of the parameter name, which + * would have been read by Parse(). Unlike a comment + * line or a section header, there is no lead-in + * character that can be discarded. + * + * Output: True if the parameter name and value were scanned and processed + * successfully, else False. + * + * Notes: This function is in two parts. The first loop scans the + * parameter name. Internal whitespace is compressed, and an + * equal sign (=) terminates the token. Leading and trailing + * whitespace is discarded. The second loop scans the parameter + * value. When both have been successfully identified, they are + * passed to pfunc() for processing. + * + * ------------------------------------------------------------------------ ** + */ + { + int i = 0; /* Position within bufr. */ + int end = 0; /* bufr[end] is current end-of-string. */ + int vstart = 0; /* Starting position of the parameter value. */ + char *func = "params.c:Parameter() -"; + + /* Read the parameter name. */ + while( 0 == vstart ) /* Loop until we've found the start of the value. */ + { + + if( i > (bSize - 2) ) /* Ensure there's space for next char. */ + { + bSize += BUFR_INC; + bufr = Realloc( bufr, bSize ); + if( NULL == bufr ) + { + rprintf(FERROR, "%s Memory re-allocation failure.", func) ; + return( False ); + } + } + + switch( c ) + { + case '=': /* Equal sign marks end of param name. */ + if( 0 == end ) /* Don't allow an empty name. */ + { + rprintf(FERROR, "%s Invalid parameter name in config. file.\n", func ); + return( False ); + } + bufr[end++] = '\0'; /* Mark end of string & advance. */ + i = end; /* New string starts here. */ + vstart = end; /* New string is parameter value. */ + bufr[i] = '\0'; /* New string is nul, for now. */ + break; + + case '\n': /* Find continuation char, else error. */ + i = Continuation( bufr, i ); + if( i < 0 ) + { + bufr[end] = '\0'; + rprintf(FERROR, "%s Ignoring badly formed line in configuration file: %s\n", + func, bufr ); + return( True ); + } + end = ( (i > 0) && (' ' == bufr[i - 1]) ) ? (i - 1) : (i); + c = getc( InFile ); /* Read past eoln. */ + break; + + case '\0': /* Shouldn't have EOF within param name. */ + case EOF: + bufr[i] = '\0'; + rprintf(FERROR, "%s Unexpected end-of-file at: %s\n", func, bufr ); + return( True ); + + default: + if( isspace( c ) ) /* One ' ' per whitespace region. */ + { + bufr[end] = ' '; + i = end + 1; + c = EatWhitespace( InFile ); + } + else /* All others verbatim. */ + { + bufr[i++] = c; + end = i; + c = getc( InFile ); + } + } + } + + /* Now parse the value. */ + c = EatWhitespace( InFile ); /* Again, trim leading whitespace. */ + while( (EOF !=c) && (c > 0) ) + { + + if( i > (bSize - 2) ) /* Make sure there's enough room. */ + { + bSize += BUFR_INC; + bufr = Realloc( bufr, bSize ); + if( NULL == bufr ) + { + rprintf(FERROR, "%s Memory re-allocation failure.", func) ; + return( False ); + } + } + + switch( c ) + { + case '\r': /* Explicitly remove '\r' because the older */ + c = getc( InFile ); /* version called fgets_slash() which also */ + break; /* removes them. */ + + case '\n': /* Marks end of value unless there's a '\'. */ + i = Continuation( bufr, i ); + if( i < 0 ) + c = 0; + else + { + for( end = i; (end >= 0) && isspace(bufr[end]); end-- ) + ; + c = getc( InFile ); + } + break; + + default: /* All others verbatim. Note that spaces do */ + bufr[i++] = c; /* not advance . This allows trimming */ + if( !isspace( c ) ) /* of whitespace at the end of the line. */ + end = i; + c = getc( InFile ); + break; + } + } + bufr[end] = '\0'; /* End of value. */ + + return( pfunc( bufr, &bufr[vstart] ) ); /* Pass name & value to pfunc(). */ + } /* Parameter */ + +static BOOL Parse( FILE *InFile, + BOOL (*sfunc)(char *), + BOOL (*pfunc)(char *, char *) ) + /* ------------------------------------------------------------------------ ** + * Scan & parse the input. + * + * Input: InFile - Input source. + * sfunc - Function to be called when a section name is scanned. + * See Section(). + * pfunc - Function to be called when a parameter is scanned. + * See Parameter(). + * + * Output: True if the file was successfully scanned, else False. + * + * Notes: The input can be viewed in terms of 'lines'. There are four + * types of lines: + * Blank - May contain whitespace, otherwise empty. + * Comment - First non-whitespace character is a ';' or '#'. + * The remainder of the line is ignored. + * Section - First non-whitespace character is a '['. + * Parameter - The default case. + * + * ------------------------------------------------------------------------ ** + */ + { + int c; + + c = EatWhitespace( InFile ); + while( (EOF != c) && (c > 0) ) + { + switch( c ) + { + case '\n': /* Blank line. */ + c = EatWhitespace( InFile ); + break; + + case ';': /* Comment line. */ + case '#': + c = EatComment( InFile ); + break; + + case '[': /* Section Header. */ + if( !Section( InFile, sfunc ) ) + return( False ); + c = EatWhitespace( InFile ); + break; + + case '\\': /* Bogus backslash. */ + c = EatWhitespace( InFile ); + break; + + default: /* Parameter line. */ + if( !Parameter( InFile, pfunc, c ) ) + return( False ); + c = EatWhitespace( InFile ); + break; + } + } + return( True ); + } /* Parse */ + +static FILE *OpenConfFile( char *FileName ) + /* ------------------------------------------------------------------------ ** + * Open a configuration file. + * + * Input: FileName - The pathname of the config file to be opened. + * + * Output: A pointer of type (FILE *) to the opened file, or NULL if the + * file could not be opened. + * + * ------------------------------------------------------------------------ ** + */ + { + FILE *OpenedFile; + char *func = "params.c:OpenConfFile() -"; + + if( NULL == FileName || 0 == *FileName ) + { + rprintf(FERROR,"%s No configuration filename specified.\n", func); + return( NULL ); + } + + OpenedFile = fopen( FileName, "r" ); + if( NULL == OpenedFile ) + { + rprintf(FERROR,"%s Unable to open configuration file \"%s\":\n\t%s\n", + func, FileName, strerror(errno)); + } + + return( OpenedFile ); + } /* OpenConfFile */ + +BOOL pm_process( char *FileName, + BOOL (*sfunc)(char *), + BOOL (*pfunc)(char *, char *) ) + /* ------------------------------------------------------------------------ ** + * Process the named parameter file. + * + * Input: FileName - The pathname of the parameter file to be opened. + * sfunc - A pointer to a function that will be called when + * a section name is discovered. + * pfunc - A pointer to a function that will be called when + * a parameter name and value are discovered. + * + * Output: TRUE if the file was successfully parsed, else FALSE. + * + * ------------------------------------------------------------------------ ** + */ + { + int result; + FILE *InFile; + char *func = "params.c:pm_process() -"; + + InFile = OpenConfFile( FileName ); /* Open the config file. */ + if( NULL == InFile ) + return( False ); + + if( NULL != bufr ) /* If we already have a buffer */ + result = Parse( InFile, sfunc, pfunc ); /* (recursive call), then just */ + /* use it. */ + + else /* If we don't have a buffer */ + { /* allocate one, then parse, */ + bSize = BUFR_INC; /* then free. */ + bufr = (char *)malloc( bSize ); + if( NULL == bufr ) + { + rprintf(FERROR,"%s memory allocation failure.\n", func); + fclose(InFile); + return( False ); + } + result = Parse( InFile, sfunc, pfunc ); + free( bufr ); + bufr = NULL; + bSize = 0; + } + + fclose(InFile); + + if( !result ) /* Generic failure. */ + { + rprintf(FERROR,"%s Failed. Error returned from params.c:parse().\n", func); + return( False ); + } + + return( True ); /* Generic success. */ + } /* pm_process */ + +/* -------------------------------------------------------------------------- */