513 lines
13 KiB
C
513 lines
13 KiB
C
#include <ctype.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "config.h"
|
|
#include "log.h"
|
|
#include "boolean.h"
|
|
#include "string.h"
|
|
#include "mem_debug.h"
|
|
|
|
|
|
static const int KEYS_PER_BLOCK=64;
|
|
|
|
|
|
static char **config_find_entry(config_t *cfg, const char *key);
|
|
static int config_rebuild_index (config_t *cfg);
|
|
static int config_iter_seek(config_iter_t *iter);
|
|
static void config_reset_content(config_t *cfg);
|
|
|
|
/* TODO add parsing state info/offsets for better error messages? */
|
|
/* TODO iff this is an ACTUAL performance problem, could qsort cfg->keys at
|
|
* completion then bsearch on lookup */
|
|
static int config_build_index(config_t *cfg)
|
|
{
|
|
/* we modify cfg->content, NULL-terminating keys and values */
|
|
char *c = cfg->content;
|
|
int pair_num=0;
|
|
|
|
/* scan through looking for comments and "key=value\n" strings. */
|
|
/* blank lines may contain whitespace, which is ignored (and line may be indented) */
|
|
while (*c!='\0')
|
|
{
|
|
if (*c=='\n')
|
|
{
|
|
c++;
|
|
continue;
|
|
}
|
|
if (*c=='#')
|
|
{
|
|
if((c=strchr(c, '\n'))==NULL)
|
|
{
|
|
real_log("config_build_index: un-terminated comment.\n");
|
|
return FALSE;
|
|
}
|
|
c++; /* move past the '\n' */
|
|
continue;
|
|
}
|
|
if (isalpha(*c))
|
|
{
|
|
/* if we have crossed a multiple of KEY_BLOCK_SIZE keys, grow by another 64. */
|
|
if (pair_num && pair_num%KEYS_PER_BLOCK==0)
|
|
{
|
|
size_t new_size = (pair_num+KEYS_PER_BLOCK)*sizeof(char *);
|
|
cfg->keys=(char **)realloc_d(cfg->keys,new_size,"config_build_index: cfg->keys"); /* XXX errors */
|
|
cfg->values=(char **)realloc_d(cfg->values,new_size,"config_build_index: cfg_values");
|
|
}
|
|
cfg->keys[pair_num] = c;
|
|
if((c=strchr(c,'='))==NULL)
|
|
{
|
|
real_log("config_build_index: value without '=' found.\n");
|
|
return FALSE;
|
|
}
|
|
*c='\0';
|
|
c++;
|
|
cfg->values[pair_num] = c;
|
|
if((c=strchr(c, '\n'))==NULL)
|
|
{
|
|
real_log("config_build_index: a value was found that was not terminated by new-line.\n");
|
|
return FALSE;
|
|
}
|
|
*c='\0';
|
|
c++;
|
|
pair_num++;
|
|
continue;
|
|
}
|
|
if(isspace(*c))
|
|
{
|
|
while(isspace(*c)) c++;
|
|
continue;
|
|
}
|
|
real_log("config_build_index: unexpected char '%c' at offset %d\n", *c, c-cfg->content);
|
|
return FALSE;
|
|
}
|
|
cfg->num_keys=pair_num;
|
|
return TRUE;
|
|
}
|
|
|
|
config_t *config_open_inmem(
|
|
const char *data
|
|
)
|
|
{
|
|
int success=FALSE;
|
|
config_t *cfg=NULL;
|
|
|
|
for(;;)
|
|
{
|
|
if ((cfg=(config_t *)malloc_d(sizeof(config_t),"config_open_inmem: config_t"))==NULL
|
|
|| !memset(cfg,0,sizeof(config_t))
|
|
|| (cfg->content=strdup_d(data, "config_open_inmem: cfg->content")) == NULL
|
|
|| (cfg->keys=malloc_d(KEYS_PER_BLOCK*sizeof(char *), "config_open_inmem: cfg->keys"))==NULL
|
|
|| (cfg->values=malloc_d(KEYS_PER_BLOCK*sizeof(char *), "config_open_inmem: cfg->values"))==NULL)
|
|
{
|
|
real_log("config_open_inmem: memory allocation failed for config_t\n");
|
|
break;
|
|
}
|
|
if (!config_build_index(cfg))
|
|
{
|
|
real_log("config_open_inmem: failed to build config index\n");
|
|
break;
|
|
}
|
|
cfg->content_bufsz = strlen(data) + 1;
|
|
success=TRUE;
|
|
break;
|
|
}
|
|
if(!success)
|
|
{
|
|
if (cfg) config_destroy(cfg);
|
|
return NULL;
|
|
}
|
|
return cfg;
|
|
}
|
|
|
|
|
|
|
|
config_t *config_open(
|
|
const char *file_name
|
|
)
|
|
{
|
|
int sz, success=FALSE;
|
|
config_t *cfg=NULL;
|
|
struct stat stats;
|
|
FILE *file=NULL;
|
|
|
|
for(;;)
|
|
{
|
|
if (stat(file_name,&stats)==-1)
|
|
{
|
|
real_log("config_open: could not get information for '%s': %s\n", file_name, LOG_ERR_STR(errno));
|
|
break;
|
|
}
|
|
if ((file=fopen(file_name,"r"))==NULL)
|
|
{
|
|
real_log("config_open: could not open '%s': %s\n", file_name, LOG_ERR_STR(errno));
|
|
break;
|
|
}
|
|
if ((cfg=(config_t *)malloc_d(sizeof(config_t),"config_open: config_t"))==NULL
|
|
|| !memset(cfg,0,sizeof(config_t))
|
|
|| (cfg->content=malloc_d(stats.st_size+1,"config_open: cfg->content"))==NULL
|
|
|| (cfg->keys=malloc_d(KEYS_PER_BLOCK*sizeof(char *), "config_open: cfg->keys"))==NULL
|
|
|| (cfg->values=malloc_d(KEYS_PER_BLOCK*sizeof(char *), "config_open: cfg->values"))==NULL)
|
|
{
|
|
real_log("config_open: memory allocation failed for config_t\n");
|
|
break;
|
|
}
|
|
if ((sz=fread(cfg->content,sizeof(char),stats.st_size,file))!=stats.st_size)
|
|
{
|
|
real_log("config_open: fread return unexpected length (got %d, expected %d)\n", sz, stats.st_size);
|
|
break;
|
|
}
|
|
(cfg->content)[stats.st_size]='\0'; /* terminate file with NULL */
|
|
cfg->content_bufsz = stats.st_size + 1;
|
|
if (!config_build_index(cfg))
|
|
{
|
|
real_log("config_open: failed to build config index\n");
|
|
break;
|
|
}
|
|
success=TRUE;
|
|
break;
|
|
}
|
|
if(file) fclose(file);
|
|
if(!success)
|
|
{
|
|
if (cfg) config_destroy(cfg);
|
|
return NULL;
|
|
}
|
|
return cfg;
|
|
}
|
|
|
|
void config_destroy(
|
|
config_t *cfg
|
|
)
|
|
{
|
|
free_d(cfg->keys,"config_destroy: cfg->keys");
|
|
free_d(cfg->values,"config_destroy: cfg->values");
|
|
free_d(cfg->content,"config_destroy: cfg->content");
|
|
free(cfg);
|
|
}
|
|
|
|
config_iter_t *config_iter_create(
|
|
config_t *cfg,
|
|
const char* prefix
|
|
)
|
|
{
|
|
config_iter_t *iter;
|
|
iter=(config_iter_t *)malloc_d(sizeof(config_iter_t),"config_iter_create: iter");
|
|
memset(iter,0,sizeof(config_iter_t));
|
|
iter->cfg=cfg;
|
|
if(prefix) iter->prefix=strdup_d(prefix,"config_iter_create: pattern");
|
|
config_iter_seek(iter);
|
|
return iter;
|
|
}
|
|
|
|
|
|
int config_iter_next(
|
|
config_iter_t *iter
|
|
)
|
|
{
|
|
/* if iterator currently valid, increment */
|
|
if(iter->key!=NULL)
|
|
{
|
|
iter->idx++;
|
|
if (config_iter_seek(iter))
|
|
{
|
|
return TRUE;
|
|
}
|
|
/* indicate iter now at end */
|
|
(iter->key)=(iter->value)=NULL;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
void config_iter_destroy(
|
|
config_iter_t *cfg_iter
|
|
)
|
|
{
|
|
free_d(cfg_iter->prefix,"config_iter_destroy: pattern");
|
|
free_d(cfg_iter,"config_iter_destroy: cfg_iter");
|
|
}
|
|
|
|
/* returns NULL if string not found, or if key is NULL */
|
|
char *config_get_str(
|
|
config_t *cfg,
|
|
const char *key)
|
|
{
|
|
char **entry = config_find_entry(cfg, key);
|
|
|
|
if (entry)
|
|
return strdup_d (*entry, "config_get_str: cfg->values[i]");
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
/* reallocates space for the config's content buffer, adjusting the
|
|
* existing size by delta.
|
|
*/
|
|
static void config_realloc_content (config_t *cfg, int delta)
|
|
{
|
|
cfg->content = realloc_d (cfg->content,
|
|
cfg->content_bufsz + delta,
|
|
"config_realloc_content: cfg->content");
|
|
}
|
|
|
|
/* Slides the block of memory from 'block' to the end of the content buffer
|
|
* by dist bytes (may be negative).
|
|
*/
|
|
static void config_slide_content (config_t *cfg, char* block, int dist)
|
|
{
|
|
size_t block_size = cfg->content_bufsz - (block - cfg->content);
|
|
|
|
memmove (block + dist,
|
|
block,
|
|
block_size);
|
|
}
|
|
|
|
/* Replaces the entry string with the new_entry string within the
|
|
* config's content buffer, adjusting the buffer size accordingly.
|
|
*/
|
|
void config_replace_entry(config_t *cfg, char *entry, const char *new_entry)
|
|
{
|
|
size_t old_length = strlen(entry), new_length = strlen(new_entry);
|
|
int delta_size = new_length - old_length;
|
|
|
|
/* point into the data directly after to the current entry value. */
|
|
char *next_block = entry + old_length + 1;
|
|
|
|
/* We need integer offsets into the buffer, so we can re-find
|
|
* the right position after realloc() is called.
|
|
*/
|
|
size_t next_block_offset = next_block - cfg->content;
|
|
size_t entry_offset = entry - cfg->content;
|
|
|
|
/* The order we do this in depends on whether we're growing
|
|
* or shrinking the existing key's value. */
|
|
if (delta_size > 0)
|
|
{
|
|
config_realloc_content (cfg, delta_size);
|
|
|
|
/* Buffer will have (possibly) moved - point at the (possibly different)
|
|
* new location */
|
|
next_block = cfg->content + next_block_offset;
|
|
|
|
config_slide_content (cfg, next_block, delta_size);
|
|
}
|
|
else if (delta_size < 0)
|
|
{
|
|
config_slide_content (cfg, next_block, delta_size);
|
|
config_realloc_content (cfg, delta_size);
|
|
}
|
|
|
|
cfg->content_bufsz += delta_size;
|
|
|
|
entry = cfg->content + entry_offset;
|
|
memcpy(entry, new_entry, new_length+1);
|
|
}
|
|
|
|
/* changes the value of an existing key, returning the new value or NULL
|
|
* if the key doesn't exist.
|
|
*/
|
|
char *config_put_str (config_t *cfg,
|
|
const char *key,
|
|
const char *new_value)
|
|
{
|
|
char **entry = config_find_entry(cfg, key);
|
|
char *res = NULL;
|
|
|
|
if (entry)
|
|
{
|
|
config_replace_entry (cfg, *entry, new_value);
|
|
if (config_rebuild_index (cfg))
|
|
res = strdup_d (*entry, "config_put_str: value");
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
|
|
int config_get_enum(
|
|
config_t *cfg,
|
|
const char *key,
|
|
const char* const allowed[],
|
|
int *val
|
|
)
|
|
{
|
|
int i, success=FALSE;
|
|
char *str_val;
|
|
if ((str_val=config_get_str(cfg,key))==NULL) return FALSE;
|
|
for(i=0;allowed[i]!=NULL;i++)
|
|
{
|
|
if(strcmp(str_val,allowed[i])==0)
|
|
{
|
|
*val=i;
|
|
success=TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (!success) real_log("config_get_enum: key '%s' has out-of-range enum value '%s'\n", key, str_val);
|
|
free_d(str_val,"config_get_enum: val");
|
|
return success;
|
|
}
|
|
|
|
/* same as config_get_enum, with 'allowed' == {"FALSE", "TRUE", NULL} */
|
|
int config_get_bool(
|
|
config_t *cfg,
|
|
const char *key,
|
|
int *val)
|
|
{
|
|
return config_get_enum(cfg, key, bool_str_g, val);
|
|
}
|
|
|
|
/* return value associated with 'key' through 'val'. returns TRUE if
|
|
value found and int-like, or FALSE otherwise. */
|
|
int config_get_int(
|
|
config_t *cfg,
|
|
const char *key,
|
|
int *val)
|
|
{
|
|
int res;
|
|
int success=TRUE;
|
|
char *str_val;
|
|
if ((str_val=config_get_str(cfg, key))==NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
/* we expect a single integer term to match */
|
|
if (sscanf(str_val,"%d",&res)!=1)
|
|
{
|
|
real_log("config_get_int: key: '%s' not an integer, instead '%s'\n",key,str_val);
|
|
success=FALSE;
|
|
}
|
|
else *val=res;
|
|
free_d(str_val, "config_get_int: str_val");
|
|
return success;
|
|
}
|
|
|
|
int config_get_long (config_t *cfg, const char *key, long *val)
|
|
{
|
|
long res;
|
|
int success=TRUE;
|
|
char *str_val;
|
|
if ((str_val=config_get_str(cfg, key))==NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
/* we expect a single integer term to match */
|
|
if (sscanf(str_val,"%ld",&res)!=1)
|
|
{
|
|
real_log("config_get_long: key: '%s' not a long, instead '%s'\n",key,str_val);
|
|
success=FALSE;
|
|
}
|
|
else *val=res;
|
|
free_d(str_val, "config_get_long: str_val");
|
|
return success;
|
|
}
|
|
|
|
/* JR */
|
|
|
|
/* Clones a config_t object */
|
|
config_t *config_clone (const config_t * const other_cfg)
|
|
{
|
|
config_t *cfg;
|
|
|
|
if ((cfg=(config_t *)malloc_d(sizeof(config_t),"config_clone: config_t"))==NULL
|
|
|| !memset(cfg,0,sizeof(config_t))
|
|
|| (cfg->content=malloc_d(other_cfg->content_bufsz,"config_clone: cfg->content"))==NULL
|
|
|| (cfg->keys=malloc_d(KEYS_PER_BLOCK*sizeof(char *), "config_clone: cfg->keys"))==NULL
|
|
|| (cfg->values=malloc_d(KEYS_PER_BLOCK*sizeof(char *), "config_clone: cfg->values"))==NULL)
|
|
{
|
|
real_log("config_open: memory allocation failed for config_t\n");
|
|
return NULL;
|
|
}
|
|
|
|
memcpy (cfg->content, other_cfg->content, other_cfg->content_bufsz);
|
|
if (config_rebuild_index (cfg))
|
|
{
|
|
return cfg;
|
|
}
|
|
else
|
|
{
|
|
config_destroy (cfg);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
int config_save (config_t *cfg,
|
|
const char *file_name)
|
|
{
|
|
int success = FALSE;
|
|
|
|
FILE *file = fopen (file_name, "w");
|
|
|
|
if (file)
|
|
{
|
|
config_reset_content(cfg);
|
|
|
|
fputs (cfg->content, file);
|
|
|
|
fclose (file);
|
|
|
|
config_build_index (cfg);
|
|
|
|
success = TRUE;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
/* replaces all the '=' and '\n' characters in the content buffer */
|
|
static void config_reset_content(config_t *cfg)
|
|
{
|
|
int i, keyOrValue = 0;
|
|
char *c = cfg->content;
|
|
|
|
for (i = 0; i < cfg->content_bufsz - 1; i++)
|
|
if (cfg->content[i] == '\0')
|
|
cfg->content[i] = (keyOrValue = !keyOrValue) ? '=' : '\n';
|
|
}
|
|
|
|
static int config_rebuild_index (config_t *cfg)
|
|
{
|
|
config_reset_content (cfg);
|
|
return config_build_index (cfg);
|
|
}
|
|
|
|
static char **config_find_entry(config_t *cfg, const char *key)
|
|
{
|
|
int i;
|
|
|
|
if (key == NULL)
|
|
return NULL;
|
|
|
|
for (i = 0; i < cfg->num_keys; i++)
|
|
if (strcmp (cfg->keys[i], key) == 0)
|
|
return &(cfg->values[i]);
|
|
|
|
real_log ("config_find_entry: key not found '%s'\n", key);
|
|
return NULL;
|
|
}
|
|
|
|
static int config_iter_seek(config_iter_t *iter)
|
|
{
|
|
config_t *cfg=iter->cfg;
|
|
int prefix_len=(iter->prefix?strlen(iter->prefix):0);
|
|
|
|
while(iter->idx<cfg->num_keys)
|
|
{
|
|
if(iter->prefix==NULL
|
|
||strncmp(cfg->keys[iter->idx],iter->prefix,prefix_len)==0)
|
|
{
|
|
iter->key=cfg->keys[iter->idx];
|
|
iter->value=cfg->values[iter->idx];
|
|
return TRUE;
|
|
}
|
|
iter->idx++;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|