/* Copyright (C) 2003 Aleksey Krivoshey * * 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 // dirname #include // glob #include #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::iterator i = nodeTree.begin(); i != nodeTree.end(); ++i){ delete(*i); } for(std::list::iterator i = requiredOptions.begin(); i != requiredOptions.end(); ++i){ free(*i); } for(std::list::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::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::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 ", 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::iterator & from) { int ret = 0; for(std::list::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::iterator from; DOTCONFDocumentNode * tagNode = NULL; int vi = 0; for(std::list::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::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::const_iterator ci = requiredOptions.begin(); ci != requiredOptions.end(); ++ci){ bool matched = false; for(std::list::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::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::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++; } }