libSystem/src/config.c

430 lines
9.0 KiB
C

/* $Id$ */
/* Copyright (c) 2005-2012 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS System libSystem */
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* TODO:
* - offer a way to load preferences (optionally from the system first) */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include "System.h"
/* Config */
/* private */
/* types */
typedef struct _ConfigForeachData
{
ConfigForeachCallback callback;
void * priv;
} ConfigForeachData;
typedef struct _ConfigForeachSectionData
{
ConfigForeachSectionCallback callback;
void * priv;
} ConfigForeachSectionData;
/* public */
/* functions */
/* config_new */
Config * config_new(void)
{
return hash_new(hash_func_string, hash_compare_string);
}
/* config_delete */
void config_delete(Config * config)
{
config_reset(config);
hash_delete(config);
}
/* accessors */
/* config_get */
char const * config_get(Config * config, char const * section,
char const * variable)
{
Hash * h;
char const * value;
if(section == NULL)
section = "";
if((h = hash_get(config, section)) == NULL)
{
/* the section does not exist */
if(section[0] == '\0')
error_set_code(1, "%s", "No default section");
else
error_set_code(1, "%s%s", section, ": No such section");
return NULL;
}
if((value = hash_get(h, variable)) == NULL)
{
/* the variable is not defined */
error_set_code(1, "%s%s%s%s%s", variable, ": Not defined in",
(section[0] == '\0') ? " default" : "",
" section ",
(section[0] != '\0') ? section : "");
return NULL;
}
return value;
}
/* config_set */
int config_set(Config * config, char const * section, char const * variable,
char const * value)
{
Hash * hash;
char * p;
char * oldvalue = NULL;
char * newvalue = NULL;
if(section == NULL)
section = "";
if((hash = hash_get(config, section)) == NULL)
{
/* create a new section */
if((hash = hash_new(hash_func_string, hash_compare_string))
== NULL)
return 1;
if((p = string_new(section)) == NULL
|| hash_set(config, p, hash) != 0)
{
string_delete(p);
hash_delete(hash);
return 1;
}
}
else
/* to free the current value if already set */
oldvalue = hash_get(hash, variable);
if((p = string_new(variable)) == NULL)
return 1;
if(value != NULL && (newvalue = string_new(value)) == NULL)
{
string_delete(p);
return 1;
}
/* set the new value */
if(hash_set(hash, p, newvalue) != 0)
{
string_delete(p);
string_delete(newvalue);
return 1;
}
string_delete(oldvalue);
return 0;
}
/* useful */
/* config_foreach */
static void _foreach_callback(void const * key, void * value, void * data);
void config_foreach(Config * config, ConfigForeachCallback callback,
void * priv)
{
ConfigForeachData data;
data.callback = callback;
data.priv = priv;
hash_foreach(config, _foreach_callback, &data);
}
static void _foreach_callback(void const * key, void * value, void * data)
{
ConfigForeachData * priv = data;
priv->callback(key, priv->priv);
}
/* config_foreach_section */
static void _foreach_section_callback(void const * key, void * value,
void * data);
void config_foreach_section(Config * config, char const * section,
ConfigForeachSectionCallback callback, void * priv)
{
Hash * h;
ConfigForeachSectionData data;
if((h = hash_get(config, section)) == NULL)
return; /* could not find section */
data.callback = callback;
data.priv = priv;
hash_foreach(h, _foreach_section_callback, &data);
}
static void _foreach_section_callback(void const * key, void * value,
void * data)
{
ConfigForeachSectionData * priv = data;
priv->callback(key, value, priv->priv);
}
/* config_load */
static int _load_isprint(int c);
static String * _load_section(FILE * fp);
static String * _load_variable(FILE * fp, int c);
static String * _load_value(FILE * fp);
int config_load(Config * config, char const * filename)
{
int ret = 0;
size_t line;
FILE * fp;
String * section;
String * variable = NULL;
int c;
String * str;
if((section = string_new("")) == NULL)
return 1;
if((fp = fopen(filename, "r")) == NULL)
{
string_delete(section);
return error_set_code(1, "%s: %s", filename, strerror(errno));
}
/* FIXME unescape backslashes (eg allow multiple lines) */
for(line = 0; (c = fgetc(fp)) != EOF; line++)
if(c == '#')
while((c = fgetc(fp)) != EOF && c != '\n');
else if(c == '[')
{
if((str = _load_section(fp)) == NULL)
break;
string_delete(section);
section = str;
}
else if(_load_isprint(c))
{
if((str = _load_variable(fp, c)) == NULL)
break;
string_delete(variable);
variable = str;
if((str = _load_value(fp)) == NULL)
break;
/* XXX optimize string alloc/free, and may fail */
config_set(config, section, variable, str);
string_delete(str);
}
else if(c != '\n')
break;
string_delete(section);
string_delete(variable);
if(!feof(fp))
ret = error_set_code(1, "%s: %s%zd", filename, "Syntax error"
" at line ", line);
if(fclose(fp) != 0)
ret = error_set_code(1, "%s: %s", filename, strerror(errno));
return ret;
}
static int _load_isprint(int c)
{
if(c == '\n' || c == '\0')
return 0;
return 1;
}
static String * _load_section(FILE * fp)
{
int c;
char * str = NULL;
size_t len = 0;
char * p;
while((c = fgetc(fp)) != EOF && c != ']' && _load_isprint(c))
{
if((p = realloc(str, sizeof(*str) * (len + 2))) == NULL)
{
free(str);
return NULL;
}
str = p;
str[len++] = c;
}
if(c != ']')
{
free(str);
return NULL;
}
if(str == NULL)
return string_new("");
str[len] = '\0';
return str;
}
static String * _load_variable(FILE * fp, int c)
{
String * str;
String buf[2] = "\0";
buf[0] = c;
if((str = string_new(buf)) == NULL)
return NULL;
while((c = fgetc(fp)) != EOF && c != '=' && _load_isprint(c))
{
buf[0] = c;
if(string_append(&str, buf) != 0)
{
string_delete(str);
return NULL;
}
}
if(c != '=')
{
string_delete(str);
return NULL;
}
return str;
}
static String * _load_value(FILE * fp)
{
int c;
String * str = NULL;
size_t len = 0;
char * p;
while((c = fgetc(fp)) != EOF && _load_isprint(c))
{
if((p = realloc(str, sizeof(*str) * (len + 2))) == NULL)
{
free(str);
return NULL;
}
str = p;
str[len++] = c;
}
if(c != '\n')
{
free(str);
return NULL;
}
if(str == NULL)
return string_new("");
str[len] = '\0';
return str;
}
/* config_reset */
static void _delete_foreach(void const * key, void * value, void * data);
static void _delete_foreach_section(void const * key, void * value,
void * data);
int config_reset(Config * config)
{
hash_foreach(config, _delete_foreach, NULL);
return hash_reset(config);
}
static void _delete_foreach(void const * key, void * value, void * data)
{
char * str = (char*)key;
Hash * hash = value;
free(str);
hash_foreach(hash, _delete_foreach_section, data);
hash_delete(hash);
}
static void _delete_foreach_section(void const * key, void * value, void * data)
{
char * k = (char*)key;
char * v = value;
free(k);
free(v);
}
/* config_save */
void _save_foreach_default(void const * key, void * value, void * data);
void _save_foreach(void const * key, void * value, void * data);
void _save_foreach_section(void const * key, void * value, void * data);
int config_save(Config * config, char const * filename)
{
FILE * fp;
if((fp = fopen(filename, "w")) == NULL)
return error_set_code(1, "%s: %s", filename, strerror(errno));
hash_foreach(config, _save_foreach_default, &fp);
hash_foreach(config, _save_foreach, &fp);
if(fp == NULL || fclose(fp) != 0)
return error_set_code(1, "%s: %s", filename, strerror(errno));
return 0;
}
void _save_foreach_default(void const * key, void * value, void * data)
{
FILE ** fp = data;
char const * section = key;
Hash * hash = value;
if(*fp == NULL)
return;
if(section[0] != '\0')
return;
hash_foreach(hash, _save_foreach_section, fp);
}
void _save_foreach(void const * key, void * value, void * data)
{
FILE ** fp = data;
char const * section = key;
Hash * hash = value;
if(*fp == NULL)
return;
if(section[0] == '\0')
return;
if(fprintf(*fp, "\n[%s]\n", section) < 0)
{
fclose(*fp);
*fp = NULL;
return;
}
hash_foreach(hash, _save_foreach_section, fp);
}
void _save_foreach_section(void const * key, void * value, void * data)
{
FILE ** fp = data;
char const * var = key;
char const * val = value;
if(*fp == NULL)
return;
/* FIXME escape lines with a backslash */
if(val == NULL || fprintf(*fp, "%s=%s\n", var, val) >= 0)
return;
fclose(*fp);
*fp = NULL;
}