/* GConf
* Copyright (C) 1999, 2000 Red Hat Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include <gconf/gconf-backend.h>
#include <gconf/gconf-internals.h>
#include <gconf/gconf.h>
#include "xml-cache.h"
#include <libxml/tree.h>
#include <libxml/parser.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
/*
* Overview
*
* Basically we have a directory tree underneath an arbitrary root
* directory. The directory tree reflects the configuration
* namespace. Each directory contains an XML file which contains
* metadata for the directory and the key-value pairs in that
* directory. The magic file in each directory is called %gconf.xml,
* and can't clash with the database namespace because names containing
* % aren't allowed. So:
*
* /
* %gconf.xml
* guppi/
* %gconf.xml
* gnumeric/
* %gconf.xml
*
*
* Locking
*
* Locking doesn't _really_ matter because there's only one instance
* of the daemon at a time. However, eventually we want a non-daemon
* command line tool and library, e.g. for the debconf stuff,
* so we will eventually have locking. I'll figure out then how
* it will work.
*
* Caching
*
* I haven't decided the best way to do caching yet. As a first cut;
* we'll cache the parse tree for any files we've looked at. The cache
* will contain time stamps; we'll nuke cache entries that haven't been
* used in a while, either in a main loop timeout or by checking whenever
* we add a new cache entry. Parse trees correspond to "directories" in the
* configuration namespace.
*
* A more precise cache will store specific key-value pairs; this cache
* will probably contain a pointer to the parse tree node the key-value
* pair is inside.
*
* We'll of course need a "dirty" list of stuff not yet written to disk.
*
* We'll save the mod time of parse trees when we load them, so we can
* paranoia check that no one has change the file before we save.
*
* Ideally we could monitor our own process size and also free up
* cache whenever we started to use massive RAM. However, not sure
* this can be done at all portably. Could possibly have some measure
* of parse tree size.
*
* The libxml parse trees are pretty huge, so in theory we could
* "compress" them by extracting all the information we want into a
* specialized data structure, then nuking the parse tree. However,
* that would add more CPU overhead at load and save time. Anyway, as
* a first cut I'm not going to do this, we might do it later.
*
* Atomic Saving
*
* We'll want to save atomically by creating a temporary file for the
* new file version, renaming the original file, moving the temporary
* file into place, then deleting the original file, checking for
* errors and mod times along the way.
*
* Failed lookup caching
*
* If a key/directory doesn't exist, we create a cache entry anyway
* so we can rapidly re-determine that it doesn't exist.
* We also need to save "dirty" nonexistent entries, so we can delete
* the stuff off disk.
*
*/
typedef struct _XMLSource XMLSource;
/* XMLSource **/
struct _XMLSource {
GConfSource source; /* inherit from GConfSource */
Cache* cache;
gchar* root_dir;
guint timeout_id;
GConfLock* lock;
guint dir_mode;
guint file_mode;
};
static XMLSource* xs_new (const gchar* root_dir,
guint dir_mode,
guint file_mode,
GConfLock* lock);
static void xs_destroy (XMLSource* source);
/*
* VTable functions
*/
/* shutdown() is a BSD libc function */
static void x_shutdown (GError** err);
static GConfSource* resolve_address (const gchar* address,
GError** err);
static void lock (GConfSource* source,
GError** err);
static void unlock (GConfSource* source,
GError** err);
static gboolean readable (GConfSource* source,
const gchar* key,
GError** err);
static gboolean writable (GConfSource* source,
const gchar* key,
GError** err);
static GConfValue* query_value (GConfSource* source,
const gchar* key,
const gchar** locales,
gchar** schema_name,
GError** err);
static GConfMetaInfo*query_metainfo (GConfSource* source,
const gchar* key,
GError** err);
static void set_value (GConfSource* source,
const gchar* key,
const GConfValue* value,
GError** err);
static GSList* all_entries (GConfSource* source,
const gchar* dir,
const gchar** locales,
GError** err);
static GSList* all_subdirs (GConfSource* source,
const gchar* dir,
GError** err);
static void unset_value (GConfSource* source,
const gchar* key,
const gchar* locale,
GError** err);
static gboolean dir_exists (GConfSource *source,
const gchar *dir,
GError** err);
static void remove_dir (GConfSource* source,
const gchar* dir,
GError** err);
static void set_schema (GConfSource* source,
const gchar* key,
const gchar* schema_key,
GError** err);
static gboolean sync_all (GConfSource* source,
GError** err);
static void destroy_source (GConfSource* source);
static void clear_cache (GConfSource* source);
static void blow_away_locks (const char *address);
static GConfBackendVTable xml_vtable = {
sizeof (GConfBackendVTable),
x_shutdown,
resolve_address,
lock,
unlock,
readable,
writable,
query_value,
query_metainfo,
set_value,
all_entries,
all_subdirs,
unset_value,
dir_exists,
remove_dir,
set_schema,
sync_all,
destroy_source,
clear_cache,
blow_away_locks,
NULL, /* set_notify_func */
NULL, /* add_listener */
NULL /* remove_listener */
};
static void
x_shutdown (GError** err)
{
gconf_log(GCL_DEBUG, _("Unloading XML backend module."));
}
static void
lock (GConfSource* source,
GError** err)
{
}
static void
unlock (GConfSource* source,
GError** err)
{
}
static gboolean
readable (GConfSource* source,
const gchar* key,
GError** err)
{
return TRUE;
}
static gboolean
writable (GConfSource* source,
const gchar* key,
GError** err)
{
return TRUE;
}
static char*
get_dir_from_address (const char *address,
GError **err)
{
char *root_dir;
int len;
root_dir = gconf_address_resource (address);
if (root_dir == NULL)
{
gconf_set_error (err, GCONF_ERROR_BAD_ADDRESS,
_("Couldn't find the XML root directory in the address `%s'"),
address);
return NULL;
}
/* Chop trailing '/' to canonicalize */
len = strlen (root_dir);
if (root_dir[len-1] == '/')
root_dir[len-1] = '\0';
return root_dir;
}
static char*
get_lock_dir_from_root_dir (const char *root_dir)
{
gchar* lockdir;
lockdir = gconf_concat_dir_and_key (root_dir, "%gconf-xml-backend.lock");
return lockdir;
}
static GConfSource*
resolve_address (const gchar* address, GError** err)
{
struct stat statbuf;
gchar* root_dir;
XMLSource* xsource;
GConfSource* source;
gint flags = 0;
GConfLock* lock = NULL;
guint dir_mode = 0700;
guint file_mode = 0600;
gchar** address_flags;
gchar** iter;
gboolean force_readonly;
root_dir = get_dir_from_address (address, err);
if (root_dir == NULL)
return NULL;
if (g_stat (root_dir, &statbuf) == 0)
{
/* Already exists, base our dir_mode on it */
dir_mode = _gconf_mode_t_to_mode (statbuf.st_mode);
/* dir_mode without search bits */
file_mode = dir_mode & (~0111);
}
else if (g_mkdir (root_dir, dir_mode) < 0)
{
/* Error out even on EEXIST - shouldn't happen anyway */
gconf_set_error (err, GCONF_ERROR_FAILED,
_("Could not make directory `%s': %s"),
(gchar *)root_dir, g_strerror (errno));
g_free (root_dir);
return NULL;
}
force_readonly = FALSE;
address_flags = gconf_address_flags (address);
if (address_flags)
{
iter = address_flags;
while (*iter)
{
if (strcmp (*iter, "readonly") == 0)
{
force_readonly = TRUE;
break;
}
++iter;
}
}
g_strfreev (address_flags);
{
/* See if we're writable */
gboolean writable;
int fd;
gchar* testfile;
writable = FALSE;
if (!force_readonly)
{
testfile = g_strconcat(root_dir, "/.testing.writeability", NULL);
fd = g_open(testfile, O_CREAT|O_WRONLY, S_IRWXU);
if (fd >= 0)
{
writable = TRUE;
close(fd);
}
g_unlink(testfile);
g_free(testfile);
}
if (writable)
flags |= GCONF_SOURCE_ALL_WRITEABLE;
/* We only do locking if it's writable,
* and if not using local locks,
* which is sort of broken but close enough
*/
if (writable && !gconf_use_local_locks ())
{
gchar* lockdir;
lockdir = get_lock_dir_from_root_dir (root_dir);
lock = gconf_get_lock(lockdir, err);
if (lock != NULL)
gconf_log(GCL_DEBUG, "Acquired lock directory `%s'", lockdir);
g_free(lockdir);
if (lock == NULL)
{
g_free(root_dir);
return NULL;
}
}
}
{
/* see if we're readable */
gboolean readable = FALSE;
GDir* d;
d = g_dir_open(root_dir, 0, NULL);
if (d != NULL)
{
readable = TRUE;
g_dir_close(d);
}
if (readable)
flags |= GCONF_SOURCE_ALL_READABLE;
}
if (!(flags & GCONF_SOURCE_ALL_READABLE) &&
!(flags & GCONF_SOURCE_ALL_WRITEABLE))
{
gconf_set_error(err, GCONF_ERROR_BAD_ADDRESS, _("Can't read from or write to the XML root directory in the address \"%s\""), address);
g_free(root_dir);
return NULL;
}
/* Create the new source */
xsource = xs_new(root_dir, dir_mode, file_mode, lock);
gconf_log(GCL_DEBUG,
_("Directory/file permissions for XML source at root %s are: %o/%o"),
root_dir, dir_mode, file_mode);
source = (GConfSource*)xsource;
source->flags = flags;
g_free(root_dir);
return source;
}
static GConfValue*
query_value (GConfSource* source,
const gchar* key,
const gchar** locales,
gchar** schema_name,
GError** err)
{
XMLSource* xs = (XMLSource*)source;
gchar* parent;
Dir* dir;
GError* error = NULL;
parent = gconf_key_directory(key);
g_assert(parent != NULL);
dir = cache_lookup(xs->cache, parent, FALSE, &error);
/* We DO NOT want to return an error unless it represents a general
problem with the backend; since we don't want to report stuff
like "this key doesn't exist yet" - however this is a maintenance
problem, since some errors may be added that need reporting. */
if (error != NULL)
{
gconf_log(GCL_WARNING, "%s", error->message);
g_error_free(error);
error = NULL;
}
g_free(parent);
parent = NULL;
if (dir != NULL)
{
const gchar* relative_key;
GConfValue* retval;
relative_key = gconf_key_key(key);
retval = dir_get_value(dir, relative_key, locales, schema_name, &error);
/* perhaps we should be reporting this error... */
if (error != NULL)
{
gconf_log(GCL_WARNING, "%s", error->message);
g_error_free(error);
error = NULL;
}
return retval;
}
else
return NULL;
}
static GConfMetaInfo*
query_metainfo (GConfSource* source, const gchar* key,
GError** err)
{
XMLSource* xs = (XMLSource*)source;
gchar* parent;
Dir* dir;
parent = gconf_key_directory(key);
if (parent != NULL)
{
dir = cache_lookup(xs->cache, parent, FALSE, err);
g_free(parent);
parent = NULL;
if (dir != NULL)
{
const gchar* relative_key;
relative_key = gconf_key_key (key);
return dir_get_metainfo (dir, relative_key, err);
}
}
/* No metainfo found */
return NULL;
}
static void
set_value (GConfSource* source, const gchar* key, const GConfValue* value,
GError** err)
{
XMLSource* xs = (XMLSource*)source;
Dir* dir;
gchar* parent;
g_return_if_fail(value != NULL);
g_return_if_fail(source != NULL);
parent = gconf_key_directory(key);
g_assert(parent != NULL);
dir = cache_lookup(xs->cache, parent, TRUE, err);
g_free(parent);
parent = NULL;
if (dir == NULL)
{
g_return_if_fail((err == NULL || *err != NULL));
return;
}
else
{
const gchar* relative_key;
relative_key = gconf_key_key(key);
dir_set_value(dir, relative_key, value, err);
}
}
static GSList*
all_entries (GConfSource* source,
const gchar* key,
const gchar** locales,
GError** err)
{
XMLSource* xs = (XMLSource*)source;
Dir* dir;
dir = cache_lookup(xs->cache, key, FALSE, err);
if (dir == NULL)
return NULL;
else
return dir_all_entries(dir, locales, err);
}
static GSList*
all_subdirs (GConfSource* source,
const gchar* key,
GError** err)
{
Dir* dir;
XMLSource* xs = (XMLSource*)source;
GError *sync_err;
/* We have to sync before we can do this, to see which
* subdirs have gone away.
*/
sync_err = NULL;
cache_sync (xs->cache, &sync_err);
if (sync_err)
{
gconf_log (GCL_WARNING, _("Error syncing the XML backend directory cache: %s"),
sync_err->message);
g_error_free (sync_err);
sync_err = NULL;
/* continue, may as well try our best. */
}
dir = cache_lookup (xs->cache, key, FALSE, err);
if (dir == NULL)
return NULL;
else
return dir_all_subdirs (dir, err);
}
static void
unset_value (GConfSource* source,
const gchar* key,
const gchar* locale,
GError** err)
{
XMLSource* xs = (XMLSource*)source;
Dir* dir;
gchar* parent;
gconf_log(GCL_DEBUG, "XML backend: unset value `%s'", key);
parent = gconf_key_directory(key);
dir = cache_lookup(xs->cache, parent, FALSE, err);
g_free(parent);
if (dir == NULL)
return;
else
{
const gchar* relative_key;
relative_key = gconf_key_key(key);
dir_unset_value(dir, relative_key, locale, err);
}
}
static gboolean
dir_exists (GConfSource*source,
const gchar* key,
GError** err)
{
XMLSource *xs = (XMLSource*)source;
Dir* dir;
dir = cache_lookup(xs->cache, key, FALSE, err);
return (dir != NULL);
}
static void
remove_dir (GConfSource* source,
const gchar* key,
GError** err)
{
g_set_error (err, GCONF_ERROR,
GCONF_ERROR_FAILED,
_("Remove directory operation is no longer supported, just remove all the values in the directory"));
}
static void
set_schema (GConfSource *source,
const gchar *key,
const gchar *schema_key,
GError **err)
{
XMLSource* xs = (XMLSource*)source;
Dir* dir;
gchar* parent;
g_return_if_fail (source != NULL);
g_return_if_fail (key != NULL);
parent = gconf_key_directory (key);
g_assert (parent != NULL);
dir = cache_lookup (xs->cache, parent, TRUE, err);
g_free (parent);
parent = NULL;
if (dir == NULL)
return; /* error should be set */
else
{
const gchar* relative_key;
relative_key = gconf_key_key (key);
dir_set_schema (dir, relative_key, schema_key, err);
}
}
static gboolean
sync_all (GConfSource* source,
GError** err)
{
XMLSource* xs = (XMLSource*)source;
return cache_sync (xs->cache, err);
}
static void
destroy_source (GConfSource* source)
{
xs_destroy((XMLSource*)source);
}
static void
clear_cache (GConfSource* source)
{
XMLSource* xs = (XMLSource*)source;
/* clean all entries older than 0 seconds */
cache_clean(xs->cache, 0);
}
static void
blow_away_locks (const char *address)
{
char *root_dir;
char *lock_dir;
GDir *dp;
const char *dent;
/* /tmp locks should never be stuck, and possible security issue to
* blow them away
*/
if (gconf_use_local_locks ())
return;
root_dir = get_dir_from_address (address, NULL);
if (root_dir == NULL)
return;
lock_dir = get_lock_dir_from_root_dir (root_dir);
dp = g_dir_open (lock_dir, 0, NULL);
if (dp == NULL)
{
g_printerr (_("Could not open lock directory for %s to remove locks: %s\n"),
address, g_strerror (errno));
goto out;
}
while ((dent = g_dir_read_name (dp)) != NULL)
{
char *path;
path = g_build_filename (lock_dir, dent, NULL);
if (g_unlink (path) < 0)
{
g_printerr (_("Could not remove file %s: %s\n"),
path, g_strerror (errno));
}
g_free (path);
}
out:
if (dp)
g_dir_close (dp);
g_free (root_dir);
g_free (lock_dir);
}
/* Initializer */
#ifndef G_OS_WIN32
/* If we use G_MODULE_EXPORT, *only* thusly marked functions will be
* exported, and xml-test uses other ones, too.
*/
G_MODULE_EXPORT
#endif
const gchar*
g_module_check_init (GModule *module)
{
gconf_log(GCL_DEBUG, _("Initializing XML backend module"));
LIBXML_TEST_VERSION;
xmlKeepBlanksDefault(1);
return NULL;
}
#ifndef G_OS_WIN32
G_MODULE_EXPORT
#endif
GConfBackendVTable*
gconf_backend_get_vtable(void)
{
return &xml_vtable;
}
/* ****************************************************/
/*
* XMLSource
*/
/* This timeout periodically cleans up
the old cruft in the cache */
static gboolean
cleanup_timeout(gpointer data)
{
XMLSource* xs = (XMLSource*)data;
cache_clean(xs->cache, 60*5 /* 5 minutes */);
return TRUE;
}
static XMLSource*
xs_new (const gchar* root_dir, guint dir_mode, guint file_mode, GConfLock* lock)
{
XMLSource* xs;
g_return_val_if_fail(root_dir != NULL, NULL);
xs = g_new0(XMLSource, 1);
xs->root_dir = g_strdup(root_dir);
xs->cache = cache_get(xs->root_dir, dir_mode, file_mode);
xs->timeout_id = g_timeout_add(1000*60*5, /* 1 sec * 60 s/min * 5 min */
cleanup_timeout,
xs);
xs->lock = lock;
xs->dir_mode = dir_mode;
xs->file_mode = file_mode;
return xs;
}
static void
xs_destroy (XMLSource* xs)
{
GError* error = NULL;
g_return_if_fail(xs != NULL);
/* do this first in case we're in a "fast cleanup just before exit"
situation */
if (xs->lock != NULL && !gconf_release_lock(xs->lock, &error))
{
gconf_log (GCL_ERR, _("Failed to give up lock on XML directory \"%s\": %s"),
xs->root_dir, error->message);
g_error_free(error);
error = NULL;
}
if (!g_source_remove(xs->timeout_id))
{
/* should not happen, don't translate */
gconf_log(GCL_ERR, "timeout not found to remove?");
}
cache_unref(xs->cache);
g_free(xs->root_dir);
g_free(xs);
}
syntax highlighted by Code2HTML, v. 0.9.1