/* realmd -- Realm configuration service
*
* Copyright 2012 Red Hat Inc
*
* 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; either version 2 of the licence or (at
* your option) any later version.
*
* See the included COPYING file for more information.
*
* Author: Stef Walter <stefw@gnome.org>
*/
#include "config.h"
#include "realm-settings.h"
#include "realm-ini-config.h"
#include <glib/gi18n.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
#define REALM_INI_CONFIG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), REALM_TYPE_INI_CONFIG, RealmIniConfigClass))
#define REALM_IS_INI_CONFIG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), REALM_TYPE_INI_CONFIG))
#define REALM_INI_CONFIG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), REALM_TYPE_INI_CONFIG, RealmIniConfigClass))
typedef struct _ConfigLine {
gchar *name;
GBytes *bytes;
struct _ConfigLine *prev;
struct _ConfigLine *next;
} ConfigLine;
typedef struct {
GHashTable *parameters;
ConfigLine *head;
ConfigLine *tail;
} ConfigSection;
struct _RealmIniConfig {
GObject parent;
gint flags;
GHashTable *sections;
ConfigLine *head;
ConfigLine *tail;
gboolean changing;
gchar *filename;
GFileMonitor *monitor;
gulong monitor_sig;
guint reload_scheduled;
};
typedef struct {
GObjectClass parent_class;
} RealmIniConfigClass;
enum {
PROP_0,
PROP_FLAGS
};
enum {
CHANGED,
NUM_SIGNALS
};
static guint signals[NUM_SIGNALS] = { 0, };
G_DEFINE_TYPE (RealmIniConfig, realm_ini_config, G_TYPE_OBJECT);
static guint
conf_str_hash (gconstpointer v)
{
const signed char *p;
guint32 h = 5381;
/* Case insensitive for ascii */
for (p = v; *p != '\0'; p++)
h = (h << 5) + h + g_ascii_tolower (*p);
return h;
}
static gboolean
conf_str_equal (gconstpointer v1,
gconstpointer v2)
{
const gchar *string1 = v1;
const gchar *string2 = v2;
/* Case insensitive for ascii */
return g_ascii_strcasecmp (string1, string2) == 0;
}
static void
config_section_free (gpointer data)
{
ConfigSection *sect = data;
g_hash_table_destroy (sect->parameters);
g_free (sect);
}
static void
config_line_free (gpointer data)
{
ConfigLine *line = data;
g_free (line->name);
g_bytes_unref (line->bytes);
g_free (line);
}
static void
realm_ini_config_init (RealmIniConfig *self)
{
self->sections = g_hash_table_new_full (conf_str_hash, conf_str_equal,
NULL, config_section_free);
}
static void
realm_ini_config_set_property (GObject *obj,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
RealmIniConfig *self = REALM_INI_CONFIG (obj);
switch (prop_id) {
case PROP_FLAGS:
self->flags = g_value_get_int (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
break;
}
}
static gboolean
on_changes_reload_file (gpointer user_data)
{
RealmIniConfig *self = REALM_INI_CONFIG (user_data);
realm_ini_config_reload (self);
return FALSE; /* don't call this timeout again */
}
static void
on_directory_changed (GFileMonitor *monitor,
GFile *file,
GFile *other_file,
GFileMonitorEvent event_type,
gpointer user_data)
{
RealmIniConfig *self = REALM_INI_CONFIG (user_data);
gchar *event_base;
gchar *our_base;
switch (event_type) {
case G_FILE_MONITOR_EVENT_CHANGED:
case G_FILE_MONITOR_EVENT_CREATED:
case G_FILE_MONITOR_EVENT_DELETED:
break;
default:
return;
}
if (!self->filename)
return;
event_base = g_file_get_basename (file);
our_base = g_path_get_basename (self->filename);
/* If it's our file, then schedule a reload */
if (g_strcmp0 (event_base, our_base) == 0) {
if (self->reload_scheduled == 0)
self->reload_scheduled = g_timeout_add (1, on_changes_reload_file, self);
}
g_free (event_base);
g_free (our_base);
}
const gchar *
realm_ini_config_get_filename (RealmIniConfig *self)
{
return self->filename;
}
void
realm_ini_config_set_filename (RealmIniConfig *self,
const gchar *filename)
{
GError *error = NULL;
GFile *directory;
GFile *file;
/* Already connected to this filename */
if (g_strcmp0 (self->filename, filename) == 0)
return;
if (self->monitor) {
g_signal_handler_disconnect (self->monitor, self->monitor_sig);
g_object_unref (self->monitor);
self->monitor = NULL;
self->monitor_sig = 0;
}
if (self->reload_scheduled)
g_source_remove (self->reload_scheduled);
self->reload_scheduled = 0;
g_free (self->filename);
self->filename = NULL;
if (!filename)
return;
self->filename = g_strdup (filename);
/*
* Setup a file monitor. Have to monitor directory, since the file
* could theoretically not exist yet.
*/
if (!(self->flags & REALM_INI_NO_WATCH)) {
file = g_file_new_for_path (self->filename);
directory = g_file_get_parent (file);
self->monitor = g_file_monitor_directory (directory, G_FILE_MONITOR_NONE,
NULL, &error);
if (error == NULL) {
self->monitor_sig = g_signal_connect (self->monitor, "changed",
G_CALLBACK (on_directory_changed),
self);
} else {
g_warning ("Couldn't monitor directory: %s", error->message);
g_error_free (error);
}
g_object_unref (directory);
g_object_unref (file);
}
}
static void
reset_config_data (RealmIniConfig *self)
{
ConfigLine *line, *next;
g_hash_table_remove_all (self->sections);
for (line = self->head; line != NULL; line = next) {
next = line->next;
config_line_free (line);
}
self->head = NULL;
self->tail = NULL;
}
static void
realm_ini_config_finalize (GObject *obj)
{
RealmIniConfig *self = REALM_INI_CONFIG (obj);
/* Should free filename and clear up monitors */
realm_ini_config_set_filename (self, NULL);
reset_config_data (self);
g_hash_table_destroy (self->sections);
G_OBJECT_CLASS (realm_ini_config_parent_class)->finalize (obj);
}
static void
realm_ini_config_class_init (RealmIniConfigClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->set_property = realm_ini_config_set_property;
object_class->finalize = realm_ini_config_finalize;
g_object_class_install_property (object_class, PROP_FLAGS,
g_param_spec_int ("flags", "Flags", "Ini file flags",
0, G_MAXINT, 0, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
signals[CHANGED] = g_signal_new ("changed", REALM_TYPE_INI_CONFIG, G_SIGNAL_RUN_FIRST,
0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 0);
}
enum {
NONE,
COMMENT,
SECTION,
PARAMETER,
INVALID
};
static gint
parse_config_line_type_and_name (GBytes *bytes,
gchar **name)
{
const gchar *from;
const gchar *end;
const gchar *at;
gsize len;
*name = NULL;
at = g_bytes_get_data (bytes, &len);
end = at + len;
/* Skip initial spaces */
while (at < end && g_ascii_isspace (*at))
at++;
if (at == end)
return NONE;
/* A comment? */
if (*at == '#' || *at == ';')
return COMMENT;
/* A section? */
if (*at == '[') {
at++;
from = at;
while (at < end && *at != ']' && *at != '\n')
at++;
if (at < end && *at == ']' && at > from) {
*name = g_strndup (from, at - from);
return SECTION;
}
return NONE;
}
/* A parameter? */
from = at;
at = memchr (from, '=', end - from);
if (at != NULL && at > from) {
while (at - 1 > from && g_ascii_isspace (*(at - 1)))
at--;
if (at > from) {
*name = g_strndup (from, at - from);
return PARAMETER;
}
}
return INVALID;
}
static void
remove_new_lines (RealmIniConfig *self,
GString *value)
{
gsize offset = 0;
/* Remove all \r charaters from DOS style endings */
for (;;) {
gchar *at = strchr (value->str + offset, '\r');
if (at == NULL)
break;
g_string_erase (value, at - value->str, 1);
}
offset = 0;
/* Remove all \n characters, including escaped newlines */
for (;;) {
gchar *at = strchr (value->str + offset, '\n');
if (at == NULL)
break;
if ((self->flags & REALM_INI_LINE_CONTINUATIONS) &&
(at > value->str && *(at - 1) == '\\'))
g_string_erase (value, (at - 1) - value->str, 2);
else
g_string_erase (value, at - value->str, 1);
}
}
static gchar *
parse_config_line_value (RealmIniConfig *self,
GBytes *bytes)
{
GString *value;
const gchar *end;
const gchar *at;
gsize len;
at = g_bytes_get_data (bytes, &len);
end = at + len;
/* Should always have an = when parsed */
at = memchr (at, '=', end - at);
g_return_val_if_fail (at != NULL, NULL);
at++;
/* Skip spaces after equal */
while (at < end && g_ascii_isspace (*at))
at++;
value = g_string_new_len (at, end - at);
/* Remove any continuations and line endings */
remove_new_lines (self, value);
return g_strstrip (g_string_free (value, FALSE));
}
static void
append_config_line (RealmIniConfig *self,
ConfigLine *line)
{
if (self->tail == NULL) {
self->head = line;
self->tail = line;
} else {
self->tail->next = line;
line->prev = self->tail;
self->tail = line;
}
}
static void
insert_config_line (RealmIniConfig *self,
ConfigLine *after,
ConfigLine *line)
{
g_assert (after != NULL);
g_assert (line != NULL);
line->next = after->next;
line->prev = after;
if (after->next)
after->next->prev = line;
after->next = line;
if (after == self->tail)
self->tail = line;
}
static void
remove_config_line (RealmIniConfig *self,
ConfigLine *line)
{
g_assert (line != NULL);
/* We only get called for parameters in sections */
g_assert (line->prev != NULL);
g_assert (self->head != line);
if (line->next)
line->next->prev = line->prev;
line->prev->next = line->next;
}
static void
parse_config_line (RealmIniConfig *self,
GBytes *bytes,
ConfigSection **current)
{
ConfigSection *sect;
ConfigLine *line;
gchar *name = NULL;
gint type;
line = g_new0 (ConfigLine, 1);
line->bytes = g_bytes_ref (bytes);
/* What kind of line is this? */
type = parse_config_line_type_and_name (bytes, &name);
switch (type) {
case SECTION:
sect = g_hash_table_lookup (self->sections, name);
if (sect == NULL) {
sect = g_new0 (ConfigSection, 1);
sect->parameters = g_hash_table_new (conf_str_hash, conf_str_equal);
g_hash_table_replace (self->sections, name, sect);
sect->head = line;
sect->tail = line;
}
*current = sect;
break;
case PARAMETER:
if (*current != NULL)
g_hash_table_insert ((*current)->parameters, name, line);
break;
}
line->name = name;
append_config_line (self, line);
/* Add this line as the end of the current section */
if (type != NONE && type != COMMENT && *current != NULL)
(*current)->tail = line;
}
static void
parse_config_bytes (RealmIniConfig *self,
GBytes *bytes)
{
ConfigSection *current;
GBytes *line;
const gchar *beg;
const gchar *end;
const gchar *at;
const gchar *from;
gsize len;
/* Clear the current data */
reset_config_data (self);
current = NULL;
beg = from = at = g_bytes_get_data (bytes, &len);
end = at + len;
for (;;) {
const gchar *search = at;
at = memchr (search, '\n', end - search);
if (at == NULL) {
line = g_bytes_new_from_bytes (bytes,
from - beg,
end - from);
} else {
const gchar *last = at > search ? at - 1 : NULL;
at++;
/* Line continuation */
if ((self->flags & REALM_INI_LINE_CONTINUATIONS) &&
(last != NULL && *last == '\\'))
continue;
line = g_bytes_new_from_bytes (bytes,
from - beg,
at - from);
}
parse_config_line (self, line, ¤t);
g_bytes_unref (line);
if (at == NULL)
break;
from = at;
}
if (!self->changing)
g_signal_emit (self, signals[CHANGED], 0);
}
void
realm_ini_config_read_string (RealmIniConfig *self,
const gchar *data)
{
GBytes *bytes;
g_return_if_fail (REALM_IS_INI_CONFIG (self));
g_return_if_fail (data != NULL);
bytes = g_bytes_new (data, strlen (data));
realm_ini_config_read_bytes (self, bytes);
g_bytes_unref (bytes);
}
void
realm_ini_config_read_bytes (RealmIniConfig *self,
GBytes *bytes)
{
g_return_if_fail (REALM_IS_INI_CONFIG (self));
g_return_if_fail (bytes != NULL);
realm_ini_config_set_filename (self, NULL);
parse_config_bytes (self, bytes);
}
static GString *
write_to_string (RealmIniConfig *self)
{
ConfigLine *line;
GString *result;
const gchar *data;
gsize len;
result = g_string_sized_new (4096);
for (line = self->head; line != NULL; line = line->next) {
/*
* Add \n between lines if not already present. This happens
* if the file was parsed without a trailing \n, and then
* stuff was added to the end.
*/
if (result->len > 0 && result->str[result->len - 1] != '\n')
g_string_append_c (result, '\n');
data = g_bytes_get_data (line->bytes, &len);
g_string_append_len (result, data, len);
}
return result;
}
gchar *
realm_ini_config_write_string (RealmIniConfig *self)
{
GString *result;
g_return_val_if_fail (REALM_IS_INI_CONFIG (self), NULL);
result = write_to_string (self);
return g_string_free (result, FALSE);
}
GBytes *
realm_ini_config_write_bytes (RealmIniConfig *self)
{
GString *result;
gsize len;
g_return_val_if_fail (REALM_IS_INI_CONFIG (self), NULL);
result = write_to_string (self);
len = result->len;
return g_bytes_new_take (g_string_free (result, FALSE), len);
}
gboolean
realm_ini_config_read_file (RealmIniConfig *self,
const gchar *filename,
GError **error)
{
GError *err = NULL;
GBytes *bytes;
gchar *contents;
gsize length;
g_return_val_if_fail (REALM_IS_INI_CONFIG (self), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
if (filename == NULL) {
g_return_val_if_fail (self->filename != NULL, FALSE);
filename = self->filename;
}
g_file_get_contents (filename, &contents, &length, &err);
/* Ignore errors of the file not existing */
if (g_error_matches (err, G_FILE_ERROR, G_FILE_ERROR_NOENT))
g_clear_error (&err);
if (err != NULL) {
g_propagate_error (error, err);
return FALSE;
}
bytes = g_bytes_new_take (contents, length);
parse_config_bytes (self, bytes);
g_bytes_unref (bytes);
realm_ini_config_set_filename (self, filename);
return TRUE;
}
gboolean
realm_ini_config_write_file (RealmIniConfig *self,
const gchar *filename,
GError **error)
{
GBytes *bytes;
gboolean ret = TRUE;
const gchar *contents;
mode_t mask = 0;
gsize length;
g_return_val_if_fail (REALM_IS_INI_CONFIG (self), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
if (filename == NULL) {
g_return_val_if_fail (self->filename != NULL, FALSE);
filename = self->filename;
}
bytes = realm_ini_config_write_bytes (self);
g_return_val_if_fail (bytes != NULL, FALSE);
contents = g_bytes_get_data (bytes, &length);
/*
* If not writing any data, and no file is present, don't
* write an empty file.
*/
if (length > 0 || g_file_test (filename, G_FILE_TEST_EXISTS)) {
if (self->flags & REALM_INI_PRIVATE)
mask = umask (S_IRWXG | S_IRWXO);
ret = g_file_set_contents (filename, contents, length, error);
if (self->flags & REALM_INI_PRIVATE)
umask (mask);
}
g_bytes_unref (bytes);
if (ret)
realm_ini_config_set_filename (self, filename);
return ret;
}
gboolean
realm_ini_config_write_fd (RealmIniConfig *self,
gint fd,
GError **error)
{
GBytes *bytes;
gboolean ret = TRUE;
const gchar *contents;
gint result;
gsize length;
gint errn;
g_return_val_if_fail (REALM_IS_INI_CONFIG (self), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
bytes = realm_ini_config_write_bytes (self);
g_return_val_if_fail (bytes != NULL, FALSE);
contents = g_bytes_get_data (bytes, &length);
while (length > 0) {
result = write (fd, contents, length);
if (result < 0) {
if (errno != EINTR && errno != EAGAIN) {
errn = errno;
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errn),
_("Couldn't write out config: %s"), g_strerror (errn));
ret = FALSE;
break;
}
result = 0;
}
g_return_val_if_fail (result <= length, FALSE);
contents += result;
length -= result;
}
g_bytes_unref (bytes);
if (ret)
realm_ini_config_set_filename (self, NULL);
return ret;
}
static void
config_set_value (RealmIniConfig *self,
const gchar *section,
const gchar *name,
const gchar *value)
{
ConfigSection *sect;
ConfigLine *line;
gchar *data;
g_return_if_fail (strchr (section, ']') == NULL);
g_return_if_fail (strchr (section, '[') == NULL);
g_return_if_fail (name != NULL);
g_return_if_fail (strchr (name, '=') == NULL);
g_return_if_fail (strchr (name, '\n') == NULL);
g_return_if_fail (value == NULL || strchr (value ? value : NULL, '\n') == NULL);
sect = g_hash_table_lookup (self->sections, section);
if (sect == NULL) {
/* No such section, and removing */
if (value == NULL)
return;
/* A blank line */
line = g_new0 (ConfigLine, 1);
line->bytes = g_bytes_new ("\n", 1);
line->name = NULL;
append_config_line (self, line);
/* The actual section header */
data = g_strdup_printf ("[%s]\n", section);
line = g_new0 (ConfigLine, 1);
line->bytes = g_bytes_new_take (data, strlen (data));
line->name = g_strdup (section);
append_config_line (self, line);
/* Register it */
sect = g_new0 (ConfigSection, 1);
sect->parameters = g_hash_table_new (conf_str_hash, conf_str_equal);
sect->head = sect->tail = line;
g_hash_table_replace (self->sections, line->name, sect);
}
line = g_hash_table_lookup (sect->parameters, name);
/* Removing this parameter? */
if (value == NULL) {
if (line != NULL) {
if (sect->tail == line)
sect->tail = line->prev;
/* head points to [section] line, not parameter */
g_assert (sect->head != line);
remove_config_line (self, line);
if (!g_hash_table_remove (sect->parameters, line->name))
g_assert_not_reached ();
config_line_free (line);
}
return;
}
data = g_strdup_printf ("%s = %s\n", name, value);
/* Don't have this line, add to section */
if (line == NULL) {
line = g_new0 (ConfigLine, 1);
line->bytes = g_bytes_new_take (data, strlen (data));
line->name = g_strdup (name);
insert_config_line (self, sect->tail, line);
g_hash_table_insert (sect->parameters, line->name, line);
/* Already have this line, replace the data */
} else {
g_bytes_unref (line->bytes);
line->bytes = g_bytes_new_take (data, strlen (data));
}
}
void
realm_ini_config_set (RealmIniConfig *self,
const gchar *section,
const gchar *name,
const gchar *value,
...)
{
va_list va;
g_return_if_fail (REALM_IS_INI_CONFIG (self));
g_return_if_fail (section != NULL);
va_start (va, value);
while (name != NULL) {
config_set_value (self, section, name, value);
name = va_arg (va, const gchar *);
if (name != NULL)
value = va_arg (va, const gchar *);
}
va_end (va);
if (!self->changing)
g_signal_emit (self, signals[CHANGED], 0);
}
gchar *
realm_ini_config_get (RealmIniConfig *self,
const gchar *section,
const gchar *name)
{
ConfigSection *sect;
ConfigLine *line;
g_return_val_if_fail (REALM_IS_INI_CONFIG (self), NULL);
g_return_val_if_fail (section != NULL, NULL);
g_return_val_if_fail (name != NULL, NULL);
sect = g_hash_table_lookup (self->sections, section);
if (sect == NULL)
return NULL;
line = g_hash_table_lookup (sect->parameters, name);
if (line == NULL)
return NULL;
return parse_config_line_value (self, line->bytes);
}
gboolean
realm_ini_config_have (RealmIniConfig *self,
const gchar *section,
const gchar *name)
{
ConfigSection *sect;
ConfigLine *line;
g_return_val_if_fail (REALM_IS_INI_CONFIG (self), FALSE);
g_return_val_if_fail (section != NULL, FALSE);
g_return_val_if_fail (name != NULL, FALSE);
sect = g_hash_table_lookup (self->sections, section);
if (sect == NULL)
return FALSE;
line = g_hash_table_lookup (sect->parameters, name);
if (line == NULL)
return FALSE;
return TRUE;
}
GHashTable *
realm_ini_config_get_all (RealmIniConfig *self,
const gchar *section)
{
GHashTableIter iter;
ConfigSection *sect;
GHashTable *result;
const gchar *name;
ConfigLine *line;
g_return_val_if_fail (REALM_IS_INI_CONFIG (self), NULL);
g_return_val_if_fail (section != NULL, NULL);
sect = g_hash_table_lookup (self->sections, section);
if (sect == NULL)
return NULL;
result = g_hash_table_new_full (conf_str_hash, conf_str_equal, g_free, g_free);
g_hash_table_iter_init (&iter, sect->parameters);
while (g_hash_table_iter_next (&iter, (gpointer *)&name, (gpointer *)&line))
g_hash_table_replace (result, g_strdup (name), parse_config_line_value (self, line->bytes));
return result;
}
void
realm_ini_config_set_all (RealmIniConfig *self,
const gchar *section,
GHashTable *parameters)
{
GHashTableIter iter;
const gchar *name;
const gchar *value;
g_return_if_fail (REALM_IS_INI_CONFIG (self));
g_return_if_fail (section != NULL);
g_return_if_fail (parameters != NULL);
g_hash_table_iter_init (&iter, parameters);
while (g_hash_table_iter_next (&iter, (gpointer *)&name, (gpointer *)&value))
config_set_value (self, section, name, value);
if (!self->changing)
g_signal_emit (self, signals[CHANGED], 0);
}
gchar **
realm_ini_config_get_list (RealmIniConfig *self,
const gchar *section,
const gchar *name,
const gchar *delimiters)
{
gchar **values;
gchar *value;
gint i;
g_return_val_if_fail (REALM_IS_INI_CONFIG (self), NULL);
g_return_val_if_fail (section != NULL, NULL);
g_return_val_if_fail (name != NULL, NULL);
g_return_val_if_fail (delimiters != NULL, NULL);
value = realm_ini_config_get (self, section, name);
if (value == NULL)
return NULL;
values = g_strsplit_set (value, delimiters, -1);
for (i = 0; values[i] != NULL; i++)
values[i] = g_strstrip (values[i]);
g_free (value);
return values;
}
void
realm_ini_config_set_list (RealmIniConfig *self,
const gchar *section,
const gchar *name,
const gchar *delimiter,
const gchar **values)
{
gchar *value;
g_return_if_fail (REALM_IS_INI_CONFIG (self));
g_return_if_fail (section != NULL);
g_return_if_fail (name != NULL);
g_return_if_fail (delimiter != NULL);
value = g_strjoinv (delimiter, (gchar **)values);
realm_ini_config_set (self, section, name, value, NULL);
g_free (value);
}
void
realm_ini_config_set_list_diff (RealmIniConfig *self,
const gchar *section,
const gchar *name,
const gchar *delimiter,
const gchar **add,
const gchar **remove)
{
GPtrArray *changed;
gchar *value;
gint i, j;
gchar **original;
gchar *delim;
g_return_if_fail (REALM_IS_INI_CONFIG (self));
g_return_if_fail (section != NULL);
g_return_if_fail (name != NULL);
original = realm_ini_config_get_list (self, section, name, delimiter);
changed = g_ptr_array_new ();
/* Filter the remove values */
for (i = 0; original != NULL && original[i] != NULL; i++) {
value = g_strstrip (g_strdup (original[i]));
for (j = 0; remove != NULL && remove[j] != NULL; j++) {
if (g_ascii_strcasecmp (remove[j], value) == 0)
break;
}
if ((remove == NULL || remove[j] == NULL) && !g_str_equal (value, ""))
g_ptr_array_add (changed, value);
else
g_free (value);
}
/* Add new values */
for (j = 0; add != NULL && add[j] != NULL; j++) {
for (i = 0; original != NULL && original[i] != NULL; i++) {
if (g_ascii_strcasecmp (add[j], original[i]) == 0)
break;
}
if (original == NULL || original[i] == NULL)
g_ptr_array_add (changed, g_strdup (add[j]));
}
g_ptr_array_add (changed, NULL);
g_strfreev (original);
delim = g_strdup_printf ("%c ", delimiter[0]);
realm_ini_config_set_list (self, section, name, delim,
(const gchar **)changed->pdata);
g_ptr_array_free (changed, TRUE);
g_free (delim);
}
gchar **
realm_ini_config_get_sections (RealmIniConfig *self)
{
GHashTableIter iter;
gpointer section;
gchar **sections;
gint i = 0;
g_return_val_if_fail (REALM_IS_INI_CONFIG (self), NULL);
sections = g_new0 (gchar *, g_hash_table_size (self->sections) + 1);
g_hash_table_iter_init (&iter, self->sections);
while (g_hash_table_iter_next (&iter, §ion, NULL))
sections[i++] = g_strdup (section);
return sections;
}
gboolean
realm_ini_config_get_boolean (RealmIniConfig *config,
const gchar *section,
const gchar *name,
gboolean defahlt)
{
gboolean ret;
gchar *value;
g_return_val_if_fail (REALM_IS_INI_CONFIG (config), FALSE);
g_return_val_if_fail (section != NULL, FALSE);
g_return_val_if_fail (name != NULL, FALSE);
value = realm_ini_config_get (config, section, name);
if (value == NULL) {
ret = defahlt;
} else if (g_ascii_strcasecmp (value, "true") == 0) {
ret = TRUE;
} else if (g_ascii_strcasecmp (value, "false") == 0) {
ret = FALSE;
} else if (config->flags & REALM_INI_STRICT_BOOLEAN) {
g_warning ("Invalid %s boolean value for %s field: %s",
value, config->filename ? config->filename : "", name);
ret = defahlt;
} else if (g_ascii_strcasecmp (value, "1") == 0 ||
g_ascii_strcasecmp (value, "yes") == 0) {
ret = TRUE;
} else if (g_ascii_strcasecmp (value, "0") == 0 ||
g_ascii_strcasecmp (value, "no") == 0) {
ret = FALSE;
} else {
g_warning ("Invalid %s boolean value for %s field: %s",
value, config->filename ? config->filename : "", name);
ret = defahlt;
}
g_free (value);
return ret;
}
gboolean
realm_ini_config_have_section (RealmIniConfig *self,
const gchar *section)
{
g_return_val_if_fail (REALM_IS_INI_CONFIG (self), FALSE);
g_return_val_if_fail (section != NULL, FALSE);
return g_hash_table_lookup (self->sections, section) != NULL;
}
static gboolean
is_blank_line (GBytes *bytes)
{
const gchar *data;
gsize length;
data = g_bytes_get_data (bytes, &length);
return (length == 1 && data[0] == '\n');
}
void
realm_ini_config_remove_section (RealmIniConfig *self,
const gchar *section)
{
ConfigSection *sect;
ConfigLine *head, *tail, *line, *next;
g_return_if_fail (REALM_IS_INI_CONFIG (self));
g_return_if_fail (section != NULL);
sect = g_hash_table_lookup (self->sections, section);
if (sect == NULL)
return;
g_assert (sect->head != NULL);
g_assert (sect->tail != NULL);
head = sect->head;
tail = sect->tail;
/*
* If the prior line is a blank line, remove that too.
* This matches the behavior of config_set_value() so that
* when we add/remove sections we don't get a file full of
* empty lines.
*/
if (head->prev != NULL) {
if (is_blank_line (head->prev->bytes))
head = head->prev;
}
/* Remove this section from the config file */
if (head->prev)
head->prev->next = tail->next;
if (sect->tail->next)
tail->next->prev = head->prev;
if (self->head == head)
self->head = tail->next;
if (self->tail == tail)
self->tail = head->prev;
head->prev = NULL;
tail->next = NULL;
g_hash_table_remove (self->sections, section);
for (line = head; line != NULL; line = next) {
next = line->next;
config_line_free (line);
}
if (!self->changing)
g_signal_emit (self, signals[CHANGED], 0);
}
gboolean
realm_ini_config_change (RealmIniConfig *self,
const gchar *section,
GError **error,
...)
{
GHashTable *parameters;
const gchar *name;
const gchar *value;
gboolean ret;
va_list va;
g_return_val_if_fail (section != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
parameters = g_hash_table_new (g_str_hash, g_str_equal);
va_start (va, error);
while ((name = va_arg (va, const gchar *)) != NULL) {
value = va_arg (va, const gchar *);
g_hash_table_insert (parameters, (gpointer)name, (gpointer)value);
}
va_end (va);
ret = realm_ini_config_changev (self, section, parameters, error);
g_hash_table_unref (parameters);
return ret;
}
gboolean
realm_ini_config_changev (RealmIniConfig *self,
const gchar *section,
GHashTable *parameters,
GError **error)
{
g_return_val_if_fail (REALM_IS_INI_CONFIG (self), FALSE);
g_return_val_if_fail (section != NULL, FALSE);
g_return_val_if_fail (parameters != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
if (!realm_ini_config_begin_change (self, error))
return FALSE;
realm_ini_config_set_all (self, section, parameters);
return realm_ini_config_finish_change (self, error);
}
gboolean
realm_ini_config_change_list (RealmIniConfig *self,
const gchar *section,
const gchar *name,
const gchar *delimiters,
const gchar **add,
const gchar **remove,
GError **error)
{
g_return_val_if_fail (REALM_IS_INI_CONFIG (self), FALSE);
g_return_val_if_fail (section != NULL, FALSE);
g_return_val_if_fail (name != NULL, FALSE);
g_return_val_if_fail (delimiters != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
if (!realm_ini_config_begin_change (self, error))
return FALSE;
realm_ini_config_set_list_diff (self, section, name, delimiters, add, remove);
return realm_ini_config_finish_change (self, error);
}
void
realm_ini_config_reset (RealmIniConfig *self)
{
g_return_if_fail (REALM_IS_INI_CONFIG (self));
reset_config_data (self);
if (!self->changing)
g_signal_emit (self, signals[CHANGED], 0);
}
void
realm_ini_config_reload (RealmIniConfig *self)
{
GError *error = NULL;
g_return_if_fail (self->filename != NULL);
self->reload_scheduled = 0;
realm_ini_config_read_file (self, NULL, &error);
if (error != NULL) {
g_warning ("Couldn't reload config file: %s: %s",
self->filename, error->message);
g_clear_error (&error);
}
}
RealmIniConfig *
realm_ini_config_new (RealmIniFlags flags)
{
return g_object_new (REALM_TYPE_INI_CONFIG,
"flags", flags,
NULL);
}
gboolean
realm_ini_config_begin_change (RealmIniConfig *self,
GError **error)
{
g_return_val_if_fail (REALM_IS_INI_CONFIG (self), FALSE);
g_return_val_if_fail (self->changing == FALSE, FALSE);
self->changing = TRUE;
if (!realm_ini_config_read_file (self, NULL, error)) {
self->changing = FALSE;
return FALSE;
}
return TRUE;
}
void
realm_ini_config_abort_change (RealmIniConfig *self)
{
if (!self->changing) {
g_warning ("A realm_ini_config_begin_change() was not matched "
"correctly with realm_ini_config_abort_change()");
return;
}
self->changing = FALSE;
g_signal_emit (self, signals[CHANGED], 0);
}
gboolean
realm_ini_config_finish_change (RealmIniConfig *self,
GError **error)
{
gboolean ret;
g_return_val_if_fail (REALM_IS_INI_CONFIG (self), FALSE);
if (!self->changing) {
g_warning ("A realm_ini_config_begin_change() was not matched "
"correctly with realm_ini_config_finish_change()");
return FALSE;
}
self->changing = FALSE;
ret = realm_ini_config_write_file (self, NULL, error);
g_signal_emit (self, signals[CHANGED], 0);
return ret;
}