1 /* Copyright (C) 2003 Aleksey Krivoshey <krivoshey@users.sourceforge.net>
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 #include <libgen.h> // dirname
19 #include <glob.h> // glob
22 #include "stg/dotconfpp.h"
25 DOTCONFDocumentNode::DOTCONFDocumentNode():previousNode(NULL), nextNode(NULL), parentNode(NULL), childNode(NULL),
26 values(NULL), valuesCount(0),
27 name(NULL), lineNum(0), fileName(NULL), closed(true)
31 DOTCONFDocumentNode::~DOTCONFDocumentNode()
35 for(int i = 0 ; i < valuesCount; i++){
42 void DOTCONFDocumentNode::pushValue(char * _value)
45 values = (char**)realloc(values, valuesCount*sizeof(char*));
46 values[valuesCount-1] = strdup(_value);
49 const char* DOTCONFDocumentNode::getValue(int index) const
51 if(index >= valuesCount){
57 DOTCONFDocument::DOTCONFDocument(DOTCONFDocument::CaseSensitive caseSensitivity):
59 curParent(NULL), curPrev(NULL), errorCallback(NULL), errorCallbackData(NULL),
60 curLine(0), file(NULL), fileName(NULL)
62 if(caseSensitivity == CASESENSITIVE){
65 cmp_func = strcasecmp;
68 mempool = new AsyncDNSMemPool(1024);
69 mempool->initialize();
72 DOTCONFDocument::~DOTCONFDocument()
74 for(std::list<DOTCONFDocumentNode*>::iterator i = nodeTree.begin(); i != nodeTree.end(); ++i){
77 for(std::list<char*>::iterator i = requiredOptions.begin(); i != requiredOptions.end(); ++i){
80 for(std::list<char*>::iterator i = processedFiles.begin(); i != processedFiles.end(); ++i){
87 int DOTCONFDocument::cleanupLine(char * line)
91 bool multiline = false;
95 if(!words.empty() && quoted)
99 if((*line == '#' || *line == ';') && !quoted){
102 //printf("2start='%s'\n", start);
104 word = (char*)mempool->alloc(strlen(words.back())+strlen(start)+1);
105 strcpy(word, words.back());
110 word = mempool->strdup(start);
112 words.push_back(word);
116 if(*line == '=' && !quoted){ // 'parameter = value' is the same as 'parameter value' but do not replace with ' ' when used in quoted value
117 *line = ' ';continue;
119 if(*line == '\\' && (*(line+1) == '"' || *(line+1) == '\'')){
123 if(*line == '\\' && *(line+1) == 'n'){
127 if(*line == '\\' && *(line+1) == 'r'){
131 if(*line == '\\' && (*(line+1) == '\n' || *(line+1) == '\r')){ //multiline
134 //printf("3start='%s'\n", start);
136 word = (char*)mempool->alloc(strlen(words.back())+strlen(start)+1);
137 strcpy(word, words.back());
142 word = mempool->strdup(start);
144 words.push_back(word);
149 if(*line == '"' || *line == '\''){ //need to handle quotes because of spaces or = that may be between
153 if(isspace(*line) && !quoted){
156 //printf("start='%s'\n", start);
158 word = (char*)mempool->alloc(strlen(words.back())+strlen(start)+1);
159 strcpy(word, words.back());
164 word = mempool->strdup(start);
166 words.push_back(word);
169 while(isspace(*++line)) {};
175 if(quoted && !multiline){
176 error(curLine, fileName, "unterminated quote");
180 return multiline?1:0;
183 int DOTCONFDocument::parseLine()
186 char * nodeName = NULL;
187 char * nodeValue = NULL;
188 DOTCONFDocumentNode * tagNode = NULL;
189 bool newNode = false;
191 for(std::list<char*>::iterator i = words.begin(); i != words.end(); ++i) {
204 size_t wordLen = strlen(word);
205 if(word[wordLen-1] == '>'){
210 if(nodeName == NULL){
212 bool closed = true; //if this not <> node then it is closed by default
213 if(*nodeName == '<'){
214 if(*(nodeName+1) != '/'){ //opening tag
217 } else { //closing tag
219 std::list<DOTCONFDocumentNode*>::reverse_iterator i=nodeTree.rbegin();
220 for(; i!=nodeTree.rend(); ++i){
221 if(!cmp_func(nodeName, (*i)->name) && !(*i)->closed){
223 curParent = (*i)->parentNode;
228 if(i==nodeTree.rend()){
229 error(curLine, fileName, "not matched closing tag </%s>", nodeName);
235 tagNode = new DOTCONFDocumentNode;
236 tagNode->name = strdup(nodeName);
237 tagNode->document = this;
238 tagNode->fileName = processedFiles.back();
239 tagNode->lineNum = curLine;
240 tagNode->closed = closed;
241 if(!nodeTree.empty()){
242 DOTCONFDocumentNode * prev = nodeTree.back();
245 curPrev->nextNode = tagNode;
246 tagNode->previousNode = curPrev;
247 tagNode->parentNode = curParent;
250 prev->childNode = tagNode;
251 tagNode->parentNode = prev;
255 nodeTree.push_back(tagNode);
259 tagNode->pushValue(nodeValue);
265 int DOTCONFDocument::parseFile(DOTCONFDocumentNode * _parent)
274 while(fgets(str, 511, file)){
276 size_t slen = strlen(str);
278 error(curLine, fileName, "warning: line too long");
280 if(str[slen-1] != '\n'){
284 if((ret = cleanupLine(str)) == -1){
302 int DOTCONFDocument::checkConfig(const std::list<DOTCONFDocumentNode*>::iterator & from)
306 for(std::list<DOTCONFDocumentNode*>::iterator i = from; i != nodeTree.end(); ++i){
307 DOTCONFDocumentNode * tagNode = *i;
308 if(!tagNode->closed){
309 error(tagNode->lineNum, tagNode->fileName, "unclosed tag %s", tagNode->name);
314 while( vi < tagNode->valuesCount ){
315 //if((tagNode->values[vi])[0] == '$' && (tagNode->values[vi])[1] == '{' && strchr(tagNode->values[vi], '}') ){
316 if(strstr(tagNode->values[vi], "${") && strchr(tagNode->values[vi], '}') ){
317 ret = macroSubstitute(tagNode, vi );
333 int DOTCONFDocument::setContent(const char * _fileName)
336 char realpathBuf[PATH_MAX];
338 if(realpath(_fileName, realpathBuf) == NULL){
339 error(0, _fileName, "realpath('%s') failed: %s", _fileName, strerror(errno));
343 fileName = strdup(realpathBuf);
345 char * forPathName = strdup(realpathBuf);
347 if (forPathName == NULL) {
348 error(0, NULL, "Not enought memory to duplicate realpath");
352 char * _pathName = dirname(forPathName);
354 std::string pathName(_pathName);
356 free(forPathName); // From strdup
358 processedFiles.push_back(strdup(realpathBuf));
360 if(( file = fopen(fileName, "r")) == NULL){
361 error(0, NULL, "failed to open file '%s': %s", fileName, strerror(errno));
371 if( (ret = checkConfig(nodeTree.begin())) == -1){
375 std::list<DOTCONFDocumentNode*>::iterator from;
376 DOTCONFDocumentNode * tagNode = NULL;
378 for(std::list<DOTCONFDocumentNode*>::iterator i = nodeTree.begin(); i!=nodeTree.end(); ++i){
380 if(!cmp_func("IncludeFile", tagNode->name)){
382 while( vi < tagNode->valuesCount ){
384 std::string nodeFilePath;
385 if (*tagNode->values[vi] != '/') {
387 nodeFilePath = pathName + "/" + tagNode->values[vi];
390 nodeFilePath = tagNode->values[vi];
392 int res = glob(nodeFilePath.c_str(), 0, NULL, &globBuf);
396 error(tagNode->lineNum, tagNode->fileName, "glob call failed for '%s': no free space", nodeFilePath.c_str());
400 // printf("Read error\n");
404 // printf("No match\n");
409 error(tagNode->lineNum, tagNode->fileName, "glob call failed for '%s': unknown error", nodeFilePath.c_str());
414 for (size_t i = 0; i < globBuf.gl_pathc; ++i) {
415 std::string nodeFilePath(globBuf.gl_pathv[i]);
416 if(access(nodeFilePath.c_str(), R_OK) == -1){
417 error(tagNode->lineNum, tagNode->fileName, "%s: %s", nodeFilePath.c_str(), strerror(errno));
420 if(realpath(nodeFilePath.c_str(), realpathBuf) == NULL){
421 error(tagNode->lineNum, tagNode->fileName, "realpath(%s) failed: %s", nodeFilePath.c_str(), strerror(errno));
425 bool processed = false;
426 for(std::list<char*>::const_iterator itInode = processedFiles.begin(); itInode != processedFiles.end(); ++itInode){
427 if(!strcmp(*itInode, realpathBuf)){
436 processedFiles.push_back(strdup(realpathBuf));
438 file = fopen(nodeFilePath.c_str(), "r");
440 error(tagNode->lineNum, fileName, "failed to open file '%s': %s", nodeFilePath.c_str(), strerror(errno));
444 fileName = strdup(realpathBuf);
445 from = nodeTree.end(); --from;
447 if(tagNode->parentNode){
448 DOTCONFDocumentNode * nd = tagNode->parentNode->childNode;
457 ret = parseFile(tagNode->parentNode);
459 //ret = parseFile(tagNode->parentNode);
463 if(checkConfig(++from) == -1){
474 if( (ret = checkConfig(nodeTree.begin())) == -1){
479 if(!requiredOptions.empty())
480 ret = checkRequiredOptions();
486 int DOTCONFDocument::checkRequiredOptions()
488 for(std::list<char*>::const_iterator ci = requiredOptions.begin(); ci != requiredOptions.end(); ++ci){
489 bool matched = false;
490 for(std::list<DOTCONFDocumentNode*>::iterator i = nodeTree.begin(); i!=nodeTree.end(); ++i){
491 if(!cmp_func((*i)->name, *ci)){
497 error(0, NULL, "required option '%s' not specified", *ci);
504 void DOTCONFDocument::error(int lineNum, const char * fileName, const char * fmt, ...)
511 vsnprintf(msg, 256, fmt, args);
513 size_t len = (lineNum!=0?strlen(fileName):0) + strlen(msg) + 50;
514 char * buf = (char*)mempool->alloc(len);
517 (void) snprintf(buf, len, "DOTCONF++: file '%s', line %d: %s\n", fileName, lineNum, msg);
519 (void) snprintf(buf, len, "DOTCONF++: file '%s': %s\n", fileName, msg);
522 errorCallback(errorCallbackData, buf);
524 (void) vfprintf(stderr, buf, args);
530 char * DOTCONFDocument::getSubstitution(char * macro, int lineNum)
533 char * variable = macro+2;
535 char * endBr = strchr(macro, '}');
538 error(lineNum, fileName, "unterminated '{'");
543 char * defaultValue = strchr(variable, ':');
547 if(*defaultValue != '-'){
548 error(lineNum, fileName, "incorrect macro substitution syntax");
552 if(*defaultValue == '"' || *defaultValue == '\''){
554 defaultValue[strlen(defaultValue)-1] = 0;
560 char * subs = getenv(variable);
562 buf = mempool->strdup(subs);
564 std::list<DOTCONFDocumentNode*>::iterator i = nodeTree.begin();
565 for(; i!=nodeTree.end(); ++i){
566 DOTCONFDocumentNode * tagNode = *i;
567 if(!cmp_func(tagNode->name, variable)){
568 if(tagNode->valuesCount != 0){
569 buf = mempool->strdup(tagNode->values[0]);
574 if( i == nodeTree.end() ){
576 buf = mempool->strdup(defaultValue);
578 error(lineNum, fileName, "substitution not found and default value not given");
586 int DOTCONFDocument::macroSubstitute(DOTCONFDocumentNode * tagNode, int valueIndex)
589 char * macro = tagNode->values[valueIndex];
590 size_t valueLen = strlen(tagNode->values[valueIndex])+1;
591 char * value = (char*)mempool->alloc(valueLen);
596 if(*macro == '$' && *(macro+1) == '{'){
597 char * m = strchr(macro, '}');
598 subs = getSubstitution(macro, tagNode->lineNum);
605 v = (char*)mempool->alloc(strlen(value)+strlen(subs)+valueLen);
607 value = strcat(v, subs);
608 v = value + strlen(value);
615 free(tagNode->values[valueIndex]);
616 tagNode->values[valueIndex] = strdup(value);
620 const DOTCONFDocumentNode * DOTCONFDocument::getFirstNode() const
622 if ( !nodeTree.empty() ) {
623 return *nodeTree.begin();
629 const DOTCONFDocumentNode * DOTCONFDocument::findNode(const char * nodeName, const DOTCONFDocumentNode * parentNode, const DOTCONFDocumentNode * startNode) const
631 //printf("nodeName=%s, cont=%s, start=%s\n", nodeName, containingNode!=NULL?containingNode->name:"NULL", startNode!=NULL?startNode->name:"NULL");
633 std::list<DOTCONFDocumentNode*>::const_iterator i = nodeTree.begin();
635 if(startNode == NULL)
636 startNode = parentNode;
638 if(startNode != NULL){
639 while( i != nodeTree.end() && (*i) != startNode ){
642 if( i != nodeTree.end() ) ++i;
645 for(; i!=nodeTree.end(); ++i){
646 //if(parentNode != NULL && (*i)->parentNode != parentNode){
647 if((*i)->parentNode != parentNode){
650 if(!cmp_func(nodeName, (*i)->name)){
657 void DOTCONFDocument::setRequiredOptionNames(const char ** requiredOptionNames)
659 while(*requiredOptionNames){
660 requiredOptions.push_back(strdup( *requiredOptionNames ));
661 requiredOptionNames++;