summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian Tarricone <brian@tarricone.org>2007-09-14 11:38:51 +0000
committerBrian Tarricone <brian@tarricone.org>2007-09-14 11:38:51 +0000
commitf0178d23716e3ec788b78de234ab899c05f9b4a0 (patch)
tree860053e6992f57bf062233d510ba6db7ea6d6bbb
parent77a9f25c480be7a9c3b1c59fabe23e78d01fc39c (diff)
downloadxfconf-f0178d23716e3ec788b78de234ab899c05f9b4a0.tar.gz
implement most of the the perchannel-xml reading and parsing code. it's not
complete, and it completely differs from the write code (right now, it can't properly read the files it writes), and there are some problems parsing string lists (sometimes), which i know how to fix. also there's a new locking scheme that dupilcates the functionality currently in XfceKiosk. regardless, it's totally not usable right now, but i need to commit so i can work on this elsewhere. (Old svn revision: 26734)
-rw-r--r--TODO1
-rw-r--r--common/xfconf-util.c110
-rw-r--r--common/xfconf-util.h2
-rw-r--r--configure.ac.in2
-rw-r--r--docs/spec/perchannel-xml.txt48
-rw-r--r--xfconfd/xfconf-backend-perchannel-xml.c551
6 files changed, 681 insertions, 33 deletions
diff --git a/TODO b/TODO
index 2262207..97e2c8f 100644
--- a/TODO
+++ b/TODO
@@ -11,5 +11,6 @@
* figure out how to let libxfconf know about the XfconfBackendError enum --
maybe just rename to XfconfError and move to common/
* expire channels from memory if they aren't accessed for a while
+* add a GetAllChannels() method to enable a gconf-editor type app
* separate XSETTINGS daemon that reads values from xfconfd
diff --git a/common/xfconf-util.c b/common/xfconf-util.c
index 160bd29..0a18c0d 100644
--- a/common/xfconf-util.c
+++ b/common/xfconf-util.c
@@ -21,6 +21,22 @@
#include <config.h>
#endif
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+
+#ifdef HAVE_GRP_H
+#include <grp.h>
+#endif
+
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+
#include "xfconf-util.h"
void
@@ -31,3 +47,97 @@ xfconf_g_value_free(GValue *value)
g_value_unset(value);
g_free(value);
}
+
+
+/* group cache stuff */
+
+static time_t etc_group_mtime = 0;
+static GHashTable *group_cache = NULL;
+
+static void
+xfconf_ensure_group_cache()
+{
+ gboolean needs_rebuild = FALSE;
+ struct stat st;
+ struct group *gr;
+ GHashTable *members;
+
+ if(!stat("/etc/group", &st)) {
+ if(st.st_mtime > etc_group_mtime) {
+ etc_group_mtime = st.st_mtime;
+ needs_rebuild = TRUE;
+ }
+ } else
+ needs_rebuild = TRUE;
+
+ if(!needs_rebuild && group_cache)
+ return;
+
+ if(group_cache)
+ g_hash_table_destroy(group_cache);
+
+ group_cache = g_hash_table_new_full(g_str_hash, g_str_equal,
+ (GDestroyNotify)g_free,
+ (GDestroyNotify)g_hash_table_destroy);
+
+ for(setgrent(), gr = getgrent(); gr; gr = getgrent()) {
+ gint i;
+
+ members = g_hash_table_new_full(g_str_hash, g_str_equal,
+ (GDestroyNotify)g_free, NULL);
+
+ for(i = 0; gr->gr_mem[i]; ++i) {
+ g_hash_table_replace(members, g_strdup(gr->gr_mem[i]),
+ GINT_TO_POINTER(1));
+ }
+
+ g_hash_table_replace(group_cache, g_strdup(gr->gr_name), members);
+ }
+}
+
+static gboolean
+xfconf_user_is_in_group(const gchar *user,
+ const gchar *group)
+{
+ GHashTable *members;
+
+ xfconf_ensure_group_cache();
+
+ members = g_hash_table_lookup(group_cache, group);
+
+ if(G_UNLIKELY(!members))
+ return FALSE;
+
+ return g_hash_table_lookup(members, user) ? TRUE : FALSE;
+}
+
+gboolean
+xfconf_user_is_in_list(const gchar *list)
+{
+ gboolean ret = FALSE;
+ const gchar *user_name = g_get_user_name();
+ gchar **tokens;
+ gint i;
+
+ tokens = g_strsplit(list, ";", -1);
+
+ for(i = 0; tokens[i]; ++i) {
+ if(!*tokens[i])
+ continue;
+ else if(*tokens[i] == '@') {
+ if(xfconf_user_is_in_group(user_name, tokens[i]+1)) {
+ ret = TRUE;
+ break;
+ }
+ } else {
+ if(!strcmp(user_name, tokens[i])) {
+ ret = TRUE;
+ break;
+ }
+ }
+ }
+
+ g_strfreev(tokens);
+
+ return ret;
+}
diff --git a/common/xfconf-util.h b/common/xfconf-util.h
index 8f8d31e..14cf1be 100644
--- a/common/xfconf-util.h
+++ b/common/xfconf-util.h
@@ -26,6 +26,8 @@ G_BEGIN_DECLS
void xfconf_g_value_free(GValue *value);
+gboolean xfconf_user_is_in_list(const gchar *list);
+
G_END_DECLS
#endif /* __XFCONF_UTIL_H__ */
diff --git a/configure.ac.in b/configure.ac.in
index 1d4dce9..5aa97fd 100644
--- a/configure.ac.in
+++ b/configure.ac.in
@@ -35,7 +35,7 @@ dnl AC_PROG_MAKE_SET
dnl check for standard header files
AC_HEADER_STDC
-AC_CHECK_HEADERS([errno.h fcntl.h \
+AC_CHECK_HEADERS([errno.h fcntl.h grp.h \
signal.h stdlib.h string.h \
sys/stat.h sys/time.h sys/types.h \
unistd.h])
diff --git a/docs/spec/perchannel-xml.txt b/docs/spec/perchannel-xml.txt
index b26241c..b5fb0a2 100644
--- a/docs/spec/perchannel-xml.txt
+++ b/docs/spec/perchannel-xml.txt
@@ -36,9 +36,14 @@
+ version(string): The current file version. Right now that's
"1.0". In general, files with the same 'major version' are
compatible with each other (required).
- + locked(bool): Whether or not the channel is locked to prevent
- user modification (only allowed in config files in
- non-user-writable locations; optional, defaults to "false").
+ + locked(userlist): A list of users/groups who cannot modify
+ any properties in this channel (only allowed in config files
+ in non-user-writable locations; mutually exclusive with the
+ "unlocked" attribute; optional, defaults to empty).
+ + unlocked(userlist): A list of users/groups who can modify
+ properties in this channel (only allowed in config files
+ in non-user-writable locations; mutually exclusive with the
+ "locked" attribute; optional, defaults to "*").
* <property>
Allowed as a child of the <channel> element or another <property>
element.
@@ -48,9 +53,14 @@
"strlist", "int", "int64", "double", "bool", "empty" (required).
+ value(string): The value of the property (required except for
type="strlist" and type="empty").
- + locked(bool): Whether or not the property is locked to prevent
- user modification (only allowed in config files in
- non-user-writable locations; optional, defaults to "false").
+ + locked(userlist): A list of users/groups who cannot modify
+ this property (only allowed in config files in non-user-writable
+ locations; mutually exclusive with the "unlocked" attribute;
+ optional, defaults to empty).
+ + unlocked(userlist): A list of users/groups who can modify
+ this property (only allowed in config files in non-user-writable
+ locations; mutually exclusive with the "locked" attribute;
+ optional, defaults to "*").
* <string>
Only allowed inside <property> elements where type="strlist". Has
no attributes; the text between the opening and closing tags is
@@ -82,12 +92,24 @@
the application's default fallback value will be used instead.
Locking a channel or property is as simple as creating a configuration
- file and setting the "locked" attribute to "true" on <channel> or
- <property> elements that should be locked. This configuration file
- should be placed in a system location that is read by the daemon
- (see "File locations" above).
+ file and setting either (and only either) the "locked" or "unlocked"
+ attribute on <channel> or <property> elements that should be
+ restritcted. This configuration file should be placed in a system
+ location that is read by the daemon (see "File locations" above).
+
+ Both the "locked" and "unlocked" attributes take a semicolon-separated
+ list of system user and group names. User names should be entered
+ as-is, and group names should be entered with an "@" symbol prepended
+ to the group name.
+
+ The "locked" attribute specifies users and groups who may not modify
+ the property. The "unlocked" attribute specifies users and groups
+ who may modify the property, with other users locked out. Only
+ one of the two attributes may be specified for a particular channel
+ or property. If both are present, the "unlocked" attribute is used,
+ and the "locked" attribute is ignored.
Note that <channel> locking locks all properties under that channel,
- but <property> locking locks only the property with the locked="true"
- attribute; none of the sub-properties are locked unless they also
- contain the locked="true" attribute.
+ but <property> locking locks only the property with the "locked" or
+ "unlocked" attribute; none of the sub-properties are locked unless
+ they also contain a "locked" or "unlocked" attribute.
diff --git a/xfconfd/xfconf-backend-perchannel-xml.c b/xfconfd/xfconf-backend-perchannel-xml.c
index c5ff6be..2ed49e1 100644
--- a/xfconfd/xfconf-backend-perchannel-xml.c
+++ b/xfconfd/xfconf-backend-perchannel-xml.c
@@ -21,6 +21,16 @@
#include <config.h>
#endif
+#include <stdio.h>
+
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
@@ -33,6 +43,14 @@
#include <string.h>
#endif
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+
#include <libxfce4util/libxfce4util.h>
#include <dbus/dbus-glib.h>
@@ -40,14 +58,21 @@
#include "xfconf-backend.h"
#include "xfconf-util.h"
-#define CACHE_TIMEOUT (20*60*1000) /* 20 minutes */
-#define WRITE_TIMEOUT (5*1000) /* 5 secionds */
+#define FILE_VERSION_MAJOR "1"
+#define FILE_VERSION_MINOR "0"
+
+#define CONFIG_DIR_STEM "xfce4/xfconf/" XFCONF_BACKEND_PERCHANNEL_XML_TYPE_ID "/"
+#define CONFIG_FILE_FMT CONFIG_DIR_STEM "%s.xml"
+#define CACHE_TIMEOUT (20*60*1000) /* 20 minutes */
+#define WRITE_TIMEOUT (5*1000) /* 5 secionds */
+
+#define MAX_PREF_PATH 4096
struct _XfconfBackendPerchannelXml
{
GObject parent;
- gchar *config_path;
+ gchar *config_save_path;
GTree *channels;
@@ -60,6 +85,37 @@ typedef struct _XfconfBackendPerchannelXmlClass
GObjectClass parent;
} XfconfBackendPerchannelXmlClass;
+typedef struct
+{
+ GValue *value;
+ gboolean locked;
+} XfconfProperty;
+
+typedef enum
+{
+ ELEM_NONE = 0,
+ ELEM_CHANNEL,
+ ELEM_PROPERTY,
+ ELEM_STRING,
+} XmlParserElem;
+
+/* FIXME: due to the hierarchical nature of the file, i need to use a
+ * stack for strlist_property and strlist_value because a more than one
+ * strlist property can be open at once */
+typedef struct
+{
+ XfconfBackendPerchannelXml *xbpx;
+ GTree *properties;
+ gboolean is_system_file;
+ XmlParserElem cur_elem;
+ gchar *channel_name;
+ gboolean channel_locked;
+ gchar cur_path[MAX_PREF_PATH];
+ gchar *cur_text;
+ gchar *strlist_property;
+ GValue *strlist_value;
+} XmlParserState;
+
static void xfconf_backend_perchannel_xml_class_init(XfconfBackendPerchannelXmlClass *klass);
static void xfconf_backend_perchannel_xml_init(XfconfBackendPerchannelXml *instance);
@@ -110,6 +166,8 @@ static gboolean xfconf_backend_perchannel_xml_flush_channel(XfconfBackendPerchan
const gchar *channel,
GError **error);
+static void xfconf_property_free(XfconfProperty *property);
+
G_DEFINE_TYPE_WITH_CODE(XfconfBackendPerchannelXml, xfconf_backend_perchannel_xml, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE(XFCONF_TYPE_BACKEND,
@@ -147,7 +205,8 @@ xfconf_backend_perchannel_xml_finalize(GObject *obj)
xfconf_backend_perchannel_xml_flush(XFCONF_BACKEND(xbpx), NULL);
g_tree_destroy(xbpx->channels);
- g_free(xbpx->config_path);
+
+ g_free(xbpx->config_save_path);
G_OBJECT_CLASS(xfconf_backend_perchannel_xml_parent_class)->finalize(obj);
}
@@ -171,7 +230,7 @@ xfconf_backend_perchannel_xml_initialize(XfconfBackend *backend,
{
XfconfBackendPerchannelXml *backend_px = XFCONF_BACKEND_PERCHANNEL_XML(backend);
gchar *path = xfce_resource_save_location(XFCE_RESOURCE_CONFIG,
- "xfce4/xfconf/" XFCONF_BACKEND_PERCHANNEL_XML_TYPE_ID "/",
+ CONFIG_DIR_STEM,
TRUE);
if(!path || !g_file_test(path, G_FILE_TEST_IS_DIR)) {
@@ -184,7 +243,7 @@ xfconf_backend_perchannel_xml_initialize(XfconfBackend *backend,
return FALSE;
}
- backend_px->config_path = path;
+ backend_px->config_save_path = path;
return TRUE;
}
@@ -371,7 +430,7 @@ xfconf_backend_perchannel_xml_remove_channel(XfconfBackend *backend,
g_tree_remove(xbpx->channels, channel);
- filename = g_strdup_printf("%s/%s.xml", xbpx->config_path, channel);
+ filename = g_strdup_printf("%s/%s.xml", xbpx->config_save_path, channel);
if(unlink(filename)) {
if(error) {
g_set_error(error, XFCONF_BACKEND_ERROR,
@@ -402,6 +461,14 @@ xfconf_backend_perchannel_xml_flush(XfconfBackend *backend,
+static void
+xfconf_property_free(XfconfProperty *property)
+{
+ g_value_unset(property->value);
+ g_free(property->value);
+ g_free(property);
+}
+
static gboolean
xfconf_backend_perchannel_xml_save_timeout(gpointer data)
{
@@ -447,35 +514,480 @@ xfconf_backend_perchannel_xml_create_channel(XfconfBackendPerchannelXml *xbpx,
return properties;
}
- properties = g_tree_new_full((GCompareDataFunc)strcmp, NULL,
+ properties = g_tree_new_full((GCompareDataFunc)g_ascii_strcasecmp, NULL,
(GDestroyNotify)g_free,
- (GDestroyNotify)xfconf_g_value_free);
+ (GDestroyNotify)xfconf_property_free);
g_tree_insert(xbpx->channels, g_strdup(channel), properties);
return properties;
}
+static void
+xfconf_backend_perchannel_xml_start_elem(GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ XmlParserState *state = user_data;
+ gint i;
+ const gchar *name = NULL, *type = NULL, *value = NULL;
+ const gchar *version = NULL, *locked = NULL, *unlocked = NULL;
+ gchar fullpath[MAX_PREF_PATH], *p;
+ gint maj_ver_len;
+ XfconfProperty *prop = NULL;
+
+ switch(state->cur_elem) {
+ case ELEM_NONE:
+ if(strcmp(element_name, "channel")) {
+ if(error) {
+ g_set_error(error, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+ "Element <%s> not valid at top level",
+ element_name);
+ }
+ return;
+ } else {
+ for(i = 0; attribute_names[i]; ++i) {
+ if(!strcmp(attribute_names[i], "name"))
+ name = attribute_values[i];
+ else if(!strcmp(attribute_names[i], "version"))
+ version = attribute_values[i];
+ else if(!strcmp(attribute_names[i], "locked"))
+ locked = attribute_values[i];
+ else if(!strcmp(attribute_names[i], "unlocked"))
+ unlocked = attribute_values[i];
+ else {
+ if(error) {
+ g_set_error(error, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
+ "Unknown attribute in <%s>: %s",
+ element_name, attribute_names[i]);
+ }
+ return;
+ }
+ }
+
+ if(!name || !*name || !version || !*version) {
+ if(error) {
+ g_set_error(error, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_EMPTY,
+ "Element <channel> requires both name and version attributes");
+ }
+ return;
+ }
+
+ /* compare versions */
+ p = strstr(version, ".");
+ maj_ver_len = p ? p - version : strlen(version);
+ if(maj_ver_len != strlen(FILE_VERSION_MAJOR)
+ || strncmp(version, FILE_VERSION_MAJOR, maj_ver_len))
+ {
+ if(error) {
+ g_set_error(error, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "On-disk file version %s is not compatible with our file version %s.%s",
+ version, FILE_VERSION_MAJOR,
+ FILE_VERSION_MINOR);
+ }
+ return;
+ }
+
+ /* FIXME: validate name for valid chars */
+ state->channel_name = g_strdup(name);
+
+ if((locked && *locked) || (unlocked && *unlocked)) {
+ if(!state->is_system_file) {
+ if(error) {
+ g_set_error(error, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
+ "Attribute \"locked\" not allowed in <%s> for non-system files",
+ element_name);
+ }
+ return;
+ }
+
+ if(unlocked && *unlocked)
+ state->channel_locked = !xfconf_user_is_in_list(unlocked);
+ else if(locked && *locked)
+ state->channel_locked = xfconf_user_is_in_list(locked);
+
+ if(state->channel_locked) {
+ /* if this channel is locked now, we want to throw
+ * out all existing properties. it's possible that
+ * there's more than one file that describes this
+ * channel in the various system config dirs, and this
+ * file here is not the first one we've examined.
+ * i'm not 100% sure this should be the correct
+ * behavior: another option would be to go back and
+ * mark all previously-read properties as locked,
+ * and just stop processing properties for this
+ * channel after this file is done.
+ */
+
+ g_tree_destroy(state->properties);
+ state->properties = g_tree_new_full((GCompareDataFunc)g_ascii_strcasecmp,
+ NULL,
+ (GDestroyNotify)g_free,
+ (GDestroyNotify)xfconf_property_free);
+ }
+ }
+
+ state->cur_elem = ELEM_CHANNEL;
+ }
+ break;
+
+ case ELEM_CHANNEL:
+ case ELEM_PROPERTY:
+ if(!strcmp(element_name, "property")) {
+ for(i = 0; attribute_names[i]; ++i) {
+ if(!strcmp(attribute_names[i], "name"))
+ name = attribute_values[i];
+ else if(!strcmp(attribute_names[i], "type"))
+ type = attribute_values[i];
+ else if(!strcmp(attribute_names[i], "value"))
+ value = attribute_values[i];
+ else if(!strcmp(attribute_names[i], "locked"))
+ locked = attribute_values[i];
+ else if(!strcmp(attribute_names[i], "unlocked"))
+ unlocked = attribute_values[i];
+ else {
+ if(error) {
+ g_set_error(error, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
+ "Unknown attribute in <%s>: %s",
+ element_name, attribute_names[i]);
+ }
+ return;
+ }
+ }
+
+ if(!name || !*name || !type || !*type) {
+ if(error) {
+ g_set_error(error, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_EMPTY,
+ "Element <property> requires both name and type attributes");
+ }
+ return;
+ }
+
+ /* FIXME: name validation! */
+ g_strlcpy(fullpath, state->cur_path, MAX_PREF_PATH);
+ g_strlcat(fullpath, "/", MAX_PREF_PATH);
+ g_strlcat(fullpath, name, MAX_PREF_PATH);
+
+ /* check if property is locked in a previous file */
+ prop = g_tree_lookup(state->properties, fullpath);
+ if(prop) {
+ if(prop->locked) {
+ /* we just want to skip over this property, but not
+ * throw an error */
+ state->cur_elem = ELEM_PROPERTY;
+ g_strlcpy(state->cur_path, fullpath, MAX_PREF_PATH);
+ return;
+ } else {
+ /* clear out the old data */
+ g_value_unset(prop->value);
+ }
+ } else {
+ prop = g_new0(XfconfProperty, 1);
+ prop->value = g_new0(GValue, 1);
+ g_tree_insert(state->properties, g_strdup(fullpath), prop);
+ }
+
+ if(state->channel_locked)
+ prop->locked = TRUE;
+ else if((locked && *locked) || (unlocked && *unlocked)) {
+ if(!state->is_system_file) {
+ if(error) {
+ g_set_error(error, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
+ "Attribute \"locked\" not allowed in <%s> for non-system files",
+ element_name);
+ }
+ g_tree_remove(state->properties, fullpath);
+ return;
+ }
+
+ if(unlocked && *unlocked)
+ prop->locked = !xfconf_user_is_in_list(unlocked);
+ else if(locked && *locked)
+ prop->locked = xfconf_user_is_in_list(locked);
+ }
+
+ /* parse types and values */
+ if(!strcmp(type, "string")) {
+ g_value_init(prop->value, G_TYPE_STRING);
+ g_value_set_string(prop->value, value);
+ } else if(!strcmp(type, "strlist")) {
+ GPtrArray *arr = g_ptr_array_new();
+ g_value_init(prop->value,
+ dbus_g_type_get_collection("GPtrArray",
+ G_TYPE_STRING));
+ g_value_set_boxed(prop->value, arr);
+ /* FIXME: use stacks here */
+ state->strlist_property = g_strdup(fullpath);
+ state->strlist_value = prop->value;
+ } else if(!strcmp(type, "int")) {
+ g_value_init(prop->value, G_TYPE_INT);
+ g_value_set_int(prop->value, atoi(value));
+ } else if(!strcmp(type, "int64")) {
+ g_value_init(prop->value, G_TYPE_INT64);
+ g_value_set_int64(prop->value, g_ascii_strtoll(value, NULL,
+ 0));
+ } else if(!strcmp(type, "double")) {
+ g_value_init(prop->value, G_TYPE_DOUBLE);
+ g_value_set_double(prop->value, g_ascii_strtod(value,
+ NULL));
+ } else if(!strcmp(type, "bool")) {
+ g_value_init(prop->value, G_TYPE_BOOLEAN);
+ if(!g_ascii_strcasecmp(value, "true"))
+ g_value_set_boolean(prop->value, TRUE);
+ else
+ g_value_set_boolean(prop->value, FALSE);
+ } else {
+ g_tree_remove(state->properties, fullpath);
+
+ if(strcmp(type, "empty")) {
+ if(error) {
+ g_set_error(error, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "Invalid property type \"%s\"", type);
+ }
+ return;
+ }
+ }
+
+ g_strlcpy(state->cur_path, fullpath, MAX_PREF_PATH);
+ } else if(ELEM_PROPERTY == state->cur_elem
+ && state->strlist_property /* FIXME: use stack */
+ && state->strlist_value /* FIXME: use stack */
+ && !strcmp(element_name, "string"))
+ {
+ state->cur_elem = ELEM_STRING;
+ } else {
+ if(error) {
+ g_set_error(error, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+ "Element <%s> not allowed inside <%s>",
+ element_name, ELEM_CHANNEL == state->cur_elem
+ ? "channel" : "property");
+ }
+ return;
+ }
+
+ case ELEM_STRING:
+ if(error) {
+ g_set_error(error, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+ "No other elements are allowed inside a string element.");
+ }
+ return;
+ }
+}
+
+static void
+xfconf_backend_perchannel_xml_end_elem(GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ XmlParserState *state = user_data;
+ gchar *p;
+ GPtrArray *arr;
+
+ switch(state->cur_elem) {
+ case ELEM_CHANNEL:
+ state->cur_elem = ELEM_NONE;
+ break;
+
+ case ELEM_PROPERTY:
+ /* FIXME: use stacks here */
+ state->strlist_property = NULL;
+ state->strlist_value = NULL;
+
+ p = g_strrstr(state->cur_path, "/");
+
+ if(p) {
+ *p = 0;
+ if(!*p)
+ state->cur_elem = ELEM_CHANNEL;
+ else
+ state->cur_elem = ELEM_PROPERTY;
+ } else {
+ g_warning("Missing '/' char: I don't think this should happen");
+ state->cur_elem = ELEM_CHANNEL;
+ }
+ break;
+
+ case ELEM_STRING:
+ /* FIXME: use stack */
+ arr = g_value_get_boxed(state->strlist_value);
+ g_ptr_array_add(arr, state->cur_text);
+ state->cur_text = NULL;
+ break;
+
+ case ELEM_NONE:
+ /* this really can't happen */
+ break;
+ }
+}
+
+static void
+xfconf_backend_perchannel_xml_text_elem(GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ XmlParserState *state = user_data;
+
+ if(ELEM_STRING != state->cur_elem) {
+ if(error) {
+ g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+ "Content only allowed in <string> elements");
+ }
+ return;
+ }
+
+ if(!state->cur_text)
+ state->cur_text = g_strndup(text, text_len);
+ else {
+ gint cur_len = strlen(state->cur_text);
+ state->cur_text = g_realloc(state->cur_text, cur_len + text_len + 1);
+ memcpy(state->cur_text + cur_len, text, text_len);
+ state->cur_text[cur_len + text_len] = 0;
+ }
+}
+
+static gboolean
+xfconf_backend_perchannel_xml_merge_file(XfconfBackendPerchannelXml *xbpx,
+ const gchar *filename)
+{
+ gboolean ret = FALSE;
+ gchar *file_contents = NULL;
+ GMarkupParseContext *context = NULL;
+ GMarkupParser parser = {
+ xfconf_backend_perchannel_xml_start_elem,
+ xfconf_backend_perchannel_xml_end_elem,
+ xfconf_backend_perchannel_xml_text_elem,
+ NULL,
+ };
+ XmlParserState state;
+ int fd = -1;
+ struct stat st;
+ GError *error = NULL;
+#ifdef HAVE_MMAP
+ void *addr = NULL;
+#endif
+
+ fd = open(filename, O_RDONLY, 0);
+ if(fd < 0)
+ goto out;
+
+ if(fstat(fd, &st))
+ goto out;
+
+#ifdef HAVE_MMAP
+ addr = mmap(NULL, st.st_size, PROT_READ, MAP_FILE | MAP_SHARED, fd, 0);
+ if(addr != MAP_FAILED)
+ file_contents = addr;
+#endif
+
+ if(!file_contents) {
+ file_contents = g_malloc(st.st_size);
+ if(read(fd, file_contents, st.st_size) != st.st_size)
+ goto out;
+ }
+
+ state.xbpx = xbpx;
+ state.cur_elem = ELEM_NONE;
+ state.channel_name = NULL;
+ memset(state.cur_path, 0, MAX_PREF_PATH);
+
+ context = g_markup_parse_context_new(&parser, 0, &state, NULL);
+ if(!g_markup_parse_context_parse(context, file_contents, st.st_size, &error)
+ || !g_markup_parse_context_end_parse(context, &error))
+ {
+ g_warning("Error parsing xfconf config file \"%s\": %s", filename,
+ error->message);
+ g_error_free(error);
+ goto out;
+ }
+
+ ret = TRUE;
+
+out:
+
+ if(context)
+ g_markup_parse_context_free(context);
+
+#ifdef HAVE_MMAP
+ if(addr) {
+ munmap(addr, st.st_size);
+ file_contents = NULL;
+ }
+#endif
+
+ g_free(file_contents);
+
+ if(fd >= 0)
+ close(fd);
+
+ return ret;
+}
+
static GTree *
xfconf_backend_perchannel_xml_load_channel(XfconfBackendPerchannelXml *xbpx,
const gchar *channel,
GError **error)
{
GTree *properties = NULL;
- gchar *filename;
+ gchar *filename_stem = NULL, **filenames = NULL;
+ GList *system_files = NULL, *user_files = NULL, *l;
+ gint i;
+
+ filename_stem = g_strdup_printf(CONFIG_FILE_FMT, channel);
+ filenames = xfce_resource_lookup_all(XFCE_RESOURCE_CONFIG, filename_stem);
+ g_free(filename_stem);
+
+ for(i = 0; filenames[i]; ++i) {
+ if(!access(filenames[i], W_OK)) /* we can write, it's ours */
+ user_files = g_list_append(user_files, filenames[i]);
+ else if(!access(filenames[i], R_OK)) /* we can read, it's system */
+ system_files = g_list_append(system_files, filenames[i]);
+ else /* we can't even read, so skip it */
+ g_free(filenames[i]);
+ }
+ g_free(filenames);
- filename = g_strdup_printf("%s/%s.xml", xbpx->config_path, channel);
- if(!g_file_test(filename, G_FILE_TEST_IS_REGULAR)) {
+ if(!system_files && !user_files) {
if(error) {
g_set_error(error, XFCONF_BACKEND_ERROR,
XFCONF_BACKEND_ERROR_CHANNEL_NOT_FOUND,
_("Channel \"%s\" does not exist"), channel);
}
- g_free(filename);
- return NULL;
+ goto out;
}
-//out:
- g_free(filename);
+ properties = g_tree_new_full((GCompareDataFunc)g_ascii_strcasecmp,
+ NULL,
+ (GDestroyNotify)g_free,
+ (GDestroyNotify)xfconf_property_free);
+
+ /* FIXME: handle locking */
+ for(l = system_files; l; l = l->next)
+ xfconf_backend_perchannel_xml_merge_file(xbpx, l->data);
+ for(l = user_files; l; l = l->next)
+ xfconf_backend_perchannel_xml_merge_file(xbpx, l->data);
+
+out:
+
+ g_list_foreach(user_files, (GFunc)g_free, NULL);
+ g_list_free(user_files);
+ g_list_foreach(system_files, (GFunc)g_free, NULL);
+ g_list_free(user_files);
return properties;
}
@@ -607,15 +1119,16 @@ xfconf_backend_perchannel_xml_flush_channel(XfconfBackendPerchannelXml *xbpx,
return FALSE;
}
- filename = g_strdup_printf("%s/%s.xml", xbpx->config_path, channel);
+ filename = g_strdup_printf("%s/%s.xml", xbpx->config_save_path, channel);
filename_tmp = g_strconcat(filename, ".new", NULL);
fp = fopen(filename_tmp, "w");
if(!fp)
goto out;
- if(fputs("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n", fp) == EOF
- || fprintf(fp, "<channel name=\"%s\" version=\"0.1\">\n", channel) < 0)
+ if(fputs("<?xml version=\"%s.%s\" encoding=\"UTF-8\"?>\n\n", fp) == EOF
+ || fprintf(fp, "<channel name=\"%s\" version=\"%s.%s\">\n", channel,
+ FILE_VERSION_MAJOR, FILE_VERSION_MINOR) < 0)
{
goto out;
}