]> git.stg.codes - stg.git/blob - libs/dotconfpp/dotconfpp.cpp
Pretty printing.
[stg.git] / libs / dotconfpp / dotconfpp.cpp
1 /*  Copyright (C) 2003 Aleksey Krivoshey <krivoshey@users.sourceforge.net>
2 *
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.
7 *
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.
12 *
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
16 */
17
18 #include <libgen.h> // dirname
19 #include <glob.h> // glob
20 #include <string>
21
22 #include "stg/dotconfpp.h"
23 #include "mempool.h"
24
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)
28 {
29 }
30
31 DOTCONFDocumentNode::~DOTCONFDocumentNode()
32 {
33     free(name);
34     if(values != NULL){
35         for(int i = 0 ; i < valuesCount; i++){
36             free(values[i]);
37         }
38         free(values);
39     }
40 }
41
42 void DOTCONFDocumentNode::pushValue(char * _value)
43 {
44     valuesCount++;
45     values = (char**)realloc(values, valuesCount*sizeof(char*));
46     values[valuesCount-1] = strdup(_value);
47 }
48
49 const char* DOTCONFDocumentNode::getValue(int index) const
50 {
51     if(index >= valuesCount){
52         return NULL;
53     }
54     return values[index];
55 }
56
57 DOTCONFDocument::DOTCONFDocument(DOTCONFDocument::CaseSensitive caseSensitivity):
58     mempool(NULL),
59     curParent(NULL), curPrev(NULL), errorCallback(NULL), errorCallbackData(NULL),
60     curLine(0), file(NULL), fileName(NULL)
61 {
62     if(caseSensitivity == CASESENSITIVE){
63         cmp_func = strcmp;
64     } else {
65         cmp_func = strcasecmp;
66     }
67
68     mempool = new AsyncDNSMemPool(1024);
69     mempool->initialize();
70 }
71
72 DOTCONFDocument::~DOTCONFDocument()
73 {
74     for(std::list<DOTCONFDocumentNode*>::iterator i = nodeTree.begin(); i != nodeTree.end(); ++i){
75         delete(*i);
76     }
77     for(std::list<char*>::iterator i = requiredOptions.begin(); i != requiredOptions.end(); ++i){
78         free(*i);
79     }
80     for(std::list<char*>::iterator i = processedFiles.begin(); i != processedFiles.end(); ++i){
81         free(*i);
82     }
83     free(fileName);
84     delete mempool;
85 }
86
87 int DOTCONFDocument::cleanupLine(char * line)
88 {
89     char * start = line;
90     char * bg = line;
91     bool multiline = false;
92     bool concat = false;
93     char * word = NULL;
94
95     if(!words.empty() && quoted)
96         concat = true;
97
98     while(*line){
99         if((*line == '#' || *line == ';') && !quoted){
100             *bg = 0;
101             if(strlen(start)){
102                 //printf("2start='%s'\n", start);
103                 if(concat){
104                     word = (char*)mempool->alloc(strlen(words.back())+strlen(start)+1);
105                     strcpy(word, words.back());
106                     strcat(word, start);
107                     words.pop_back();
108                     concat = false;
109                 } else {
110                     word = mempool->strdup(start);
111                 }
112                 words.push_back(word);
113             }
114             break;
115         }
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;
118         }
119         if(*line == '\\' && (*(line+1) == '"' || *(line+1) == '\'')){
120             *bg++ = *(line+1); 
121             line+=2; continue;
122         }
123         if(*line == '\\' && *(line+1) == 'n'){
124             *bg++ = '\n'; 
125             line+=2; continue;
126         }
127         if(*line == '\\' && *(line+1) == 'r'){
128             *bg++ = '\r'; 
129             line+=2; continue;
130         }
131         if(*line == '\\' && (*(line+1) == '\n' || *(line+1) == '\r')){ //multiline
132             *bg = 0;
133             if(strlen(start)){
134                 //printf("3start='%s'\n", start);
135                 if(concat){
136                     word = (char*)mempool->alloc(strlen(words.back())+strlen(start)+1);
137                     strcpy(word, words.back());
138                     strcat(word, start);
139                     words.pop_back();
140                     concat = false;
141                 } else {
142                     word = mempool->strdup(start);
143                 }
144                 words.push_back(word);
145             }
146             multiline = true;
147             break;
148         }
149         if(*line == '"' || *line == '\''){ //need to handle quotes because of spaces or = that may be between
150             quoted = !quoted;
151             line++; continue;
152         }
153         if(isspace(*line) && !quoted){
154             *bg++ = 0;
155             if(strlen(start)){
156                 //printf("start='%s'\n", start);
157                 if(concat){
158                     word = (char*)mempool->alloc(strlen(words.back())+strlen(start)+1);
159                     strcpy(word, words.back());
160                     strcat(word, start);
161                     words.pop_back();
162                     concat = false;
163                 } else {
164                     word = mempool->strdup(start);
165                 }
166                 words.push_back(word);
167             }
168             start = bg;
169             while(isspace(*++line)) {};
170             continue;
171         }
172         *bg++ = *line++;
173     }    
174
175     if(quoted && !multiline){
176         error(curLine, fileName, "unterminated quote");
177         return -1;
178     }
179
180     return multiline?1:0;
181 }
182
183 int DOTCONFDocument::parseLine()
184 {
185     char * word = NULL;
186     char * nodeName = NULL;
187     char * nodeValue = NULL;
188     DOTCONFDocumentNode * tagNode = NULL;
189     bool newNode = false;
190
191     for(std::list<char*>::iterator i = words.begin(); i != words.end(); ++i) {
192         word = *i;
193
194         if(*word == '<'){
195             newNode = true;
196         }
197
198         if(newNode){
199             nodeValue = NULL;
200             nodeName = NULL;
201             newNode = false;
202         }
203
204         size_t wordLen = strlen(word);
205         if(word[wordLen-1] == '>'){
206             word[wordLen-1] = 0;
207             newNode = true;
208         }
209
210         if(nodeName == NULL){
211             nodeName = word;
212             bool closed = true; //if this not <> node then it is closed by default
213             if(*nodeName == '<'){
214                 if(*(nodeName+1) != '/'){ //opening tag
215                     nodeName++;
216                     closed = false;
217                 } else { //closing tag
218                     nodeName+=2;
219                     std::list<DOTCONFDocumentNode*>::reverse_iterator i=nodeTree.rbegin();
220                     for(; i!=nodeTree.rend(); ++i){
221                         if(!cmp_func(nodeName, (*i)->name) && !(*i)->closed){
222                             (*i)->closed = true;
223                             curParent = (*i)->parentNode;
224                             curPrev = *i;
225                             break;
226                         }
227                     }
228                     if(i==nodeTree.rend()){
229                         error(curLine, fileName, "not matched closing tag </%s>", nodeName);
230                         return -1;
231                     }
232                     continue;
233                 }
234             }
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();
243                 if(prev->closed){
244
245                     curPrev->nextNode = tagNode;
246                     tagNode->previousNode = curPrev;
247                     tagNode->parentNode = curParent;
248
249                 } else {
250                     prev->childNode = tagNode;
251                     tagNode->parentNode = prev;
252                     curParent = prev;
253                 }
254             }
255             nodeTree.push_back(tagNode);
256             curPrev = tagNode;
257         } else {
258             nodeValue = word;
259             tagNode->pushValue(nodeValue);
260         }
261     }
262     
263     return 0;
264 }
265 int DOTCONFDocument::parseFile(DOTCONFDocumentNode * _parent)
266 {
267     char str[512];
268     int ret = 0;
269     curLine = 0;
270     curParent = _parent;
271
272     quoted = false;
273
274     while(fgets(str, 511, file)){
275         curLine++;
276         size_t slen = strlen(str);
277         if( slen >= 510 ){
278             error(curLine, fileName, "warning: line too long");
279         }
280         if(str[slen-1] != '\n'){
281             str[slen] = '\n';
282             str[slen+1] = 0;
283         }
284         if((ret = cleanupLine(str)) == -1){
285             break;
286         }
287         if(ret == 0){
288             if(!words.empty()){
289                 ret = parseLine();
290                 mempool->free();
291                 words.clear();
292                 if(ret == -1){
293                     break;
294                 }
295             }            
296         }
297     }
298
299     return ret;
300 }
301
302 int DOTCONFDocument::checkConfig(const std::list<DOTCONFDocumentNode*>::iterator & from)
303 {
304     int ret = 0;
305
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);
310             ret = -1;
311             break;
312         }
313         int vi = 0;
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 );
318                 mempool->free();
319                 if(ret == -1){
320                     break;
321                 }
322             }
323             vi++;
324         }
325         if(ret == -1){
326             break;
327         }
328     }
329
330     return ret;
331 }
332
333 int DOTCONFDocument::setContent(const char * _fileName)
334 {    
335     int ret = 0;
336     char realpathBuf[PATH_MAX];
337
338     if(realpath(_fileName, realpathBuf) == NULL){
339         error(0, _fileName, "%s", strerror(errno));
340         return -1;
341     }
342
343     fileName = strdup(realpathBuf);
344
345     char * forPathName = strdup(realpathBuf);
346
347     if (forPathName == NULL) {
348         error(0, NULL, "Not enought memory to duplicate realpath");
349         return -1;
350     }
351
352     char * _pathName = dirname(forPathName);
353
354     std::string pathName(_pathName);
355
356     free(forPathName); // From strdup
357
358     processedFiles.push_back(strdup(realpathBuf));
359
360     if(( file = fopen(fileName, "r")) == NULL){
361         error(0, NULL, "failed to open file '%s': %s", fileName, strerror(errno));
362         return -1;
363     }
364
365     ret = parseFile();
366     
367     (void) fclose(file);
368
369     if(!ret){
370     
371         if( (ret = checkConfig(nodeTree.begin())) == -1){
372             return -1;
373         }
374
375         std::list<DOTCONFDocumentNode*>::iterator from;
376         DOTCONFDocumentNode * tagNode = NULL;
377         int vi = 0;
378         for(std::list<DOTCONFDocumentNode*>::iterator i = nodeTree.begin(); i!=nodeTree.end(); ++i){
379             tagNode = *i;
380             if(!cmp_func("IncludeFile", tagNode->name)){
381                 vi = 0;
382                 while( vi < tagNode->valuesCount ){
383                     glob_t globBuf;
384                     std::string nodeFilePath;
385                     if (*tagNode->values[vi] != '/') {
386                         // Relative path
387                         nodeFilePath = pathName + "/" + tagNode->values[vi];
388                     } else {
389                         // Absolute path
390                        nodeFilePath = tagNode->values[vi];
391                     }
392                     int res = glob(nodeFilePath.c_str(), 0, NULL, &globBuf);
393                     if (res) {
394                         switch (res) {
395                             case GLOB_NOSPACE:
396                                 error(tagNode->lineNum, tagNode->fileName, "glob call failed for '%s': no free space", nodeFilePath.c_str());
397                                 return -1;
398 #ifndef FREE_BSD
399                             case GLOB_ABORTED:
400                                 // printf("Read error\n");
401                                 // Ignore that error
402                                 break;
403                             case GLOB_NOMATCH:
404                                 // printf("No match\n");
405                                 // Ignore that error
406                                 break;
407 #endif
408                             default:
409                                 error(tagNode->lineNum, tagNode->fileName, "glob call failed for '%s': unknown error", nodeFilePath.c_str());
410                                 return -1;
411                         }
412                     }
413                     if (!res) {
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));
418                                 continue;
419                             }
420                             if(realpath(nodeFilePath.c_str(), realpathBuf) == NULL){
421                                 error(tagNode->lineNum, tagNode->fileName, "realpath(%s) failed: %s", nodeFilePath.c_str(), strerror(errno));
422                                 continue;
423                             }
424
425                             bool processed = false;
426                             for(std::list<char*>::const_iterator itInode = processedFiles.begin(); itInode != processedFiles.end(); ++itInode){
427                                 if(!strcmp(*itInode, realpathBuf)){
428                                     processed = true;
429                                     break;
430                                 }
431                             }
432                             if(processed){
433                                 break;
434                             }
435
436                             processedFiles.push_back(strdup(realpathBuf));
437
438                             file = fopen(nodeFilePath.c_str(), "r");
439                             if(file == NULL){
440                                 error(tagNode->lineNum, fileName, "failed to open file '%s': %s", nodeFilePath.c_str(), strerror(errno));
441                                 continue;
442                             }
443                             //free(fileName);
444                             fileName = strdup(realpathBuf);
445                             from = nodeTree.end(); --from;
446                             
447                             if(tagNode->parentNode){
448                                 DOTCONFDocumentNode * nd = tagNode->parentNode->childNode;
449                                 while(nd){
450                                     if(!nd->nextNode)
451                                         break;
452                                     nd = nd->nextNode;
453                                 }
454
455                                 curPrev = nd;
456                             }
457                             ret = parseFile(tagNode->parentNode);
458                             
459                             //ret = parseFile(tagNode->parentNode);
460                             (void) fclose(file);
461                             if(ret == -1)
462                                 continue;
463                             if(checkConfig(++from) == -1){
464                                 continue;
465                             }
466                         }
467                     }
468                     globfree(&globBuf);
469                     vi++;
470                 }
471             }
472         }
473         /*
474         if( (ret = checkConfig(nodeTree.begin())) == -1){
475             return -1;
476         }
477         */
478
479         if(!requiredOptions.empty())
480             ret = checkRequiredOptions();
481     }
482
483     return ret;
484 }
485
486 int DOTCONFDocument::checkRequiredOptions()
487 {
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)){
492                 matched = true;
493                 break;
494             }
495         }
496         if(!matched){
497             error(0, NULL, "required option '%s' not specified", *ci);
498             return -1;
499         }
500     }
501     return 0;
502 }
503
504 void DOTCONFDocument::error(int lineNum, const char * fileName, const char * fmt, ...)
505 {
506     va_list args;
507     va_start(args, fmt);
508
509     char msg[256];
510
511     vsnprintf(msg, 256, fmt, args);
512
513     size_t len = (lineNum!=0?strlen(fileName):0) + strlen(msg) + 50;
514     char * buf = (char*)mempool->alloc(len);
515
516     if(lineNum)
517         (void) snprintf(buf, len, "File '%s', line %d: %s\n", fileName, lineNum, msg);
518     else
519         (void) snprintf(buf, len, "File '%s':  %s\n", fileName, msg);
520
521     if (errorCallback) {
522         errorCallback(errorCallbackData, buf);
523     } else {
524         (void) vfprintf(stderr, buf, args);
525     }
526
527     va_end(args);
528 }
529
530 char * DOTCONFDocument::getSubstitution(char * macro, int lineNum)
531 {
532     char * buf = NULL;
533     char * variable = macro+2;
534
535     char * endBr = strchr(macro, '}');
536
537     if(!endBr){
538         error(lineNum, fileName, "unterminated '{'");
539         return NULL;
540     }
541     *endBr = 0;
542
543     char * defaultValue = strchr(variable, ':');
544
545     if(defaultValue){
546         *defaultValue++ = 0;
547         if(*defaultValue != '-'){
548             error(lineNum, fileName, "incorrect macro substitution syntax");
549             return NULL;
550         }
551         defaultValue++;
552         if(*defaultValue == '"' || *defaultValue == '\''){
553             defaultValue++;
554             defaultValue[strlen(defaultValue)-1] = 0;
555         }
556     } else {
557         defaultValue = NULL;
558     }
559
560     char * subs = getenv(variable);
561     if( subs ){
562         buf = mempool->strdup(subs);
563     } else {
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]);
570                     break;
571                 }
572             }
573         }
574         if( i == nodeTree.end() ){
575             if( defaultValue ){
576                 buf = mempool->strdup(defaultValue);
577             } else {
578                 error(lineNum, fileName, "substitution not found and default value not given");
579                 return NULL;
580             }
581         }
582     }
583     return buf;
584 }
585
586 int DOTCONFDocument::macroSubstitute(DOTCONFDocumentNode * tagNode, int valueIndex)
587 {
588     int ret = 0;
589     char * macro = tagNode->values[valueIndex];
590     size_t valueLen = strlen(tagNode->values[valueIndex])+1;
591     char * value = (char*)mempool->alloc(valueLen);
592     char * v = value;
593     char * subs = NULL;
594
595     while(*macro){
596         if(*macro == '$' && *(macro+1) == '{'){
597             char * m = strchr(macro, '}');
598             subs = getSubstitution(macro, tagNode->lineNum);
599             if(subs == NULL){
600                 ret = -1;
601                 break;
602             }
603             macro = m + 1;
604             *v = 0;
605             v = (char*)mempool->alloc(strlen(value)+strlen(subs)+valueLen);
606             strcpy(v, value);
607             value = strcat(v, subs);
608             v = value + strlen(value);
609             continue;
610         }
611         *v++ = *macro++;
612     }
613     *v = 0;
614
615     free(tagNode->values[valueIndex]);
616     tagNode->values[valueIndex] = strdup(value);
617     return ret;
618 }
619
620 const DOTCONFDocumentNode * DOTCONFDocument::getFirstNode() const
621 {
622     if ( !nodeTree.empty() ) {
623         return *nodeTree.begin();
624     } else {
625         return NULL;
626     }
627 }
628
629 const DOTCONFDocumentNode * DOTCONFDocument::findNode(const char * nodeName, const DOTCONFDocumentNode * parentNode, const DOTCONFDocumentNode * startNode) const
630 {
631     //printf("nodeName=%s, cont=%s, start=%s\n", nodeName, containingNode!=NULL?containingNode->name:"NULL", startNode!=NULL?startNode->name:"NULL");
632     
633     std::list<DOTCONFDocumentNode*>::const_iterator i = nodeTree.begin();
634
635     if(startNode == NULL)
636         startNode = parentNode;
637
638     if(startNode != NULL){
639         while( i != nodeTree.end() && (*i) != startNode ){
640             ++i;
641         }
642         if( i != nodeTree.end() ) ++i;
643     }
644
645     for(; i!=nodeTree.end(); ++i){
646         //if(parentNode != NULL && (*i)->parentNode != parentNode){
647         if((*i)->parentNode != parentNode){
648             continue;
649         }
650         if(!cmp_func(nodeName, (*i)->name)){
651             return *i;
652         }
653     }
654     return NULL;
655 }
656
657 void DOTCONFDocument::setRequiredOptionNames(const char ** requiredOptionNames)
658 {
659     while(*requiredOptionNames){
660         requiredOptions.push_back(strdup( *requiredOptionNames ));
661         requiredOptionNames++;
662     }
663 }
664