/* $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 #include #include #include #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 "keyworddata" 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; }