/* 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); }