#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include "config.h"
#include "inifile.hh"
#define MAX_EXTEND_LINES 20
static bool check_line_endings(const char *s) {
if(!s) return false;
for(; *s; s++ ) {
if(*s == '\r') {
char c = s[1];
if(c == '\n' || c == '\0') {
static bool warned = 0;
if(!warned)
fprintf(stderr, "inifile: warning: File contains DOS-style line endings.\n");
warned = true;
continue;
}
fprintf(stderr, "inifile: error: File contains ambiguous carriage returns\n");
return true;
}
}
return false;
}
IniFile::IniFile(int _errMask, FILE *_fp)
{
fp = _fp;
errMask = _errMask;
owned = false;
if(fp != NULL)
LockFile();
}
bool
IniFile::Open(const char *file)
{
char path[LINELEN] = "";
if(IsOpen()) Close();
TildeExpansion(file, path, sizeof(path));
if((fp = fopen(path, "r")) == NULL)
return(false);
owned = true;
if(!LockFile())
return(false);
return(true);
}
bool
IniFile::Close()
{
int rVal = 0;
if(fp != NULL){
lock.l_type = F_UNLCK;
fcntl(fileno(fp), F_SETLKW, &lock);
if(owned)
rVal = fclose(fp);
fp = NULL;
}
return(rVal == 0);
}
IniFile::ErrorCode
IniFile::Find(int *result, StrIntPair *pPair,
const char *tag, const char *section, int num, int *lineno)
{
const char *pStr;
int tmp;
if((pStr = Find(tag, section, num)) == NULL){
if (lineno)
*lineno = 0;
return(ERR_TAG_NOT_FOUND);
}
if(sscanf(pStr, "%i", &tmp) == 1){
*result = tmp;
if (lineno)
*lineno = lineNo;
return(ERR_NONE);
}
while(pPair->pStr != NULL){
if(strcasecmp(pStr, pPair->pStr) == 0){
*result = pPair->value;
if (lineno)
*lineno = lineNo;
return(ERR_NONE);
}
pPair++;
}
ThrowException(ERR_CONVERSION);
return(ERR_CONVERSION);
}
IniFile::ErrorCode
IniFile::Find(double *result, StrDoublePair *pPair,
const char *tag, const char *section, int num, int *lineno)
{
const char *pStr;
double tmp;
if((pStr = Find(tag, section, num)) == NULL){
if (lineno)
*lineno = 0;
return(ERR_TAG_NOT_FOUND);
}
if(sscanf(pStr, "%lf", &tmp) == 1){
if (lineno)
*lineno = lineNo;
*result = tmp;
if (lineno)
*lineno = lineNo;
return(ERR_NONE);
}
while(pPair->pStr != NULL){
if(strcasecmp(pStr, pPair->pStr) == 0){
*result = pPair->value;
if (lineno)
*lineno = lineNo;
return(ERR_NONE);
}
pPair++;
}
ThrowException(ERR_CONVERSION);
return(ERR_CONVERSION);
}
const char *
IniFile::Find(const char *_tag, const char *_section, int _num, int *lineno)
{
static char line[LINELEN + 2] = "";
char bracketSection[LINELEN + 2] = "";
char *nonWhite;
int newLinePos;
int len;
char tagEnd;
char *valueString;
char *endValueString;
char eline [(LINELEN + 2) * (MAX_EXTEND_LINES + 1)];
char* elineptr;
char* elinenext;
int extend_ct = 0;
lineNo = 0;
tag = _tag;
section = _section;
num = _num;
if(!CheckIfOpen())
return(NULL);
rewind(fp);
if(section != NULL){
sprintf(bracketSection, "[%s]", section);
while (!feof(fp)) {
if (NULL == fgets(line, LINELEN + 1, fp)) {
ThrowException(ERR_SECTION_NOT_FOUND);
return(NULL);
}
if(check_line_endings(line)) {
ThrowException(ERR_CONVERSION);
return(NULL);
}
lineNo++;
newLinePos = strlen(line) - 1;
if (newLinePos < 0) {
newLinePos = 0;
}
if (line[newLinePos] == '\n') {
line[newLinePos] = 0;
}
if (NULL == (nonWhite = SkipWhite(line))) {
continue;
}
if (strncmp(bracketSection, nonWhite, strlen(bracketSection)) != 0){
continue;
}
break;
}
}
while (!feof(fp)) {
if (NULL == fgets(line, LINELEN + 1, (FILE *) fp)) {
ThrowException(ERR_TAG_NOT_FOUND);
return(NULL);
}
if(check_line_endings(line)) {
ThrowException(ERR_CONVERSION);
return(NULL);
}
lineNo++;
newLinePos = strlen(line) - 1;
if (newLinePos < 0) {
newLinePos = 0;
}
if (line[newLinePos] == '\n') {
line[newLinePos] = 0;
}
if (newLinePos > 0 && line[newLinePos-1] == '\\') {
newLinePos = newLinePos-1;
line[newLinePos] = 0;
if (!extend_ct) {
elineptr = (char*)eline; strncpy(elineptr,line,newLinePos);
elinenext = elineptr + newLinePos;
} else {
strncpy(elinenext,line,newLinePos);
elinenext = elinenext + newLinePos;
}
*elinenext = 0;
extend_ct++;
if (extend_ct > MAX_EXTEND_LINES) {
fprintf(stderr,
"INIFILE lineno=%d:Too many backslash line extends (limit=%d)\n",
lineNo, MAX_EXTEND_LINES);
ThrowException(ERR_OVER_EXTENDED);
return(NULL);
}
continue; } else {
if (extend_ct) {
strncpy(elinenext,line,newLinePos);
elinenext = elinenext + newLinePos;
*elinenext = 0;
}
}
if (!extend_ct) {
elineptr = (char*)line;
}
extend_ct = 0;
if (NULL == (nonWhite = SkipWhite(elineptr))) {
continue;
}
if (NULL != section && nonWhite[0] == '[') {
ThrowException(ERR_TAG_NOT_FOUND);
return(NULL);
}
len = strlen(tag);
if (strncmp(tag, nonWhite, len) != 0) {
continue;
}
tagEnd = nonWhite[len];
if (tagEnd == ' ' || tagEnd == '\r' || tagEnd == '\t'
|| tagEnd == '\n' || tagEnd == '=') {
if (--_num > 0) {
continue;
}
nonWhite += len;
valueString = AfterEqual(nonWhite);
if (NULL == valueString) {
ThrowException(ERR_TAG_NOT_FOUND);
return(NULL);
}
endValueString = valueString + strlen(valueString) - 1;
while (*endValueString == ' ' || *endValueString == '\t'
|| *endValueString == '\r') {
*endValueString = 0;
endValueString--;
}
if (lineno)
*lineno = lineNo;
return(valueString);
}
}
ThrowException(ERR_TAG_NOT_FOUND);
return(NULL);
}
const char *
IniFile::FindString(char *dest, size_t n, const char *_tag, const char *_section, int _num, int *lineno)
{
const char *res = Find(_tag, _section, _num, lineno);
if(res == NULL)
return res;
int r = snprintf(dest, n, "%s", res);
if(r < 0 || (size_t)r >= n) {
ThrowException(ERR_CONVERSION);
return NULL;
}
return dest;
}
const char *
IniFile::FindPath(char *dest, size_t n, const char *_tag, const char *_section, int _num, int *lineno)
{
const char *res = Find(_tag, _section, _num, lineno);
if(!res)
return res;
if(TildeExpansion(res, dest, n)) {
return 0;
}
return dest;
}
bool
IniFile::CheckIfOpen(void)
{
if(IsOpen())
return(true);
ThrowException(ERR_NOT_OPEN);
return(false);
}
bool
IniFile::LockFile(void)
{
lock.l_type = F_RDLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
if(fcntl(fileno(fp), F_SETLK, &lock) == -1){
if(owned)
fclose(fp);
fp = NULL;
return(false);
}
return(true);
}
IniFile::ErrorCode
IniFile::TildeExpansion(const char *file, char *path, size_t size)
{
char *home;
int res = snprintf(path, size, "%s", file);
if(res < 0 || (size_t)res >= size)
return ERR_CONVERSION;
if (strlen(file) < 2 || !(file[0] == '~' && file[1] == '/')) {
return ERR_NONE;
}
home = getenv("HOME");
if (!home) {
ThrowException(ERR_CONVERSION);
return ERR_CONVERSION;
}
res = snprintf(path, size, "%s%s", home, file + 1);
if(res < 0 || (size_t)res >= size) {
ThrowException(ERR_CONVERSION);
return ERR_CONVERSION;
}
return ERR_NONE;
}
int
TildeExpansion(const char *file, char *path, size_t size)
{
static IniFile f;
return !f.TildeExpansion(file, path, size);
}
void
IniFile::ThrowException(ErrorCode errCode)
{
if(errCode & errMask){
exception.errCode = errCode;
exception.tag = tag;
exception.section = section;
exception.num = num;
exception.lineNo = lineNo;
throw(exception);
}
}
char *
IniFile::AfterEqual(const char *string)
{
const char *spot = string;
for (;;) {
if (*spot == '=') {
for (;;) {
spot++;
if (0 == *spot) {
return(NULL);
} else if (*spot != ' ' && *spot != '\t' && *spot != '\r'
&& *spot != '\n') {
return((char *)spot);
} else {
continue;
}
}
} else if (*spot == 0) {
return(NULL);
} else {
spot++;
continue;
}
}
}
char *
IniFile::SkipWhite(const char *string)
{
while(true){
if (*string == 0) {
return(NULL);
}
if ((*string == ';') || (*string == '#')) {
return(NULL);
}
if (*string != ' ' && *string != '\t' && *string != '\r'
&& *string != '\n') {
return((char *)string);
}
string++;
}
}
void
IniFile::Exception::Print(FILE *fp)
{
const char *msg;
switch(errCode){
case ERR_NONE:
msg = "ERR_NONE";
break;
case ERR_NOT_OPEN:
msg = "ERR_NOT_OPEN";
break;
case ERR_SECTION_NOT_FOUND:
msg = "ERR_SECTION_NOT_FOUND";
break;
case ERR_TAG_NOT_FOUND:
msg = "ERR_TAG_NOT_FOUND";
break;
case ERR_CONVERSION:
msg = "ERR_CONVERSION";
break;
case ERR_LIMITS:
msg = "ERR_LIMITS";
break;
case ERR_OVER_EXTENDED:
msg = "ERR_OVER_EXTENDED";
break;
default:
msg = "UNKNOWN";
}
fprintf(fp, "INIFILE: %s, section=%s, tag=%s, num=%d, lineNo=%d\n",
msg, section, tag, num, lineNo);
}
extern "C" const char *
iniFind(FILE *fp, const char *tag, const char *section)
{
IniFile f(false, fp);
return(f.Find(tag, section));
}
extern "C" const int
iniFindInt(FILE *fp, const char *tag, const char *section, int *result)
{
IniFile f(false, fp);
return(f.Find(result, tag, section));
}
extern "C" const int
iniFindDouble(FILE *fp, const char *tag, const char *section, double *result)
{
IniFile f(false, fp);
return(f.Find(result, tag, section));
}