582 lines
13 KiB
C
582 lines
13 KiB
C
/* $Id$ */
|
|
/* Copyright (c) 2005-2015 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/>. */
|
|
|
|
|
|
|
|
#include <sys/stat.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include "System/error.h"
|
|
#include "System/mutator.h"
|
|
#include "System/config.h"
|
|
#include "../config.h"
|
|
|
|
/* constants */
|
|
#ifndef PREFIX
|
|
# define PREFIX "/usr/local"
|
|
#endif
|
|
#ifndef SYSCONFDIR
|
|
# define SYSCONFDIR PREFIX "/etc"
|
|
#endif
|
|
|
|
#define CONFIG_COMMENT '#'
|
|
|
|
|
|
/* Config */
|
|
/* private */
|
|
/* types */
|
|
typedef struct _ConfigForeachData
|
|
{
|
|
ConfigForeachCallback callback;
|
|
void * priv;
|
|
} ConfigForeachData;
|
|
|
|
typedef struct _ConfigForeachSectionData
|
|
{
|
|
ConfigForeachSectionCallback callback;
|
|
void * priv;
|
|
} ConfigForeachSectionData;
|
|
|
|
typedef struct _ConfigSave
|
|
{
|
|
FILE * fp;
|
|
char const * sep;
|
|
} ConfigSave;
|
|
|
|
|
|
/* public */
|
|
/* functions */
|
|
/* config_new */
|
|
Config * config_new(void)
|
|
{
|
|
return mutator_new();
|
|
}
|
|
|
|
|
|
/* config_delete */
|
|
void config_delete(Config * config)
|
|
{
|
|
config_reset(config);
|
|
mutator_delete(config);
|
|
}
|
|
|
|
|
|
/* accessors */
|
|
/* config_get */
|
|
char const * config_get(Config * config, char const * section,
|
|
char const * variable)
|
|
{
|
|
Mutator * mutator;
|
|
char const * value;
|
|
|
|
if(section == NULL)
|
|
section = "";
|
|
if((mutator = mutator_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 = mutator_get(mutator, 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)
|
|
{
|
|
Mutator * mutator;
|
|
char * p;
|
|
char * newvalue = NULL;
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "DEBUG: %s(\"%s\", \"%s\", \"%s\")\n", __func__,
|
|
section, variable, value);
|
|
#endif
|
|
if(section == NULL)
|
|
section = "";
|
|
if(variable == NULL || string_get_length(variable) == 0)
|
|
return error_set_code(-EINVAL, "variable: %s",
|
|
strerror(EINVAL));
|
|
if((mutator = mutator_get(config, section)) == NULL)
|
|
{
|
|
/* create a new section */
|
|
if((mutator = mutator_new()) == NULL)
|
|
return -1;
|
|
if(mutator_set(config, section, mutator) != 0)
|
|
{
|
|
mutator_delete(mutator);
|
|
return -1;
|
|
}
|
|
p = NULL;
|
|
}
|
|
else if((p = mutator_get(mutator, variable)) == NULL && value == NULL)
|
|
/* there is nothing to do */
|
|
return 0;
|
|
if(value != NULL && (newvalue = string_new(value)) == NULL)
|
|
return -1;
|
|
if(mutator_set(mutator, variable, newvalue) != 0)
|
|
{
|
|
string_delete(newvalue);
|
|
return -1;
|
|
}
|
|
/* free the former value */
|
|
string_delete(p);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* useful */
|
|
/* config_foreach */
|
|
static void _foreach_callback(char const * key, void * value, void * data);
|
|
|
|
void config_foreach(Config * config, ConfigForeachCallback callback,
|
|
void * priv)
|
|
{
|
|
ConfigForeachData data;
|
|
|
|
data.callback = callback;
|
|
data.priv = priv;
|
|
mutator_foreach(config, _foreach_callback, &data);
|
|
}
|
|
|
|
static void _foreach_callback(char const * key, void * value, void * data)
|
|
{
|
|
ConfigForeachData * priv = data;
|
|
(void) value;
|
|
|
|
priv->callback(key, priv->priv);
|
|
}
|
|
|
|
|
|
/* config_foreach_section */
|
|
static void _foreach_section_callback(char const * key, void * value,
|
|
void * data);
|
|
|
|
void config_foreach_section(Config * config, char const * section,
|
|
ConfigForeachSectionCallback callback, void * priv)
|
|
{
|
|
Mutator * mutator;
|
|
ConfigForeachSectionData data;
|
|
|
|
if((mutator = mutator_get(config, section)) == NULL)
|
|
return; /* could not find section */
|
|
data.callback = callback;
|
|
data.priv = priv;
|
|
mutator_foreach(mutator, _foreach_section_callback, &data);
|
|
}
|
|
|
|
static void _foreach_section_callback(char 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 = NULL;
|
|
String * variable = NULL;
|
|
String * value = NULL;
|
|
int c;
|
|
String * str;
|
|
|
|
if((fp = fopen(filename, "r")) == NULL)
|
|
return error_set_code(-errno, "%s: %s", filename,
|
|
strerror(errno));
|
|
for(line = 0; (c = fgetc(fp)) != EOF; line++)
|
|
if(c == CONFIG_COMMENT)
|
|
/* skip the comment */
|
|
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;
|
|
string_delete(value);
|
|
value = str;
|
|
if(config_set(config, section, variable, value) != 0)
|
|
break;
|
|
}
|
|
else if(c != '\n')
|
|
break;
|
|
string_delete(section);
|
|
string_delete(variable);
|
|
string_delete(value);
|
|
if(c != EOF)
|
|
ret = error_set_code(1, "%s: %s%ld", filename, "Syntax error"
|
|
" at line ", line);
|
|
if(fclose(fp) != 0)
|
|
ret = error_set_code(-errno, "%s: %s", filename,
|
|
strerror(errno));
|
|
return ret;
|
|
}
|
|
|
|
static int _load_isprint(int c)
|
|
{
|
|
if(c == EOF || 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 != EOF && c != '\n')
|
|
{
|
|
free(str);
|
|
return NULL;
|
|
}
|
|
if(str == NULL)
|
|
return string_new("");
|
|
str[len] = '\0';
|
|
return str;
|
|
}
|
|
|
|
|
|
/* config_load_preferences */
|
|
int config_load_preferences(Config * config, String const * vendor,
|
|
String const * package, String const * filename)
|
|
{
|
|
int ret;
|
|
|
|
if((ret = config_load_preferences_system(config, vendor, package,
|
|
filename)) != 0
|
|
&& ret != -ENOENT)
|
|
return ret;
|
|
if((ret = config_load_preferences_user(config, vendor, package,
|
|
filename)) != 0
|
|
&& ret != -ENOENT)
|
|
return ret;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* config_load_preferences_system */
|
|
int config_load_preferences_system(Config * config, String const * vendor,
|
|
String const * package, String const * filename)
|
|
{
|
|
int ret;
|
|
String * f;
|
|
|
|
if(filename == NULL)
|
|
return error_set_code(-EINVAL, "%s", strerror(EINVAL));
|
|
if(vendor != NULL && string_find(vendor, "/") != NULL)
|
|
return error_set_code(-EPERM, "%s", strerror(EPERM));
|
|
if(package != NULL && string_find(package, "/") != NULL)
|
|
return error_set_code(-EPERM, "%s", strerror(EPERM));
|
|
if((f = string_new_append(SYSCONFDIR, "/",
|
|
(vendor != NULL) ? vendor : "", "/",
|
|
(package != NULL) ? package : "", "/",
|
|
filename, NULL)) == NULL)
|
|
return error_get_code();
|
|
ret = config_load(config, f);
|
|
string_delete(f);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* config_load_preferences_user */
|
|
int config_load_preferences_user(Config * config, String const * vendor,
|
|
String const * package, String const * filename)
|
|
{
|
|
int ret;
|
|
String const * homedir;
|
|
String * f;
|
|
|
|
if(filename == NULL)
|
|
return error_set_code(-EINVAL, "%s", strerror(EINVAL));
|
|
if(vendor != NULL && string_find(vendor, "/") != NULL)
|
|
return error_set_code(-EPERM, "%s", strerror(EPERM));
|
|
if(package != NULL && string_find(package, "/") != NULL)
|
|
return error_set_code(-EPERM, "%s", strerror(EPERM));
|
|
if(filename != NULL && string_find(filename, "/") != NULL)
|
|
return error_set_code(-EPERM, "%s", strerror(EPERM));
|
|
if((homedir = getenv("HOME")) == NULL)
|
|
return error_set_code(-errno, "%s", strerror(errno));
|
|
if((f = string_new_append(homedir, "/.config/",
|
|
(vendor != NULL) ? vendor : "", "/",
|
|
(package != NULL) ? package : "", "/",
|
|
filename, NULL)) == NULL)
|
|
return error_get_code();
|
|
ret = config_load(config, f);
|
|
string_delete(f);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* config_reset */
|
|
static void _delete_foreach(String const * key, void * value, void * data);
|
|
static void _delete_foreach_section(String const * key, void * value,
|
|
void * data);
|
|
|
|
int config_reset(Config * config)
|
|
{
|
|
mutator_foreach(config, _delete_foreach, NULL);
|
|
return mutator_reset(config);
|
|
}
|
|
|
|
static void _delete_foreach(String const * key, void * value, void * data)
|
|
{
|
|
Mutator * mutator = value;
|
|
(void) key;
|
|
(void) data;
|
|
|
|
/* free the values */
|
|
mutator_foreach(mutator, _delete_foreach_section, NULL);
|
|
mutator_delete(mutator);
|
|
}
|
|
|
|
static void _delete_foreach_section(String const * key, void * value,
|
|
void * data)
|
|
{
|
|
String * v = value;
|
|
(void) key;
|
|
(void) data;
|
|
|
|
string_delete(v);
|
|
}
|
|
|
|
|
|
/* config_save */
|
|
static void _save_foreach_default(char const * section, void * value,
|
|
void * data);
|
|
static void _save_foreach(char const * section, void * value, void * data);
|
|
static void _save_foreach_section(char const * key, void * value, void * data);
|
|
|
|
int config_save(Config * config, char const * filename)
|
|
{
|
|
ConfigSave save;
|
|
|
|
save.sep = "";
|
|
if((save.fp = fopen(filename, "w")) == NULL)
|
|
return error_set_code(-errno, "%s: %s", filename,
|
|
strerror(errno));
|
|
mutator_foreach(config, _save_foreach_default, &save);
|
|
mutator_foreach(config, _save_foreach, &save);
|
|
if(save.fp != NULL && save.sep[0] != '\0'
|
|
&& fputs(save.sep, save.fp) == EOF)
|
|
{
|
|
fclose(save.fp);
|
|
save.fp = NULL;
|
|
}
|
|
if(save.fp == NULL || fclose(save.fp) != 0)
|
|
return error_set_code(-errno, "%s: %s", filename,
|
|
strerror(errno));
|
|
return 0;
|
|
}
|
|
|
|
static void _save_foreach_default(char const * section, void * value,
|
|
void * data)
|
|
{
|
|
ConfigSave * save = data;
|
|
Mutator * mutator = value;
|
|
|
|
if(save->fp == NULL)
|
|
return;
|
|
if(section[0] != '\0')
|
|
return;
|
|
mutator_foreach(mutator, _save_foreach_section, save);
|
|
}
|
|
|
|
static void _save_foreach(char const * section, void * value, void * data)
|
|
{
|
|
ConfigSave * save = data;
|
|
Mutator * mutator = value;
|
|
|
|
if(save->fp == NULL)
|
|
return;
|
|
if(section[0] == '\0')
|
|
return;
|
|
if(fprintf(save->fp, "%s%s[%s]", save->sep, save->sep, section) < 0)
|
|
{
|
|
fclose(save->fp);
|
|
save->fp = NULL;
|
|
return;
|
|
}
|
|
save->sep = "\n";
|
|
mutator_foreach(mutator, _save_foreach_section, save);
|
|
}
|
|
|
|
static void _save_foreach_section(char const * key, void * value, void * data)
|
|
{
|
|
ConfigSave * save = data;
|
|
char const * val = value;
|
|
|
|
if(save->fp == NULL)
|
|
return;
|
|
/* FIXME escape lines with a backslash */
|
|
if(val == NULL)
|
|
return;
|
|
if(fprintf(save->fp, "%s%s=%s", save->sep, key, val) < 0)
|
|
{
|
|
fclose(save->fp);
|
|
save->fp = NULL;
|
|
return;
|
|
}
|
|
save->sep = "\n";
|
|
}
|
|
|
|
|
|
/* config_save_preferences_user */
|
|
static int _save_preferences_user_append(String ** f, String const * dir);
|
|
|
|
int config_save_preferences_user(Config * config, String const * vendor,
|
|
String const * package, String const * filename)
|
|
{
|
|
int ret;
|
|
String const * homedir;
|
|
String * f;
|
|
|
|
if(filename == NULL)
|
|
return error_set_code(-EINVAL, "%s", strerror(EINVAL));
|
|
if(vendor != NULL && string_find(vendor, "/") != NULL)
|
|
return error_set_code(-EPERM, "%s", strerror(EPERM));
|
|
if(package != NULL && string_find(package, "/") != NULL)
|
|
return error_set_code(-EPERM, "%s", strerror(EPERM));
|
|
if(filename != NULL && string_find(filename, "/") != NULL)
|
|
return error_set_code(-EPERM, "%s", strerror(EPERM));
|
|
if((homedir = getenv("HOME")) == NULL)
|
|
return error_set_code(-errno, "%s", strerror(errno));
|
|
if((f = string_new_append(homedir, "/.config", NULL)) == NULL)
|
|
return error_get_code();
|
|
if(vendor != NULL && (ret = _save_preferences_user_append(&f,
|
|
vendor)) != 0)
|
|
return ret;
|
|
if(package != NULL && (ret = _save_preferences_user_append(&f,
|
|
package)) != 0)
|
|
return ret;
|
|
if(string_append(&f, "/") != 0 || string_append(&f, filename) != 0)
|
|
return error_get_code();
|
|
ret = config_save(config, f);
|
|
string_delete(f);
|
|
return ret;
|
|
}
|
|
|
|
static int _save_preferences_user_append(String ** f, String const * dir)
|
|
{
|
|
if(string_append(f, "/") != 0)
|
|
return error_get_code();
|
|
if(string_append(f, dir) != 0)
|
|
return error_get_code();
|
|
if(mkdir(*f, 0755) != 0
|
|
&& errno != EEXIST)
|
|
return error_set_code(-errno, "%s: %s", *f, strerror(errno));
|
|
return 0;
|
|
}
|