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, "%s", 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, "File '%s', line %d: %s\n", fileName, lineNum, msg);
 
 519         (void) snprintf(buf, len, "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++;