ossp-pkg/petidomo/libconfigfile/config.c
/*
$Source: /v/ossp/cvs/ossp-pkg/petidomo/libconfigfile/config.c,v $
$Revision: 1.4 $
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\": %s", filename, strerror(errno));
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\": %s", filename, strerror(errno));
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\": %s", filename, strerror(errno));
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\": %s", filename, strerror(errno));
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\": %s", filename, strerror(errno));
free(file_buf);
return -1;
}