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