OSSP CVS Repository

ossp - ossp-pkg/petidomo/libconfigfile/config.c 1.3
Not logged in
[Honeypot]  [Browse]  [Directory]  [Home]  [Login
[Reports]  [Search]  [Ticket]  [Timeline
  [Raw

ossp-pkg/petidomo/libconfigfile/config.c 1.3
/*
   $Source: /v/ossp/cvs/ossp-pkg/petidomo/libconfigfile/config.c,v $
   $Revision: 1.3 $

   Copyright (C) 2000 by CyberSolutions GmbH, Germany.

   This file is part of OpenPetidomo.

   OpenPetidomo 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, or (at your option)
   any later version.

   OpenPetidomo 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 OpenPetidomo; see the file COPYING. If not, write to
   the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.
*/

#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <syslog.h>

#include "../liblists/lists.h"
#include "../libtext/text.h"
#include "configfile.h"

char * loadfile(const char *);

static List Files = NULL;

/* Remove a trailing carrige return from a string */

static void
ChopCR(char * p)
{
    while (*p != '\0' && *p != '\n')
      p++;

    *p = '\0';
}

/* Turn a string into the number of an array element that matches this
   string. */

static int
FindCFEntry(struct ConfigFile cf[], const char * keyword)
{
    int  i;

    for (i = 0; (&(cf[i]))->keyword != NULL; i++) {
	if ((strcasecmp((&(cf[i]))->keyword, keyword)) == 0)
	  return i;
    }

    return -1;
}

/* ReadConfig() will read and parse a given config file and fill the
   parsed tags into the ConfigFile structure given on the command
   line. The 'ConfigFile' structure consists of three fields: The name
   of the config tag to look for, a description of how to interpret
   the parameter specified with it and a pointer to a 'void
   pointer'-type, which will contain the result when ReadConfig()
   returns.

   Valid tag types are CF_STRING (result buffer will contain a string
   pointer to the text), CF_INTEGER (parameter will be turned into an
   integer and stored into the result buffer) and CF_YES_NO
   (parameter is expected to be a 'yes', 'no', 'true' or 'false' and
   the appropriate binary setting will be stored).

   RETURNS: A return code of zero (0) indicates success, -1 indicates
   failure. In case of a failure, ReadConfig() will log an error
   description using the syslog(3) mechanism.

 */

int
ReadConfig(const char * filename, /* path to the config file to parse */
	   struct ConfigFile cf[] /* structure describing the config tags */
	   )
{
    Node         node;
    char *       file_buf;
    char *       currLine;
    char *       nextLine;
    char *       keyword;
    char *       data;
    int          rc;
    int          i;
    unsigned int numLine;

    /* Initialize list of loaded config files. */

    if (Files == NULL) {
	Files = InitList(NULL);
	if (Files == NULL) {
	    syslog(LOG_ERR, "ReadConfig: Failed to initialize my internal config file list.");
	    return -1;
	}
    }
    else {
	/* Check whether we already parsed that file. */

	node = FindNodeByKey(Files, filename);
	if (node != NULL)
	  return 0;
    }

    /* Load the file into memory. */

    file_buf = loadfile(filename);
    if (file_buf == NULL) {
	syslog(LOG_WARNING, "ReadConfig: Failed to load config file \"%s\": %m", filename);
	return -1;
    }

    /* Store the buffer details in the linked list. */

    filename = strdup(filename);
    if (filename == NULL) {
	syslog(LOG_ERR, "ReadConfig: Failed to load config file \"%s\": %m", filename);
	return -1;
    }
    node = AppendNode(Files, filename, file_buf);
    if (node == NULL) {
	syslog(LOG_ERR, "ReadConfig: Internal Error: Couldn't add config file data to my list.");
	return -1;
    }

    /* Parse the file line by line. */

    for (numLine = 1, nextLine = currLine = file_buf; *currLine != '\0'; currLine = nextLine, numLine++) {
	nextLine = text_find_next_line(currLine);
	ChopCR(currLine);

	/* Ignore comments or empty lines. */

	if ((text_easy_pattern_match(currLine, "^[\t ]*#|^[\t ]*$")) == TRUE)
	  continue;		/* ignore it */

	/* Line is supposed to contain a config statement, so we
           should do a consistency check first. If the line passes,
           there're no surprises when we actually parse it. */

	if ((text_easy_pattern_match(currLine, "^[[:alnum:]_-]+[[:space:]]+[^[:space:]]+.*[^[:space:]]+[[:space:]]*$")) == FALSE) {
	    syslog(LOG_WARNING, "ReadConfig: Line \"%s\" is syntactically incorrect.",
		   currLine);
	    continue;		/* ignore it */
	}

	/* Remove all unnecessary whitespace. */

	rc = text_transform_text(currLine, currLine, "^([[:alnum:]_-]+)[[:space:]]+([^[:space:]]+.*[^[:space:]]+)[[:space:]]*$", "\\1 \\2");
	if (rc != 0) {
	    syslog(LOG_WARNING, "ReadConfig: Internal error while parsing line: %d.", rc);
	    continue;		/* ignore it */
	}

	/* Locate the keyword and data part. */

	for (keyword = currLine; *currLine != ' '; currLine++)
	  ;
	*currLine++ = '\0';
	data = currLine;

	/* Find appropriate entry in the ConfigFile structure and
           check whether we know the keyword. */

	i = FindCFEntry(cf, keyword);
	if (i == -1) {
	    syslog(LOG_WARNING, "ReadConfig: Unrecognized keyword \"%s\" in file \"%s\", line %d.",
		   keyword, filename, numLine);
	    continue;		/* ignore it */
	}

	/* Determine type of data and store it appropriately. */

	switch ((&(cf[i]))->type) {
	  case CF_STRING:
	      /* Handle strings included in quotes. */

	      rc = text_transform_text(data, data, "^\"([^\"]*)\"$", "\\1");
	      if (rc != 0 && rc != TEXT_REGEX_TRANSFORM_DIDNT_MATCH) {
		  syslog(LOG_WARNING, "ReadConfig: Internal error while parsing file \"%s\", line: %d.",
		      filename, numLine);
		  continue;
	      }

	      /* Store the string. */

	      if ((&(cf[i]))->data != NULL)
		*((char **)(&(cf[i]))->data) = data;
	      break;

	  case CF_INTEGER:
	      /* Integers will have an additional consistency check to
                 warn the user about typing errors. */

	      if ((text_easy_pattern_match(data, "^[-+][[:digit:]]+$|^[[:digit:]]+$")) == FALSE) {
		  syslog(LOG_WARNING, "ReadConfig: Specified parameter \"%s\" in file \"%s\", line %d, is not a number.", data, filename, numLine);
		  continue;		/* ignore it */
	      }
	      if ((&(cf[i]))->data != NULL)
		*((int *)(&(cf[i]))->data) = atoi(data);
	      break;
	  case CF_YES_NO:
	      if ((text_easy_pattern_match(data, "^yes$|^true$|^y$")) == TRUE) {
		  if ((&(cf[i]))->data != NULL)
		    *((bool *)(&(cf[i]))->data) = TRUE;
	      } else if ((text_easy_pattern_match(data, "^no$|^false$|^n$")) == TRUE) {
		  if ((&(cf[i]))->data != NULL)
		    *((bool *)(&(cf[i]))->data) = FALSE;
	      } else {
		  syslog(LOG_WARNING, "ReadConfig: Specified parameter \"%s\" in file \"%s\", line %d, is not a yes or a no.", data, filename, numLine);
		  continue;		/* ignore it */
	      }
	      break;
	  case CF_MULTI:
	      syslog(LOG_WARNING, "ReadConfig: Method not supported at the moment.");
	      break;
	  default:
	      syslog(LOG_ERR, "ReadConfig internal error: ConfigFile structure element %d has unknown type %d.", i++, (&(cf[i]))->type);
	}

    }

    return 0;
}

/* This routine must be used to return all resources to the system,
   that have been allocaged by a specific ReadConfig(3) call. After
   that, all result buffers provided by ReadConfig() will be invalid. */

void
FreeConfig(const char * filename)
{
    Node   node;

    node = FindNodeByKey(Files, filename);
    if (node == NULL)
      return;

    free((void *) getNodeData(node));
    free((void *) getNodeKey(node));
    RemoveNode(node);
    FreeNode(node);
}

/* This function call will free all resources for all previous
   ReadConfig(3) calls.

   BUGS: Not implemented at the moment.
*/

void
FreeAllConfigs(void)
{

}

static char *
LocateKeywordInBuffer(char * buffer, char * keyword)
{
    char *   p;

    for (p = buffer; p != NULL; ) {
	p = text_find_string(p, keyword);
	if (p != NULL) {
	    if (p == buffer || p[-1] == '\n' || p[-1] == '\r')
	      break;
	    else
	      p++;		/* Not a keyword, look again. */
	}
    }

    return p;
}

/* Load a config file and parse it for the required keyword. Then
   return the parameter. The buffer containing the parameter should be
   free()'d by the caller. #

   RETURNS: If the keyword is not found, NULL is returned and errno is
   set to 0. If an error occurs, NULL is returned also, but errno is
   set accordingly.
*/

char *
GetConfig(const char * filename, char * keyword)
{
    char *   file_buf;
    char *   result_buf;
    char *   p;
    int      rc;

    /* Load the config file. */

    file_buf = loadfile(filename);
    if (file_buf == NULL) {
	syslog(LOG_ERR, "GetConfig: Failed to load config file \"%s\". %m", filename);
	return NULL;
    }

    /* Find the keyword. */

    p = LocateKeywordInBuffer(file_buf, keyword);

    if (p == NULL) {
	/* No luck, keyword doesn't exist. */
	errno = 0;
	return NULL;
    }

    /* Okay, keyword found. Now extract the parameter. */

    ChopCR(p);
    rc = text_transform_text(p, p, "^[^\t ]+[\t ]+([^\t ].*)[\t ]*$", "\\1");
    if (rc != 0) {
	syslog(LOG_WARNING, "GetConfig: Config file entry \"%s\" is corrupt.", keyword);
	errno = -1;
	return NULL;
    }
    rc = text_transform_text(p, p, "^\"([^\"]*)\"$", "\\1");
    if (rc != 0 && rc != TEXT_REGEX_TRANSFORM_DIDNT_MATCH) {
	syslog(LOG_WARNING, "GetConfig: Config file entry \"%s\" is corrupt.", keyword);
	errno = -1;
	return NULL;
    }


    /* Copy the parameter into a new buffer. */

    result_buf = strdup(p);
    if (result_buf == NULL) {
	syslog(LOG_ERR, "GetConfig: Failed to allocate %d byte of memory.", strlen(p));
	errno = ENOMEM;
	return NULL;
    }

    /* Free the used memory and return the result. */

    free(file_buf);
    return result_buf;
}

/* This routine will set a given config entry in a config file. When
   the keyword is already there, the parameter will be changed.
   Otherwise the string "keyword<tab>data" is appended at the end of
   the file.

   RETURNS: If something goes wrong, the routine returns -1 and sets
   errno. Otherwise we return 0.

   BUGS: This routine may DESTRUCT THE FILE if some I/O error occurs.
   It is the caller's responsibility to backup the file before calling
   us.
*/

int
SetConfig(const char * filename,
	  char * keyword,	/* string pointer to the tag name */
	  const char * data	/* string pointer to the tag data */
	  )
{
    struct flock  lock;
    char *        file_buf;
    int           file_len;
    char *        p;
    char *        nextLine;
    int           fd;
    int           rc;

    /* Load the config file. */

    file_buf = loadfile(filename);
    if (file_buf == NULL) {
	syslog(LOG_ERR, "SetConfig: Failed to load config file \"%s\": %m", filename);
	return -1;
    }
    file_len = errno;

    /* Find the keyword. */

    p = LocateKeywordInBuffer(file_buf, keyword);

    if (p == NULL) {
	/* No luck, keyword doesn't exist. Append the data. */

	fd = open(filename, O_WRONLY | O_APPEND, 0666);
	if (fd == -1)
	  goto io_error;

	lock.l_start  = 0;
	lock.l_len    = 0;
	lock.l_type   = F_WRLCK;
	lock.l_whence = SEEK_SET;
	fcntl(fd, F_SETLKW, &lock);

	/* Does the file end in a '\n' or must we append one? */

	if (file_buf[file_len - 1] != '\n') {
	    rc = write(fd, "\n", 1);
	    if (rc == -1)
	      goto io_error;
	}

	/* Write the actual keyword and data. */

        if ((write(fd, keyword, strlen(keyword)) == -1)
	    || (write(fd, "\t", 1) == -1)
	    || (write(fd, data, strlen(data)) == -1)
	    || (write(fd, "\n", 1) == -1)) {
	    goto io_error;
	}
    }
    else {
	fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
	if (fd == -1)
	  goto io_error;

	lock.l_start  = 0;
	lock.l_len    = 0;
	lock.l_type   = F_WRLCK;
	lock.l_whence = SEEK_SET;
	fcntl(fd, F_SETLKW, &lock);

	nextLine = text_find_next_line(p);
	*p = '\0';

	/* Write buffer back until the to-be-replaced keyword. */

	rc = write(fd, file_buf, strlen(file_buf));
	if (fd == -1)
	  goto io_error;

	/* Write the actual keyword and data. */

        if ((write(fd, keyword, strlen(keyword)) == -1)
	    || (write(fd, "\t", 1) == -1)
	    || (write(fd, data, strlen(data)) == -1)
	    || (write(fd, "\n", 1) == -1)) {
	    goto io_error;
	}

	/* Write the rest of the buffer. */

	rc = write(fd, nextLine, strlen(nextLine));
	if (fd == -1)
	  goto io_error;
    }

    close(fd);
    free(file_buf);
    return 0;

io_error:
    syslog(LOG_ERR, "SetConfig: Failed to write to file \"%s\": %m", filename);
    free(file_buf);
    return -1;
}

CVSTrac 2.0.1