--- /dev/null
+/* Copyright (C) 2003 Aleksey Krivoshey <krivoshey@users.sourceforge.net>
+*
+* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <libgen.h> // dirname
+#include <glob.h> // glob
+#include <string>
+
+#include "stg/dotconfpp.h"
+#include "mempool.h"
+
+DOTCONFDocumentNode::DOTCONFDocumentNode():previousNode(NULL), nextNode(NULL), parentNode(NULL), childNode(NULL),
+ values(NULL), valuesCount(0),
+ name(NULL), lineNum(0), fileName(NULL), closed(true)
+{
+}
+
+DOTCONFDocumentNode::~DOTCONFDocumentNode()
+{
+ free(name);
+ if(values != NULL){
+ for(int i = 0 ; i < valuesCount; i++){
+ free(values[i]);
+ }
+ free(values);
+ }
+}
+
+void DOTCONFDocumentNode::pushValue(char * _value)
+{
+ valuesCount++;
+ values = (char**)realloc(values, valuesCount*sizeof(char*));
+ values[valuesCount-1] = strdup(_value);
+}
+
+const char* DOTCONFDocumentNode::getValue(int index) const
+{
+ if(index >= valuesCount){
+ return NULL;
+ }
+ return values[index];
+}
+
+DOTCONFDocument::DOTCONFDocument(DOTCONFDocument::CaseSensitive caseSensitivity):
+ mempool(NULL),
+ curParent(NULL), curPrev(NULL), errorCallback(NULL), errorCallbackData(NULL),
+ curLine(0), file(NULL), fileName(NULL)
+{
+ if(caseSensitivity == CASESENSITIVE){
+ cmp_func = strcmp;
+ } else {
+ cmp_func = strcasecmp;
+ }
+
+ mempool = new AsyncDNSMemPool(1024);
+ mempool->initialize();
+}
+
+DOTCONFDocument::~DOTCONFDocument()
+{
+ for(std::list<DOTCONFDocumentNode*>::iterator i = nodeTree.begin(); i != nodeTree.end(); ++i){
+ delete(*i);
+ }
+ for(std::list<char*>::iterator i = requiredOptions.begin(); i != requiredOptions.end(); ++i){
+ free(*i);
+ }
+ for(std::list<char*>::iterator i = processedFiles.begin(); i != processedFiles.end(); ++i){
+ free(*i);
+ }
+ free(fileName);
+ delete mempool;
+}
+
+int DOTCONFDocument::cleanupLine(char * line)
+{
+ char * start = line;
+ char * bg = line;
+ bool multiline = false;
+ bool concat = false;
+ char * word = NULL;
+
+ if(!words.empty() && quoted)
+ concat = true;
+
+ while(*line){
+ if((*line == '#' || *line == ';') && !quoted){
+ *bg = 0;
+ if(strlen(start)){
+ //printf("2start='%s'\n", start);
+ if(concat){
+ word = (char*)mempool->alloc(strlen(words.back())+strlen(start)+1);
+ strcpy(word, words.back());
+ strcat(word, start);
+ words.pop_back();
+ concat = false;
+ } else {
+ word = mempool->strdup(start);
+ }
+ words.push_back(word);
+ }
+ break;
+ }
+ if(*line == '=' && !quoted){ // 'parameter = value' is the same as 'parameter value' but do not replace with ' ' when used in quoted value
+ *line = ' ';continue;
+ }
+ if(*line == '\\' && (*(line+1) == '"' || *(line+1) == '\'')){
+ *bg++ = *(line+1);
+ line+=2; continue;
+ }
+ if(*line == '\\' && *(line+1) == 'n'){
+ *bg++ = '\n';
+ line+=2; continue;
+ }
+ if(*line == '\\' && *(line+1) == 'r'){
+ *bg++ = '\r';
+ line+=2; continue;
+ }
+ if(*line == '\\' && (*(line+1) == '\n' || *(line+1) == '\r')){ //multiline
+ *bg = 0;
+ if(strlen(start)){
+ //printf("3start='%s'\n", start);
+ if(concat){
+ word = (char*)mempool->alloc(strlen(words.back())+strlen(start)+1);
+ strcpy(word, words.back());
+ strcat(word, start);
+ words.pop_back();
+ concat = false;
+ } else {
+ word = mempool->strdup(start);
+ }
+ words.push_back(word);
+ }
+ multiline = true;
+ break;
+ }
+ if(*line == '"' || *line == '\''){ //need to handle quotes because of spaces or = that may be between
+ quoted = !quoted;
+ line++; continue;
+ }
+ if(isspace(*line) && !quoted){
+ *bg++ = 0;
+ if(strlen(start)){
+ //printf("start='%s'\n", start);
+ if(concat){
+ word = (char*)mempool->alloc(strlen(words.back())+strlen(start)+1);
+ strcpy(word, words.back());
+ strcat(word, start);
+ words.pop_back();
+ concat = false;
+ } else {
+ word = mempool->strdup(start);
+ }
+ words.push_back(word);
+ }
+ start = bg;
+ while(isspace(*++line)) {};
+ continue;
+ }
+ *bg++ = *line++;
+ }
+
+ if(quoted && !multiline){
+ error(curLine, fileName, "unterminated quote");
+ return -1;
+ }
+
+ return multiline?1:0;
+}
+
+int DOTCONFDocument::parseLine()
+{
+ char * word = NULL;
+ char * nodeName = NULL;
+ char * nodeValue = NULL;
+ DOTCONFDocumentNode * tagNode = NULL;
+ bool newNode = false;
+
+ for(std::list<char*>::iterator i = words.begin(); i != words.end(); ++i) {
+ word = *i;
+
+ if(*word == '<'){
+ newNode = true;
+ }
+
+ if(newNode){
+ nodeValue = NULL;
+ nodeName = NULL;
+ newNode = false;
+ }
+
+ size_t wordLen = strlen(word);
+ if(word[wordLen-1] == '>'){
+ word[wordLen-1] = 0;
+ newNode = true;
+ }
+
+ if(nodeName == NULL){
+ nodeName = word;
+ bool closed = true; //if this not <> node then it is closed by default
+ if(*nodeName == '<'){
+ if(*(nodeName+1) != '/'){ //opening tag
+ nodeName++;
+ closed = false;
+ } else { //closing tag
+ nodeName+=2;
+ std::list<DOTCONFDocumentNode*>::reverse_iterator i=nodeTree.rbegin();
+ for(; i!=nodeTree.rend(); ++i){
+ if(!cmp_func(nodeName, (*i)->name) && !(*i)->closed){
+ (*i)->closed = true;
+ curParent = (*i)->parentNode;
+ curPrev = *i;
+ break;
+ }
+ }
+ if(i==nodeTree.rend()){
+ error(curLine, fileName, "not matched closing tag </%s>", nodeName);
+ return -1;
+ }
+ continue;
+ }
+ }
+ tagNode = new DOTCONFDocumentNode;
+ tagNode->name = strdup(nodeName);
+ tagNode->document = this;
+ tagNode->fileName = processedFiles.back();
+ tagNode->lineNum = curLine;
+ tagNode->closed = closed;
+ if(!nodeTree.empty()){
+ DOTCONFDocumentNode * prev = nodeTree.back();
+ if(prev->closed){
+
+ curPrev->nextNode = tagNode;
+ tagNode->previousNode = curPrev;
+ tagNode->parentNode = curParent;
+
+ } else {
+ prev->childNode = tagNode;
+ tagNode->parentNode = prev;
+ curParent = prev;
+ }
+ }
+ nodeTree.push_back(tagNode);
+ curPrev = tagNode;
+ } else {
+ nodeValue = word;
+ tagNode->pushValue(nodeValue);
+ }
+ }
+
+ return 0;
+}
+int DOTCONFDocument::parseFile(DOTCONFDocumentNode * _parent)
+{
+ char str[512];
+ int ret = 0;
+ curLine = 0;
+ curParent = _parent;
+
+ quoted = false;
+
+ while(fgets(str, 511, file)){
+ curLine++;
+ size_t slen = strlen(str);
+ if( slen >= 510 ){
+ error(curLine, fileName, "warning: line too long");
+ }
+ if(str[slen-1] != '\n'){
+ str[slen] = '\n';
+ str[slen+1] = 0;
+ }
+ if((ret = cleanupLine(str)) == -1){
+ break;
+ }
+ if(ret == 0){
+ if(!words.empty()){
+ ret = parseLine();
+ mempool->free();
+ words.clear();
+ if(ret == -1){
+ break;
+ }
+ }
+ }
+ }
+
+ return ret;
+}
+
+int DOTCONFDocument::checkConfig(const std::list<DOTCONFDocumentNode*>::iterator & from)
+{
+ int ret = 0;
+
+ for(std::list<DOTCONFDocumentNode*>::iterator i = from; i != nodeTree.end(); ++i){
+ DOTCONFDocumentNode * tagNode = *i;
+ if(!tagNode->closed){
+ error(tagNode->lineNum, tagNode->fileName, "unclosed tag %s", tagNode->name);
+ ret = -1;
+ break;
+ }
+ int vi = 0;
+ while( vi < tagNode->valuesCount ){
+ //if((tagNode->values[vi])[0] == '$' && (tagNode->values[vi])[1] == '{' && strchr(tagNode->values[vi], '}') ){
+ if(strstr(tagNode->values[vi], "${") && strchr(tagNode->values[vi], '}') ){
+ ret = macroSubstitute(tagNode, vi );
+ mempool->free();
+ if(ret == -1){
+ break;
+ }
+ }
+ vi++;
+ }
+ if(ret == -1){
+ break;
+ }
+ }
+
+ return ret;
+}
+
+int DOTCONFDocument::setContent(const char * _fileName)
+{
+ int ret = 0;
+ char realpathBuf[PATH_MAX];
+
+ if(realpath(_fileName, realpathBuf) == NULL){
+ error(0, _fileName, "%s", strerror(errno));
+ return -1;
+ }
+
+ fileName = strdup(realpathBuf);
+
+ char * forPathName = strdup(realpathBuf);
+
+ if (forPathName == NULL) {
+ error(0, NULL, "Not enought memory to duplicate realpath");
+ return -1;
+ }
+
+ char * _pathName = dirname(forPathName);
+
+ std::string pathName(_pathName);
+
+ free(forPathName); // From strdup
+
+ processedFiles.push_back(strdup(realpathBuf));
+
+ if(( file = fopen(fileName, "r")) == NULL){
+ error(0, NULL, "failed to open file '%s': %s", fileName, strerror(errno));
+ return -1;
+ }
+
+ ret = parseFile();
+
+ (void) fclose(file);
+
+ if(!ret){
+
+ if( (ret = checkConfig(nodeTree.begin())) == -1){
+ return -1;
+ }
+
+ std::list<DOTCONFDocumentNode*>::iterator from;
+ DOTCONFDocumentNode * tagNode = NULL;
+ int vi = 0;
+ for(std::list<DOTCONFDocumentNode*>::iterator i = nodeTree.begin(); i!=nodeTree.end(); ++i){
+ tagNode = *i;
+ if(!cmp_func("IncludeFile", tagNode->name)){
+ vi = 0;
+ while( vi < tagNode->valuesCount ){
+ glob_t globBuf;
+ std::string nodeFilePath;
+ if (*tagNode->values[vi] != '/') {
+ // Relative path
+ nodeFilePath = pathName + "/" + tagNode->values[vi];
+ } else {
+ // Absolute path
+ nodeFilePath = tagNode->values[vi];
+ }
+ int res = glob(nodeFilePath.c_str(), 0, NULL, &globBuf);
+ if (res) {
+ switch (res) {
+ case GLOB_NOSPACE:
+ error(tagNode->lineNum, tagNode->fileName, "glob call failed for '%s': no free space", nodeFilePath.c_str());
+ return -1;
+#ifndef FREE_BSD
+ case GLOB_ABORTED:
+ // printf("Read error\n");
+ // Ignore that error
+ break;
+ case GLOB_NOMATCH:
+ // printf("No match\n");
+ // Ignore that error
+ break;
+#endif
+ default:
+ error(tagNode->lineNum, tagNode->fileName, "glob call failed for '%s': unknown error", nodeFilePath.c_str());
+ return -1;
+ }
+ }
+ if (!res) {
+ for (size_t i = 0; i < globBuf.gl_pathc; ++i) {
+ std::string nodeFilePath(globBuf.gl_pathv[i]);
+ if(access(nodeFilePath.c_str(), R_OK) == -1){
+ error(tagNode->lineNum, tagNode->fileName, "%s: %s", nodeFilePath.c_str(), strerror(errno));
+ continue;
+ }
+ if(realpath(nodeFilePath.c_str(), realpathBuf) == NULL){
+ error(tagNode->lineNum, tagNode->fileName, "realpath(%s) failed: %s", nodeFilePath.c_str(), strerror(errno));
+ continue;
+ }
+
+ bool processed = false;
+ for(std::list<char*>::const_iterator itInode = processedFiles.begin(); itInode != processedFiles.end(); ++itInode){
+ if(!strcmp(*itInode, realpathBuf)){
+ processed = true;
+ break;
+ }
+ }
+ if(processed){
+ break;
+ }
+
+ processedFiles.push_back(strdup(realpathBuf));
+
+ file = fopen(nodeFilePath.c_str(), "r");
+ if(file == NULL){
+ error(tagNode->lineNum, fileName, "failed to open file '%s': %s", nodeFilePath.c_str(), strerror(errno));
+ continue;
+ }
+ //free(fileName);
+ fileName = strdup(realpathBuf);
+ from = nodeTree.end(); --from;
+
+ if(tagNode->parentNode){
+ DOTCONFDocumentNode * nd = tagNode->parentNode->childNode;
+ while(nd){
+ if(!nd->nextNode)
+ break;
+ nd = nd->nextNode;
+ }
+
+ curPrev = nd;
+ }
+ ret = parseFile(tagNode->parentNode);
+
+ //ret = parseFile(tagNode->parentNode);
+ (void) fclose(file);
+ if(ret == -1)
+ continue;
+ if(checkConfig(++from) == -1){
+ continue;
+ }
+ }
+ }
+ globfree(&globBuf);
+ vi++;
+ }
+ }
+ }
+ /*
+ if( (ret = checkConfig(nodeTree.begin())) == -1){
+ return -1;
+ }
+ */
+
+ if(!requiredOptions.empty())
+ ret = checkRequiredOptions();
+ }
+
+ return ret;
+}
+
+int DOTCONFDocument::checkRequiredOptions()
+{
+ for(std::list<char*>::const_iterator ci = requiredOptions.begin(); ci != requiredOptions.end(); ++ci){
+ bool matched = false;
+ for(std::list<DOTCONFDocumentNode*>::iterator i = nodeTree.begin(); i!=nodeTree.end(); ++i){
+ if(!cmp_func((*i)->name, *ci)){
+ matched = true;
+ break;
+ }
+ }
+ if(!matched){
+ error(0, NULL, "required option '%s' not specified", *ci);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+void DOTCONFDocument::error(int lineNum, const char * fileName, const char * fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ char msg[256];
+
+ vsnprintf(msg, 256, fmt, args);
+
+ size_t len = (lineNum!=0?strlen(fileName):0) + strlen(msg) + 50;
+ char * buf = (char*)mempool->alloc(len);
+
+ if(lineNum)
+ (void) snprintf(buf, len, "File '%s', line %d: %s\n", fileName, lineNum, msg);
+ else
+ (void) snprintf(buf, len, "File '%s': %s\n", fileName, msg);
+
+ if (errorCallback) {
+ errorCallback(errorCallbackData, buf);
+ } else {
+ (void) vfprintf(stderr, buf, args);
+ }
+
+ va_end(args);
+}
+
+char * DOTCONFDocument::getSubstitution(char * macro, int lineNum)
+{
+ char * buf = NULL;
+ char * variable = macro+2;
+
+ char * endBr = strchr(macro, '}');
+
+ if(!endBr){
+ error(lineNum, fileName, "unterminated '{'");
+ return NULL;
+ }
+ *endBr = 0;
+
+ char * defaultValue = strchr(variable, ':');
+
+ if(defaultValue){
+ *defaultValue++ = 0;
+ if(*defaultValue != '-'){
+ error(lineNum, fileName, "incorrect macro substitution syntax");
+ return NULL;
+ }
+ defaultValue++;
+ if(*defaultValue == '"' || *defaultValue == '\''){
+ defaultValue++;
+ defaultValue[strlen(defaultValue)-1] = 0;
+ }
+ } else {
+ defaultValue = NULL;
+ }
+
+ char * subs = getenv(variable);
+ if( subs ){
+ buf = mempool->strdup(subs);
+ } else {
+ std::list<DOTCONFDocumentNode*>::iterator i = nodeTree.begin();
+ for(; i!=nodeTree.end(); ++i){
+ DOTCONFDocumentNode * tagNode = *i;
+ if(!cmp_func(tagNode->name, variable)){
+ if(tagNode->valuesCount != 0){
+ buf = mempool->strdup(tagNode->values[0]);
+ break;
+ }
+ }
+ }
+ if( i == nodeTree.end() ){
+ if( defaultValue ){
+ buf = mempool->strdup(defaultValue);
+ } else {
+ error(lineNum, fileName, "substitution not found and default value not given");
+ return NULL;
+ }
+ }
+ }
+ return buf;
+}
+
+int DOTCONFDocument::macroSubstitute(DOTCONFDocumentNode * tagNode, int valueIndex)
+{
+ int ret = 0;
+ char * macro = tagNode->values[valueIndex];
+ size_t valueLen = strlen(tagNode->values[valueIndex])+1;
+ char * value = (char*)mempool->alloc(valueLen);
+ char * v = value;
+ char * subs = NULL;
+
+ while(*macro){
+ if(*macro == '$' && *(macro+1) == '{'){
+ char * m = strchr(macro, '}');
+ subs = getSubstitution(macro, tagNode->lineNum);
+ if(subs == NULL){
+ ret = -1;
+ break;
+ }
+ macro = m + 1;
+ *v = 0;
+ v = (char*)mempool->alloc(strlen(value)+strlen(subs)+valueLen);
+ strcpy(v, value);
+ value = strcat(v, subs);
+ v = value + strlen(value);
+ continue;
+ }
+ *v++ = *macro++;
+ }
+ *v = 0;
+
+ free(tagNode->values[valueIndex]);
+ tagNode->values[valueIndex] = strdup(value);
+ return ret;
+}
+
+const DOTCONFDocumentNode * DOTCONFDocument::getFirstNode() const
+{
+ if ( !nodeTree.empty() ) {
+ return *nodeTree.begin();
+ } else {
+ return NULL;
+ }
+}
+
+const DOTCONFDocumentNode * DOTCONFDocument::findNode(const char * nodeName, const DOTCONFDocumentNode * parentNode, const DOTCONFDocumentNode * startNode) const
+{
+ //printf("nodeName=%s, cont=%s, start=%s\n", nodeName, containingNode!=NULL?containingNode->name:"NULL", startNode!=NULL?startNode->name:"NULL");
+
+ std::list<DOTCONFDocumentNode*>::const_iterator i = nodeTree.begin();
+
+ if(startNode == NULL)
+ startNode = parentNode;
+
+ if(startNode != NULL){
+ while( i != nodeTree.end() && (*i) != startNode ){
+ ++i;
+ }
+ if( i != nodeTree.end() ) ++i;
+ }
+
+ for(; i!=nodeTree.end(); ++i){
+ //if(parentNode != NULL && (*i)->parentNode != parentNode){
+ if((*i)->parentNode != parentNode){
+ continue;
+ }
+ if(!cmp_func(nodeName, (*i)->name)){
+ return *i;
+ }
+ }
+ return NULL;
+}
+
+void DOTCONFDocument::setRequiredOptionNames(const char ** requiredOptionNames)
+{
+ while(*requiredOptionNames){
+ requiredOptions.push_back(strdup( *requiredOptionNames ));
+ requiredOptionNames++;
+ }
+}
+