blob: 102231f0eacba3127425fd6f3eec340b36f7c109 [file] [log] [blame]
/*
* Copyright 1999-2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/***************************************************************************
* Description: DSAPI plugin for Lotus Domino *
* Author: Andy Armstrong <andy@tagish.com> *
* Date: 20010603 *
* Version: $Revision$ *
***************************************************************************/
#include "config.h"
#include "inifile.h"
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
/* We have one of these for each ini file line. Once we've read the
* file and parsed it we'll have an array in key order containing one
* of these for each configuration item in file.
*/
typedef struct
{
const char *key;
const char *value;
} inifile_key;
static char *file; /* the text of the ini file */
static inifile_key *keys; /* an array of keys, one per item */
static size_t klen; /* length of the key array */
/* Text that will prefix all of our error messages */
#define ERRPFX "INIFILE: "
/* Various error messages that we can return */
ERRTYPE inifile_outofmemory = ERRPFX "Out of memory";
ERRTYPE inifile_filenotfound = ERRPFX "File not found";
ERRTYPE inifile_readerror = ERRPFX "Error reading file";
#define SYNFMT ERRPFX "File %s, line %d: %s"
/* Case insensitive string comparison, works like strcmp() */
static int inifile__stricmp(const char *s1, const char *s2)
{
while (*s1 && tolower(*s1) == tolower(*s2))
s1++, s2++;
return tolower(*s1) - tolower(*s2);
}
/* Compare keys, suitable for passing to qsort() */
static int inifile__cmp(const void *k1, const void *k2)
{
const inifile_key *kk1 = (const inifile_key *) k1;
const inifile_key *kk2 = (const inifile_key *) k2;
return inifile__stricmp(kk1->key, kk2->key);
}
/* Return a new syntax error message. */
static ERRTYPE inifile__syntax(jk_pool_t *p, const char *file, int line, const char *msg)
{
static const char synfmt[] = SYNFMT;
size_t len = sizeof(synfmt) + strlen(msg) + strlen(file) + 10 /* fudge for line number */;
char *buf = jk_pool_alloc(p, len);
sprintf(buf, synfmt, file, line, msg);
return buf;
}
/* Various macros to tidy up the parsing code */
/* Characters that are OK in the keyname */
#define KEYCHR(c) \
(isalnum(c) || (c) == '.' || (c) == '_')
/* Skip whitespace */
#define SKIPSPC() \
while (*fp == '\t' || *fp == ' ') fp++
/* Skip to the end of the current line */
#define SKIPLN() \
while (*fp != '\0' && *fp != '\r' && *fp != '\n') fp++
/* Move from the end of the current line to the start of the next, learning what the
* newline character is and counting lines
*/
#define NEXTLN() \
do { while (*fp == '\r' || *fp == '\n') { if (nlc == -1) nlc = *fp; if (*fp == nlc) ln++; fp++; } } while (0)
/* Build the index. Called when the inifile is loaded by inifile_load()
*/
static ERRTYPE inifile__index(jk_pool_t *p, const char *name)
{
int pass;
int ln = 1;
int nlc = -1;
/* Make two passes over the data. First time we're just counting
* the lines that contain values so we can allocate the index, second
* time we build the index.
*/
for (pass = 0; pass < 2; pass++)
{
char *fp = file;
char *ks = NULL, *ke; /* key start, end */
char *vs = NULL, *ve; /* value start, end */
klen = 0;
while (*fp != '\0')
{
SKIPSPC();
/* turn a comment into an empty line by moving to the next \r|\n */
if (*fp == '#' || *fp == ';')
SKIPLN();
if (*fp != '\0' && *fp != '\r' && *fp != '\n')
{
ks = fp; /* start of key */
while (KEYCHR(*fp)) fp++;
ke = fp; /* end of key */
SKIPSPC();
if (*fp != '=')
return inifile__syntax(p, name, ln, "Missing '=' or illegal character in key");
fp++; /* past the = */
SKIPSPC();
vs = fp;
SKIPLN();
ve = fp;
/* back up over any trailing space */
while (ve > vs && (ve[-1] == ' ' || ve[-1] == '\t')) ve--;
NEXTLN(); /* move forwards *before* we trash the eol characters */
if (NULL != keys) /* second pass? if so stash a pointer */
{
*ke = '\0';
*ve = '\0';
keys[klen].key = ks;
keys[klen].value = vs;
}
klen++;
}
else
{
NEXTLN();
}
}
if (NULL == keys && (keys = jk_pool_alloc(p, sizeof(inifile_key) * klen), NULL == keys))
return inifile_outofmemory;
}
/* got the index now, sort it so we can search it quickly */
qsort(keys, klen, sizeof(inifile_key), inifile__cmp);
return ERRNONE;
}
/* Read an INI file from disk
*/
ERRTYPE inifile_read(jk_pool_t *p, const char *name)
{
FILE *fl;
size_t flen;
int ok;
if (fl = fopen(name, "rb"), NULL == fl)
return inifile_filenotfound;
fseek(fl, 0L, SEEK_END);
flen = (size_t) ftell(fl);
fseek(fl, 0L, SEEK_SET);
/* allocate one extra byte for trailing \0
*/
if (file = jk_pool_alloc(p, flen+1), NULL == file)
{
fclose(fl);
return inifile_outofmemory;
}
ok = (fread(file, flen, 1, fl) == 1);
fclose(fl);
if (!ok) return inifile_readerror;
file[flen] = '\0'; /* terminate it to simplify parsing */
return inifile__index(p, name);
}
/* Find the value associated with the given key returning it or NULL
* if no match is found. Key name matching is case insensitive.
*/
const char *inifile_lookup(const char *key)
{
int lo, mid, hi, cmp;
if (NULL == keys)
return NULL;
for (lo = 0, hi = klen-1; lo <= hi; )
{
mid = (lo + hi) / 2;
cmp = inifile__stricmp(key, keys[mid].key);
if (cmp < 0) /* key in array is greater */
hi = mid-1;
else if (cmp > 0)
lo = mid+1;
else
return keys[mid].value;
}
return NULL;
}
#ifdef TEST
static jk_pool_t pool;
extern void jk_dump_pool(jk_pool_t *p, FILE *f); /* not declared in header */
int main(void)
{
ERRTYPE e;
unsigned k;
int rc = 0;
jk_open_pool(&pool, NULL, 0);
e = inifile_read(&pool, "ok.ini");
if (e == ERRNONE)
{
printf("%u keys in ok.ini\n", klen);
for (k = 0; k < klen; k++)
{
const char *val = inifile_lookup(keys[k].key);
printf("Key: \"%s\", value: \"%s\"\n", keys[k].key, val);
}
}
else
{
printf("Error reading ok.ini: %s\n", e);
rc = 1;
}
e = inifile_read(&pool, "bad.ini");
if (e == ERRNONE)
{
printf("%u keys in bad.ini\n", klen);
for (k = 0; k < klen; k++)
{
const char *val = inifile_lookup(keys[k].key);
printf("Key: \"%s\", value: \"%s\"\n", keys[k].key, val);
}
rc = 1; /* should be a syntax error */
}
else
{
printf("Error reading bad.ini: %s (which is OK: that's what we expected)\n", e);
}
jk_dump_pool(&pool, stdout);
jk_close_pool(&pool);
return rc;
}
#endif