]> git.stg.codes - stg.git/blob - stglibs/dotconfpp.lib/dotconfpp.cpp
Better handling of errors from server. Refactoring.
[stg.git] / stglibs / dotconfpp.lib / 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     size_t slen = 0;
274
275     while(fgets(str, 511, file)){
276         curLine++;
277         slen = strlen(str);
278         if( slen >= 510 ){
279             error(curLine, fileName, "warning: line too long");
280         }
281         if(str[slen-1] != '\n'){
282             str[slen] = '\n';
283             str[slen+1] = 0;
284         }
285         if((ret = cleanupLine(str)) == -1){
286             break;
287         }
288         if(ret == 0){
289             if(!words.empty()){
290                 ret = parseLine();
291                 mempool->free();
292                 words.clear();
293                 if(ret == -1){
294                     break;
295                 }
296             }            
297         }
298     }
299
300     return ret;
301 }
302
303 int DOTCONFDocument::checkConfig(const std::list<DOTCONFDocumentNode*>::iterator & from)
304 {
305     int ret = 0;
306
307     DOTCONFDocumentNode * tagNode = NULL;
308     int vi = 0;
309     for(std::list<DOTCONFDocumentNode*>::iterator i = from; i != nodeTree.end(); i++){
310         tagNode = *i;
311         if(!tagNode->closed){
312             error(tagNode->lineNum, tagNode->fileName, "unclosed tag %s", tagNode->name);
313             ret = -1;
314             break;
315         }
316         vi = 0;
317         while( vi < tagNode->valuesCount ){
318             //if((tagNode->values[vi])[0] == '$' && (tagNode->values[vi])[1] == '{' && strchr(tagNode->values[vi], '}') ){
319             if(strstr(tagNode->values[vi], "${") && strchr(tagNode->values[vi], '}') ){
320                 ret = macroSubstitute(tagNode, vi );
321                 mempool->free();
322                 if(ret == -1){
323                     break;
324                 }
325             }
326             vi++;
327         }
328         if(ret == -1){
329             break;
330         }
331     }
332
333     return ret;
334 }
335
336 int DOTCONFDocument::setContent(const char * _fileName)
337 {    
338     int ret = 0;
339     char realpathBuf[PATH_MAX];
340
341     if(realpath(_fileName, realpathBuf) == NULL){
342         error(0, _fileName, "realpath('%s') failed: %s", _fileName, strerror(errno));
343         return -1;
344     }
345
346     fileName = strdup(realpathBuf);
347
348     char * forPathName = strdup(realpathBuf);
349
350     if (forPathName == NULL) {
351         error(0, NULL, "Not enought memory to duplicate realpath");
352         return -1;
353     }
354
355     char * _pathName = dirname(forPathName);
356
357     std::string pathName(_pathName);
358
359     free(forPathName); // From strdup
360
361     processedFiles.push_back(strdup(realpathBuf));
362
363     if(( file = fopen(fileName, "r")) == NULL){
364         error(0, NULL, "failed to open file '%s': %s", fileName, strerror(errno));
365         return -1;
366     }
367
368     ret = parseFile();
369     
370     (void) fclose(file);
371
372     if(!ret){
373     
374         if( (ret = checkConfig(nodeTree.begin())) == -1){
375             return -1;
376         }
377
378         std::list<DOTCONFDocumentNode*>::iterator from;
379         DOTCONFDocumentNode * tagNode = NULL;
380         int vi = 0;
381         for(std::list<DOTCONFDocumentNode*>::iterator i = nodeTree.begin(); i!=nodeTree.end(); i++){
382             tagNode = *i;
383             if(!cmp_func("IncludeFile", tagNode->name)){
384                 vi = 0;
385                 while( vi < tagNode->valuesCount ){
386                     glob_t globBuf;
387                     std::string nodeFilePath;
388                     if (*tagNode->values[vi] != '/') {
389                         // Relative path
390                         nodeFilePath = pathName + "/" + tagNode->values[vi];
391                     } else {
392                         // Absolute path
393                        nodeFilePath = tagNode->values[vi];
394                     }
395                     int res = glob(nodeFilePath.c_str(), 0, NULL, &globBuf);
396                     if (res) {
397                         switch (res) {
398                             case GLOB_NOSPACE:
399                                 error(tagNode->lineNum, tagNode->fileName, "glob call failed for '%s': no free space", nodeFilePath.c_str());
400                                 return -1;
401 #ifndef FREE_BSD
402                             case GLOB_ABORTED:
403                                 // printf("Read error\n");
404                                 // Ignore that error
405                                 break;
406                             case GLOB_NOMATCH:
407                                 // printf("No match\n");
408                                 // Ignore that error
409                                 break;
410 #endif
411                             default:
412                                 error(tagNode->lineNum, tagNode->fileName, "glob call failed for '%s': unknown error", nodeFilePath.c_str());
413                                 return -1;
414                         }
415                     }
416                     if (!res) {
417                         for (size_t i = 0; i < globBuf.gl_pathc; ++i) {
418                             std::string nodeFilePath(globBuf.gl_pathv[i]);
419                             if(access(nodeFilePath.c_str(), R_OK) == -1){
420                                 error(tagNode->lineNum, tagNode->fileName, "%s: %s", nodeFilePath.c_str(), strerror(errno));
421                                 continue;
422                             }
423                             if(realpath(nodeFilePath.c_str(), realpathBuf) == NULL){
424                                 error(tagNode->lineNum, tagNode->fileName, "realpath(%s) failed: %s", nodeFilePath.c_str(), strerror(errno));
425                                 continue;
426                             }
427
428                             bool processed = false;
429                             for(std::list<char*>::const_iterator itInode = processedFiles.begin(); itInode != processedFiles.end(); itInode++){
430                                 if(!strcmp(*itInode, realpathBuf)){
431                                     processed = true;
432                                     break;
433                                 }
434                             }
435                             if(processed){
436                                 break;
437                             }
438
439                             processedFiles.push_back(strdup(realpathBuf));
440
441                             file = fopen(nodeFilePath.c_str(), "r");
442                             if(file == NULL){
443                                 error(tagNode->lineNum, fileName, "failed to open file '%s': %s", nodeFilePath.c_str(), strerror(errno));
444                                 continue;
445                             }
446                             //free(fileName);
447                             fileName = strdup(realpathBuf);
448                             from = nodeTree.end(); from--;
449                             
450                             if(tagNode->parentNode){
451                                 DOTCONFDocumentNode * nd = tagNode->parentNode->childNode;
452                                 while(nd){
453                                     if(!nd->nextNode)
454                                         break;
455                                     nd = nd->nextNode;
456                                 }
457
458                                 curPrev = nd;
459                             }
460                             ret = parseFile(tagNode->parentNode);
461                             
462                             //ret = parseFile(tagNode->parentNode);
463                             (void) fclose(file);
464                             if(ret == -1)
465                                 continue;
466                             if(checkConfig(++from) == -1){
467                                 continue;
468                             }
469                         }
470                     }
471                     globfree(&globBuf);
472                     vi++;
473                 }
474             }
475         }
476         /*
477         if( (ret = checkConfig(nodeTree.begin())) == -1){
478             return -1;
479         }
480         */
481
482         if(!requiredOptions.empty())
483             ret = checkRequiredOptions();
484     }
485
486     return ret;
487 }
488
489 int DOTCONFDocument::checkRequiredOptions()
490 {
491     for(std::list<char*>::const_iterator ci = requiredOptions.begin(); ci != requiredOptions.end(); ci++){
492         bool matched = false;
493         for(std::list<DOTCONFDocumentNode*>::iterator i = nodeTree.begin(); i!=nodeTree.end(); i++){            
494             if(!cmp_func((*i)->name, *ci)){
495                 matched = true;
496                 break;
497             }
498         }
499         if(!matched){
500             error(0, NULL, "required option '%s' not specified", *ci);
501             return -1;
502         }
503     }
504     return 0;
505 }
506
507 void DOTCONFDocument::error(int lineNum, const char * fileName, const char * fmt, ...)
508 {
509     va_list args;
510     va_start(args, fmt);
511
512     char msg[256];
513
514     vsnprintf(msg, 256, fmt, args);
515
516     size_t len = (lineNum!=0?strlen(fileName):0) + strlen(msg) + 50;
517     char * buf = (char*)mempool->alloc(len);
518
519     if(lineNum)
520         (void) snprintf(buf, len, "DOTCONF++: file '%s', line %d: %s\n", fileName, lineNum, msg);
521     else
522         (void) snprintf(buf, len, "DOTCONF++: file '%s':  %s\n", fileName, msg);
523
524     if (errorCallback) {
525         errorCallback(errorCallbackData, buf);
526     } else {
527         (void) vfprintf(stderr, buf, args);
528     }
529
530     va_end(args);
531 }
532
533 char * DOTCONFDocument::getSubstitution(char * macro, int lineNum)
534 {
535     char * buf = NULL;
536     char * variable = macro+2;
537
538     char * endBr = strchr(macro, '}');
539
540     if(!endBr){
541         error(lineNum, fileName, "unterminated '{'");
542         return NULL;
543     }
544     *endBr = 0;
545
546     char * defaultValue = strchr(variable, ':');
547
548     if(defaultValue){
549         *defaultValue++ = 0;
550         if(*defaultValue != '-'){
551             error(lineNum, fileName, "incorrect macro substitution syntax");
552             return NULL;
553         }
554         defaultValue++;
555         if(*defaultValue == '"' || *defaultValue == '\''){
556             defaultValue++;
557             defaultValue[strlen(defaultValue)-1] = 0;
558         }
559     } else {
560         defaultValue = NULL;
561     }
562
563     char * subs = getenv(variable);
564     if( subs ){
565         buf = mempool->strdup(subs);
566     } else {
567         std::list<DOTCONFDocumentNode*>::iterator i = nodeTree.begin();
568         DOTCONFDocumentNode * tagNode = NULL;
569         for(; i!=nodeTree.end(); i++){            
570             tagNode = *i;
571             if(!cmp_func(tagNode->name, variable)){
572                 if(tagNode->valuesCount != 0){
573                     buf = mempool->strdup(tagNode->values[0]);
574                     break;
575                 }
576             }
577         }
578         if( i == nodeTree.end() ){
579             if( defaultValue ){
580                 buf = mempool->strdup(defaultValue);
581             } else {
582                 error(lineNum, fileName, "substitution not found and default value not given");
583                 return NULL;
584             }
585         }
586     }
587     return buf;
588 }
589
590 int DOTCONFDocument::macroSubstitute(DOTCONFDocumentNode * tagNode, int valueIndex)
591 {
592     int ret = 0;
593     char * macro = tagNode->values[valueIndex];
594     size_t valueLen = strlen(tagNode->values[valueIndex])+1;
595     char * value = (char*)mempool->alloc(valueLen);
596     char * v = value;
597     char * subs = NULL;
598
599     while(*macro){
600         if(*macro == '$' && *(macro+1) == '{'){
601             char * m = strchr(macro, '}');
602             subs = getSubstitution(macro, tagNode->lineNum);
603             if(subs == NULL){
604                 ret = -1;
605                 break;
606             }
607             macro = m + 1;
608             *v = 0;
609             v = (char*)mempool->alloc(strlen(value)+strlen(subs)+valueLen);
610             strcpy(v, value);
611             value = strcat(v, subs);
612             v = value + strlen(value);
613             continue;
614         }
615         *v++ = *macro++;
616     }
617     *v = 0;
618
619     free(tagNode->values[valueIndex]);
620     tagNode->values[valueIndex] = strdup(value);
621     return ret;
622 }
623
624 const DOTCONFDocumentNode * DOTCONFDocument::getFirstNode() const
625 {
626     if ( !nodeTree.empty() ) {
627         return *nodeTree.begin();
628     } else {
629         return NULL;
630     }
631 }
632
633 const DOTCONFDocumentNode * DOTCONFDocument::findNode(const char * nodeName, const DOTCONFDocumentNode * parentNode, const DOTCONFDocumentNode * startNode) const
634 {
635     //printf("nodeName=%s, cont=%s, start=%s\n", nodeName, containingNode!=NULL?containingNode->name:"NULL", startNode!=NULL?startNode->name:"NULL");
636     
637     std::list<DOTCONFDocumentNode*>::const_iterator i = nodeTree.begin();
638
639     if(startNode == NULL)
640         startNode = parentNode;
641
642     if(startNode != NULL){
643         while( i != nodeTree.end() && (*i) != startNode ){
644             i++;
645         }
646         if( i != nodeTree.end() ) i++;
647     }
648
649     for(; i!=nodeTree.end(); i++){
650         //if(parentNode != NULL && (*i)->parentNode != parentNode){
651         if((*i)->parentNode != parentNode){
652             continue;
653         }
654         if(!cmp_func(nodeName, (*i)->name)){
655             return *i;
656         }
657     }
658     return NULL;
659 }
660
661 void DOTCONFDocument::setRequiredOptionNames(const char ** requiredOptionNames)
662 {
663     while(*requiredOptionNames){
664         requiredOptions.push_back(strdup( *requiredOptionNames ));
665         requiredOptionNames++;
666     }
667 }
668