This will prevent unexpected redefines and unrelated files build problems. --HG-- branch : trunk
602 lines
17 KiB
C++
602 lines
17 KiB
C++
|
|
#include "Common.h"
|
|
|
|
#include "dotconfpp.h"
|
|
|
|
#ifdef WIN32
|
|
#define PATH_MAX _MAX_PATH
|
|
#define strcasecmp stricmp
|
|
#define realpath(path,resolved_path) _fullpath(resolved_path, path, _MAX_PATH)
|
|
#include <io.h>
|
|
#else
|
|
#include <unistd.h>
|
|
#include <limits.h>
|
|
#include <stdint.h>
|
|
#include <strings.h>
|
|
#endif
|
|
|
|
#if !defined(R_OK)
|
|
#define R_OK 04
|
|
#endif
|
|
|
|
DOTCONFDocumentNode::DOTCONFDocumentNode():previousNode(NULL), nextNode(NULL), parentNode(NULL), childNode(NULL),
|
|
values(NULL), valuesCount(0),
|
|
name(NULL), lineNum(0), fileName(NULL), closed(true)
|
|
{
|
|
}
|
|
|
|
DOTCONFDocumentNode::~DOTCONFDocumentNode()
|
|
{
|
|
free(name);
|
|
if(values != NULL){
|
|
for(int i = 0 ; i < valuesCount; i++){
|
|
free(values[i]);
|
|
}
|
|
free(values);
|
|
}
|
|
}
|
|
|
|
void DOTCONFDocumentNode::pushValue(char * _value)
|
|
{
|
|
++valuesCount;
|
|
values = (char**)realloc(values, valuesCount*sizeof(char*));
|
|
values[valuesCount-1] = strdup(_value);
|
|
}
|
|
|
|
const char* DOTCONFDocumentNode::getValue(int index) const
|
|
{
|
|
if(index >= valuesCount){
|
|
return NULL;
|
|
}
|
|
return values[index];
|
|
}
|
|
|
|
DOTCONFDocument::DOTCONFDocument(DOTCONFDocument::CaseSensitive caseSensitivity):
|
|
mempool(NULL),
|
|
curParent(NULL), curPrev(NULL), curLine(0), file(NULL), fileName(NULL)
|
|
{
|
|
if(caseSensitivity == CASESENSETIVE){
|
|
cmp_func = strcmp;
|
|
} else {
|
|
cmp_func = strcasecmp;
|
|
}
|
|
|
|
mempool = new AsyncDNSMemPool(1024);
|
|
mempool->initialize();
|
|
}
|
|
|
|
DOTCONFDocument::~DOTCONFDocument()
|
|
{
|
|
for(std::list<DOTCONFDocumentNode*>::iterator i = nodeTree.begin(); i != nodeTree.end(); ++i){
|
|
delete(*i);
|
|
}
|
|
for(std::list<char*>::iterator i = requiredOptions.begin(); i != requiredOptions.end(); ++i){
|
|
free(*i);
|
|
}
|
|
for(std::list<char*>::iterator i = processedFiles.begin(); i != processedFiles.end(); ++i){
|
|
free(*i);
|
|
}
|
|
free(fileName);
|
|
delete mempool;
|
|
}
|
|
|
|
int DOTCONFDocument::cleanupLine(char * line)
|
|
{
|
|
char * start = line;
|
|
char * bg = line;
|
|
bool multiline = false;
|
|
bool concat = false;
|
|
char * word = NULL;
|
|
|
|
if(!words.empty() && quoted)
|
|
concat = true;
|
|
|
|
while(*line){
|
|
if((*line == '#' || *line == ';') && !quoted){
|
|
*bg = 0;
|
|
if(strlen(start)){
|
|
|
|
if(concat){
|
|
word = (char*)mempool->alloc(strlen(words.back())+strlen(start)+1);
|
|
strcpy(word, words.back());
|
|
strcat(word, start);
|
|
words.pop_back();
|
|
concat = false;
|
|
} else {
|
|
word = mempool->strdup(start);
|
|
}
|
|
words.push_back(word);
|
|
}
|
|
break;
|
|
}
|
|
if(*line == '=' && !quoted){
|
|
*line = ' ';continue;
|
|
}
|
|
|
|
// Allowing \" in there causes problems with directory paths
|
|
// like "C:\TrinIty\"
|
|
//if(*line == '\\' && (*(line+1) == '"' || *(line+1) == '\'')){
|
|
if(*line == '\\' && (*(line+1) == '\'')) {
|
|
*bg++ = *(line+1);
|
|
line+=2; continue;
|
|
}
|
|
if(*line == '\\' && *(line+1) == 'n'){
|
|
*bg++ = '\n';
|
|
line+=2; continue;
|
|
}
|
|
if(*line == '\\' && *(line+1) == 'r'){
|
|
*bg++ = '\r';
|
|
line+=2; continue;
|
|
}
|
|
if(*line == '\\' && (*(line+1) == '\n' || *(line+1) == '\r')){
|
|
*bg = 0;
|
|
if(strlen(start)){
|
|
|
|
if(concat){
|
|
word = (char*)mempool->alloc(strlen(words.back())+strlen(start)+1);
|
|
strcpy(word, words.back());
|
|
strcat(word, start);
|
|
words.pop_back();
|
|
concat = false;
|
|
} else {
|
|
word = mempool->strdup(start);
|
|
}
|
|
words.push_back(word);
|
|
}
|
|
multiline = true;
|
|
break;
|
|
}
|
|
if(*line == '"' || *line == '\''){
|
|
quoted = !quoted;
|
|
++line; continue;
|
|
}
|
|
if(isspace((unsigned char)*line) && !quoted){
|
|
*bg++ = 0;
|
|
if(strlen(start)){
|
|
|
|
if(concat){
|
|
word = (char*)mempool->alloc(strlen(words.back())+strlen(start)+1);
|
|
strcpy(word, words.back());
|
|
strcat(word, start);
|
|
words.pop_back();
|
|
concat = false;
|
|
} else {
|
|
word = mempool->strdup(start);
|
|
}
|
|
words.push_back(word);
|
|
}
|
|
start = bg;
|
|
while(isspace((unsigned char)*++line)) {}
|
|
|
|
continue;
|
|
}
|
|
*bg++ = *line++;
|
|
}
|
|
|
|
if(quoted && !multiline){
|
|
error(curLine, fileName, "unterminated quote");
|
|
return -1;
|
|
}
|
|
|
|
return multiline?1:0;
|
|
}
|
|
|
|
int DOTCONFDocument::parseLine()
|
|
{
|
|
char * word = NULL;
|
|
char * nodeName = NULL;
|
|
char * nodeValue = NULL;
|
|
DOTCONFDocumentNode * tagNode = NULL;
|
|
bool newNode = false;
|
|
|
|
for(std::list<char*>::iterator i = words.begin(); i != words.end(); ++i) {
|
|
word = *i;
|
|
|
|
if(*word == '<'){
|
|
newNode = true;
|
|
}
|
|
|
|
if(newNode){
|
|
nodeValue = NULL;
|
|
nodeName = NULL;
|
|
newNode = false;
|
|
}
|
|
|
|
size_t wordLen = strlen(word);
|
|
if(word[wordLen-1] == '>'){
|
|
word[wordLen-1] = 0;
|
|
newNode = true;
|
|
}
|
|
|
|
if(nodeName == NULL){
|
|
nodeName = word;
|
|
bool closed = true;
|
|
if(*nodeName == '<'){
|
|
if(*(nodeName+1) != '/'){
|
|
++nodeName;
|
|
closed = false;
|
|
} else {
|
|
nodeName+=2;
|
|
std::list<DOTCONFDocumentNode*>::reverse_iterator itr=nodeTree.rbegin();
|
|
for(; itr!=nodeTree.rend(); ++itr){
|
|
if(!cmp_func(nodeName, (*itr)->name) && !(*itr)->closed){
|
|
(*itr)->closed = true;
|
|
curParent = (*itr)->parentNode;
|
|
curPrev = *itr;
|
|
break;
|
|
}
|
|
}
|
|
if(itr==nodeTree.rend()){
|
|
error(curLine, fileName, "not matched closing tag </%s>", nodeName);
|
|
return -1;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
tagNode = new DOTCONFDocumentNode;
|
|
tagNode->name = strdup(nodeName);
|
|
tagNode->document = this;
|
|
tagNode->fileName = processedFiles.back();
|
|
tagNode->lineNum = curLine;
|
|
tagNode->closed = closed;
|
|
if(!nodeTree.empty()){
|
|
DOTCONFDocumentNode * prev = nodeTree.back();
|
|
if(prev->closed){
|
|
|
|
curPrev->nextNode = tagNode;
|
|
tagNode->previousNode = curPrev;
|
|
tagNode->parentNode = curParent;
|
|
|
|
} else {
|
|
prev->childNode = tagNode;
|
|
tagNode->parentNode = prev;
|
|
curParent = prev;
|
|
}
|
|
}
|
|
nodeTree.push_back(tagNode);
|
|
curPrev = tagNode;
|
|
} else {
|
|
nodeValue = word;
|
|
tagNode->pushValue(nodeValue);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
int DOTCONFDocument::parseFile(DOTCONFDocumentNode * _parent)
|
|
{
|
|
char str[512];
|
|
int ret = 0;
|
|
curLine = 0;
|
|
curParent = _parent;
|
|
|
|
quoted = false;
|
|
size_t slen = 0;
|
|
|
|
while(fgets(str, 511, file)){
|
|
++curLine;
|
|
slen = strlen(str);
|
|
if( slen >= 510 ){
|
|
error(curLine, fileName, "warning: line too long");
|
|
}
|
|
if(str[slen-1] != '\n'){
|
|
str[slen] = '\n';
|
|
str[slen+1] = 0;
|
|
}
|
|
if((ret = cleanupLine(str)) == -1){
|
|
break;
|
|
}
|
|
if(ret == 0){
|
|
if(!words.empty()){
|
|
ret = parseLine();
|
|
mempool->free();
|
|
words.clear();
|
|
if(ret == -1){
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int DOTCONFDocument::checkConfig(const std::list<DOTCONFDocumentNode*>::iterator & from)
|
|
{
|
|
int ret = 0;
|
|
|
|
DOTCONFDocumentNode * tagNode = NULL;
|
|
int vi = 0;
|
|
for(std::list<DOTCONFDocumentNode*>::iterator i = from; i != nodeTree.end(); ++i){
|
|
tagNode = *i;
|
|
if(!tagNode->closed){
|
|
error(tagNode->lineNum, tagNode->fileName, "unclosed tag %s", tagNode->name);
|
|
ret = -1;
|
|
break;
|
|
}
|
|
vi = 0;
|
|
while( vi < tagNode->valuesCount ){
|
|
|
|
if(strstr(tagNode->values[vi], "${") && strchr(tagNode->values[vi], '}') ){
|
|
ret = macroSubstitute(tagNode, vi );
|
|
mempool->free();
|
|
if(ret == -1){
|
|
break;
|
|
}
|
|
}
|
|
++vi;
|
|
}
|
|
if(ret == -1){
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int DOTCONFDocument::setContent(const char * _fileName)
|
|
{
|
|
int ret = 0;
|
|
char realpathBuf[PATH_MAX];
|
|
|
|
if(realpath(_fileName, realpathBuf) == NULL){
|
|
error(0, NULL, "realpath(%s) failed: %s", _fileName, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
fileName = strdup(realpathBuf);
|
|
|
|
processedFiles.push_back(strdup(realpathBuf));
|
|
|
|
if(( file = fopen(fileName, "r")) == NULL){
|
|
error(0, NULL, "failed to open file '%s': %s", fileName, strerror(errno));
|
|
return -1;
|
|
}
|
|
// Try read utf8 header and skip it if exist
|
|
uint32 utf8header = 0;
|
|
fgets((char*)&utf8header, 4, file); // Try read header
|
|
if (utf8header!=0x00BFBBEF) // If not exist
|
|
fseek(file, 0, SEEK_SET); // Reset read position
|
|
|
|
ret = parseFile();
|
|
|
|
(void) fclose(file);
|
|
|
|
if(!ret){
|
|
|
|
if( (ret = checkConfig(nodeTree.begin())) == -1){
|
|
return -1;
|
|
}
|
|
|
|
std::list<DOTCONFDocumentNode*>::iterator from;
|
|
DOTCONFDocumentNode * tagNode = NULL;
|
|
int vi = 0;
|
|
for(std::list<DOTCONFDocumentNode*>::iterator i = nodeTree.begin(); i!=nodeTree.end(); ++i){
|
|
tagNode = *i;
|
|
if(!cmp_func("DOTCONFPPIncludeFile", tagNode->name)){
|
|
vi = 0;
|
|
while( vi < tagNode->valuesCount ){
|
|
if(access(tagNode->values[vi], R_OK) == -1){
|
|
error(tagNode->lineNum, tagNode->fileName, "%s: %s", tagNode->values[vi], strerror(errno));
|
|
return -1;
|
|
}
|
|
if(realpath(tagNode->values[vi], realpathBuf) == NULL){
|
|
error(tagNode->lineNum, tagNode->fileName, "realpath(%s) failed: %s", tagNode->values[vi], strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
bool processed = false;
|
|
for(std::list<char*>::const_iterator itInode = processedFiles.begin(); itInode != processedFiles.end(); ++itInode){
|
|
if(!strcmp(*itInode, realpathBuf)){
|
|
processed = true;
|
|
break;
|
|
}
|
|
}
|
|
if(processed){
|
|
break;
|
|
}
|
|
|
|
processedFiles.push_back(strdup(realpathBuf));
|
|
|
|
file = fopen(tagNode->values[vi], "r");
|
|
if(file == NULL){
|
|
error(tagNode->lineNum, fileName, "failed to open file '%s': %s", tagNode->values[vi], strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
fileName = strdup(realpathBuf);
|
|
from = nodeTree.end(); --from;
|
|
|
|
ret = parseFile();
|
|
(void) fclose(file);
|
|
if(ret == -1)
|
|
return -1;
|
|
if(checkConfig(++from) == -1){
|
|
return -1;
|
|
}
|
|
++vi;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if(!requiredOptions.empty())
|
|
ret = checkRequiredOptions();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int DOTCONFDocument::checkRequiredOptions()
|
|
{
|
|
for(std::list<char*>::const_iterator ci = requiredOptions.begin(); ci != requiredOptions.end(); ++ci){
|
|
bool matched = false;
|
|
for(std::list<DOTCONFDocumentNode*>::iterator i = nodeTree.begin(); i!=nodeTree.end(); ++i){
|
|
if(!cmp_func((*i)->name, *ci)){
|
|
matched = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!matched){
|
|
error(0, NULL, "required option '%s' not specified", *ci);
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void DOTCONFDocument::error(int lineNum, const char * fileName_, const char * fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
|
|
size_t len = (lineNum!=0?strlen(fileName_):0) + strlen(fmt) + 50;
|
|
char * buf = (char*)mempool->alloc(len);
|
|
|
|
if(lineNum)
|
|
(void) snprintf(buf, len, "DOTCONF++: file '%s', line %d: %s\n", fileName_, lineNum, fmt);
|
|
else
|
|
(void) snprintf(buf, len, "DOTCONF++: %s\n", fmt);
|
|
|
|
(void) vfprintf(stderr, buf, args);
|
|
|
|
va_end(args);
|
|
}
|
|
|
|
char * DOTCONFDocument::getSubstitution(char * macro, int lineNum)
|
|
{
|
|
char * buf = NULL;
|
|
char * variable = macro+2;
|
|
|
|
char * endBr = strchr(macro, '}');
|
|
|
|
if(!endBr){
|
|
error(lineNum, fileName, "unterminated '{'");
|
|
return NULL;
|
|
}
|
|
*endBr = 0;
|
|
|
|
char * defaultValue = strchr(variable, ':');
|
|
|
|
if(defaultValue){
|
|
*defaultValue++ = 0;
|
|
if(*defaultValue != '-'){
|
|
error(lineNum, fileName, "incorrect macro substitution syntax");
|
|
return NULL;
|
|
}
|
|
++defaultValue;
|
|
if(*defaultValue == '"' || *defaultValue == '\''){
|
|
++defaultValue;
|
|
defaultValue[strlen(defaultValue)-1] = 0;
|
|
}
|
|
} else {
|
|
defaultValue = NULL;
|
|
}
|
|
|
|
char * subs = getenv(variable);
|
|
if( subs ){
|
|
buf = mempool->strdup(subs);
|
|
} else {
|
|
std::list<DOTCONFDocumentNode*>::iterator i = nodeTree.begin();
|
|
DOTCONFDocumentNode * tagNode = NULL;
|
|
for(; i!=nodeTree.end(); i++){
|
|
tagNode = *i;
|
|
if(!cmp_func(tagNode->name, variable)){
|
|
if(tagNode->valuesCount != 0){
|
|
buf = mempool->strdup(tagNode->values[0]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if( i == nodeTree.end() ){
|
|
if( defaultValue ){
|
|
buf = mempool->strdup(defaultValue);
|
|
} else {
|
|
error(lineNum, fileName, "substitution not found and default value not given");
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
int DOTCONFDocument::macroSubstitute(DOTCONFDocumentNode * tagNode, int valueIndex)
|
|
{
|
|
int ret = 0;
|
|
char * macro = tagNode->values[valueIndex];
|
|
size_t valueLen = strlen(tagNode->values[valueIndex])+1;
|
|
char * value = (char*)mempool->alloc(valueLen);
|
|
char * v = value;
|
|
char * subs = NULL;
|
|
|
|
while(*macro){
|
|
if(*macro == '$' && *(macro+1) == '{'){
|
|
char * m = strchr(macro, '}');
|
|
subs = getSubstitution(macro, tagNode->lineNum);
|
|
if(subs == NULL){
|
|
ret = -1;
|
|
break;
|
|
}
|
|
macro = m + 1;
|
|
*v = 0;
|
|
v = (char*)mempool->alloc(strlen(value)+strlen(subs)+valueLen);
|
|
strcpy(v, value);
|
|
value = strcat(v, subs);
|
|
v = value + strlen(value);
|
|
continue;
|
|
}
|
|
*v++ = *macro++;
|
|
}
|
|
*v = 0;
|
|
|
|
free(tagNode->values[valueIndex]);
|
|
tagNode->values[valueIndex] = strdup(value);
|
|
return ret;
|
|
}
|
|
|
|
const DOTCONFDocumentNode * DOTCONFDocument::getFirstNode() const
|
|
{
|
|
if ( !nodeTree.empty() ) {
|
|
return *nodeTree.begin();
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
const DOTCONFDocumentNode * DOTCONFDocument::findNode(const char * nodeName, const DOTCONFDocumentNode * parentNode, const DOTCONFDocumentNode * startNode) const
|
|
{
|
|
|
|
|
|
std::list<DOTCONFDocumentNode*>::const_iterator i = nodeTree.begin();
|
|
|
|
if(startNode == NULL)
|
|
startNode = parentNode;
|
|
|
|
if(startNode != NULL){
|
|
while( i != nodeTree.end() && (*i) != startNode ){
|
|
++i;
|
|
}
|
|
if( i != nodeTree.end() ) ++i;
|
|
}
|
|
|
|
for(; i!=nodeTree.end(); ++i){
|
|
|
|
if((*i)->parentNode != parentNode){
|
|
continue;
|
|
}
|
|
if(!cmp_func(nodeName, (*i)->name)){
|
|
return *i;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void DOTCONFDocument::setRequiredOptionNames(const char ** requiredOptionNames)
|
|
{
|
|
while(*requiredOptionNames){
|
|
requiredOptions.push_back(strdup( *requiredOptionNames ));
|
|
++requiredOptionNames;
|
|
}
|
|
}
|
|
|