configuration parsing and loading code for rsyncd. This is based
authorAndrew Tridgell <tridge@samba.org>
Sat, 9 May 1998 14:00:55 +0000 (14:00 +0000)
committerAndrew Tridgell <tridge@samba.org>
Sat, 9 May 1998 14:00:55 +0000 (14:00 +0000)
on the Samba config code, so you'll find that the config files will
be eerily familiar if you have ever worked with Samba.

loadparm.c [new file with mode: 0644]
log.c [new file with mode: 0644]
params.c [new file with mode: 0644]

diff --git a/loadparm.c b/loadparm.c
new file mode 100644 (file)
index 0000000..6ac9abd
--- /dev/null
@@ -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 (file)
index 0000000..20dc702
--- /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 (file)
index 0000000..b89d034
--- /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:
+ *
+ *    <file>            :==  { <section> } EOF
+ *
+ *    <section>         :==  <section header> { <parameter line> }
+ *
+ *    <section header>  :==  '[' NAME ']'
+ *
+ *    <parameter line>  :==  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 <bufr>.
+ */
+
+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
+   *          <sfunc>.  False if <sfunc> failed or if a lexical error was
+   *          encountered.
+   *
+   * ------------------------------------------------------------------------ **
+   */
+  {
+  int   c;
+  int   i;
+  int   end;
+  char *func  = "params.c:Section() -";
+
+  i = 0;      /* <i> is the offset of the next free byte in bufr[] and  */
+  end = 0;    /* <end> 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 <end>     */
+              /* will be one less than <i>.                             */
+
+  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 <end>.  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 */
+
+/* -------------------------------------------------------------------------- */