Below is the file 'sqlite-backend.c' from this revision. You can also download the file.

/* GConf sqlite backend
 * Copyright (C) 1999, 2000, 2002 Red Hat Inc.
 * Copyright (C) 2005 Grahame Bowland.
 *
 * 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.
 */

/*
 * designed for correctness, speed. uses sqlite3 for local storage.
 */

#include <gconf/gconf-backend.h>
#include <gconf/gconf-internals.h>
#include <gconf/gconf.h>
#include <glib.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sqlite3.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#define ROOT_DIRECTORY_PARENT			-1
#define ROOT_DIRECTORY_NAME			""

/*
 * VTable functions
 */

 /* shutdown() is a BSD libc function */
static void           x_shutdown      (GError           **err);
static GConfSource*   resolve_address (const char        *address,
                                       GError           **err);
static void           lock            (GConfSource       *source,
                                       GError           **err);
static void           unlock          (GConfSource       *source,
                                       GError           **err);
static gboolean       readable        (GConfSource       *source,
                                       const char        *key,
                                       GError           **err);
static gboolean       writable        (GConfSource       *source,
                                       const char        *key,
                                       GError           **err);
static GConfValue*    query_value     (GConfSource       *source,
                                       const char        *key,
                                       const char       **locales,
                                       char             **schema_name,
                                       GError           **err);
static GConfMetaInfo* query_metainfo  (GConfSource       *source,
                                       const char        *key,
                                       GError           **err);
static void           set_value       (GConfSource       *source,
                                       const char        *key,
                                       const GConfValue  *value,
                                       GError           **err);
static GSList*        all_entries     (GConfSource       *source,
                                       const char        *dir,
                                       const char       **locales,
                                       GError           **err);
static GSList*        all_subdirs     (GConfSource       *source,
                                       const char        *dir,
                                       GError           **err);
static void           unset_value     (GConfSource       *source,
                                       const char        *key,
                                       const char        *locale,
                                       GError           **err);
static gboolean       dir_exists      (GConfSource       *source,
                                       const char        *dir,
                                       GError           **err);
static void           remove_dir      (GConfSource       *source,
                                       const char        *dir,
                                       GError           **err);
static void           set_schema      (GConfSource       *source,
                                       const char        *key,
                                       const char        *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);

typedef struct
{
	long long int id;
} SqliteDirectory;

typedef struct
{
	GConfSource source; /* inherit from GConfSource */
	gchar *dbfile;
	GConfLock* lock;
	sqlite3 *db;
	SqliteDirectory root_dir;
} SqliteSource;

/* to be used whenever appropriate (stock error message) */
static void
update_failure (GError **err)
{
	g_set_error (err, GCONF_ERROR,
			GCONF_ERROR_FAILED,
			_("Failed to write some configuration data to disk\n"));
}

/* returns TRUE if the directory exists; in this case dir will be filled in with the
 * details of the directory.
 *
 * returns FALSE otherwise
 */
static gboolean
get_directory (SqliteSource *ss, SqliteDirectory *parent, const char *name, SqliteDirectory *dir)
{
	char *query, *tail;
	gboolean rv;
	long long int nrow;
	int res;
	sqlite3_stmt *statement;

	/* normal sub-directory case */
	g_return_val_if_fail (parent != NULL, FALSE);
	g_return_val_if_fail (name != NULL, FALSE);

	/* NB: there should only be one row in the result (especially with the table constraints set */
	query = sqlite3_mprintf ("SELECT id FROM directories WHERE parent_id=%lld AND name='%s'", dir->id, name);

	if (sqlite3_prepare (ss->db, query, strlen(query), &statement, (const char **)&tail) != SQLITE_OK) {
		gconf_log (GCL_ERR, "(get_directory) Unable to compile query: %s", query);
		sqlite3_free (query);
		return FALSE;
	}

	nrow = 0;
	rv = TRUE;

	for (;;) {
		res = sqlite3_step (statement);
		if (res == SQLITE_ROW) {
			nrow++;
			dir->id = sqlite3_column_int64(statement, 0);
		} else if (res == SQLITE_DONE) {
			break;
		} else {
			/* ought to be one of:
				SQLITE_BUSY || res == SQLITE_ERROR || res == SQLITE_MISUSE
			*/
			gconf_log (GCL_ERR, "(get_directory) query error\n");
			break;
		}
	}

	if (nrow != 1)
		rv = FALSE;

	sqlite3_finalize (statement);
	sqlite3_free (query);

	return rv;
}

static gboolean
create_directory (SqliteSource *ss, SqliteDirectory *parent, const char *name, SqliteDirectory *dir)
{
	char *query, *err_mesg;
	gboolean rv;

	/* normal subdirectory */
	g_return_val_if_fail (parent != NULL, FALSE);
	g_return_val_if_fail (name != NULL, FALSE);

	query = sqlite3_mprintf ("INSERT INTO directories (name,parent_id) VALUES ('%s',%d)", name, parent->id);

	if (sqlite3_exec (ss->db, query, NULL, NULL, &err_mesg) != SQLITE_OK) {
		gconf_log (GCL_ERR, "(create_directory) SQL error: %s", err_mesg);
		sqlite3_free (err_mesg);
		rv = FALSE;
	} else {
		dir->id = sqlite3_last_insert_rowid (ss->db);
		rv = TRUE;
	}

	sqlite3_free (query);

	return rv;
}

/* make sure we have a root directory in the directories table */
static gboolean
ensure_has_root_directory (SqliteSource *ss)
{
	gboolean rv;

	ss->root_dir.id = -1;
	if (!get_directory (ss, &ss->root_dir, ROOT_DIRECTORY_NAME, &ss->root_dir)) {
		gconf_log (GCL_DEBUG, "(ensure_has_root_directory) no root directory\n");
		/* okay then, better create it */
		rv = create_directory (ss, &ss->root_dir, ROOT_DIRECTORY_NAME, &ss->root_dir);
	} else {
		rv = TRUE;
	}
	return rv;
}

static SqliteDirectory *
ss_ensure_directory_exists (SqliteSource *ss, const char *path, GError **error)
{
	SqliteDirectory *rv;
	char **components;
	int i;

	g_assert (ss != NULL);
	g_assert (path != NULL);

	g_return_val_if_fail (*path == '/', NULL);

	rv = g_new0 (SqliteDirectory, 1);
	components = g_strsplit (path + 1, "/", -1);

	/* we know that we have a root directory entry, so start there */
	rv->id = ss->root_dir.id;

	if (!components) {
		/* they are just after the root directory */
		return rv;
	}

	/* loop, finding and then creating directories as needed */
	i = 0;
	while (rv && components[i]) {
		if (!get_directory (ss, rv, components[i], rv)) {
			/* not there, so create it.. */
			if (!create_directory (ss, rv, components[i], rv)) {
				g_free (rv);
				rv = NULL;
				break;
			}
		}
		i++;
	}

	g_strfreev (components);
	return rv;
}

/* check that the database looks about right */
static gboolean
verify_database (SqliteSource *ss)
{
	char *query, *tail;
	gboolean rv;
	long long int nrow;
	int res;
	sqlite3_stmt *statement;

	g_return_val_if_fail (ss != NULL, FALSE);

	query = sqlite3_mprintf ("SELECT * FROM gconf");
	if (sqlite3_prepare (ss->db, query, strlen(query), &statement, (const char **)&tail) != SQLITE_OK) {
		gconf_log (GCL_ERR, "(verify_database) Unable to compile query: %s", query);
		sqlite3_free (query);
		return FALSE;
	}

	nrow = 0;
	rv = TRUE;
	for (;;) {
		res = sqlite3_step (statement);
		if (res == SQLITE_ROW) {
			nrow++;
			if (sqlite3_column_int64(statement, 0) != 1)
				rv = FALSE;
		} else if (res == SQLITE_DONE) {
			break;
		} else {
			/* ought to be one of:
				SQLITE_BUSY || res == SQLITE_ERROR || res == SQLITE_MISUSE
			*/
			gconf_log (GCL_ERR, "(verify_database) query error\n");
			break;
		}
	}

	if (nrow != 1)
		rv = FALSE;

	sqlite3_finalize (statement);
	sqlite3_free (query);

	return rv;
}

/* create a new SqliteSource, if possible.
 * otherwise, return NULL
 */
static SqliteSource *
ss_new (const char *dbfile, GConfLock *lock)
{
	SqliteSource *ss;
	mode_t m;
	int rv;

	g_return_val_if_fail (dbfile != NULL, NULL);
	ss = g_new0 (SqliteSource, 1);
	ss->dbfile = g_strdup (dbfile);
	ss->lock = lock;
	/* if the database does not exist, sqlite will create it. we must make sure
	 * it is created in with secure permissions
	 */
	m = umask(077);
	rv = sqlite3_open (ss->dbfile, &ss->db);
	umask(m);
	if (rv) {
		gconf_log (GCL_ERR, _("Couldn't open database \"%s\": %s\n"), ss->dbfile, sqlite3_errmsg(ss->db));
		sqlite3_close (ss->db);
		g_free (ss->dbfile);
		g_free (ss);
		return NULL;
	}
	if (!verify_database(ss)) {
		return NULL;
	}
	if (!ensure_has_root_directory (ss)) {
		return NULL;
	}
	return ss;
}

static void
ss_destroy (SqliteSource *ss)
{
	GError* error = NULL;
	g_return_if_fail (ss != NULL);

	if (ss->lock != NULL && !gconf_release_lock (ss->lock, &error)) {
		gconf_log (GCL_ERR, _("Failed to give up lock on database \"%s\" : %s"),
				ss->dbfile, error->message);
		g_error_free (error);
		error = NULL;
	}

	sqlite3_close (ss->db);
	g_free (ss->dbfile);
	g_free (ss);
}

/* public methods are all below this line */

static GConfBackendVTable markup_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 sqlite backend module."));
}

/* possible problem here; according to the SQLite documentation,
 * the library is threadsafe (except for sqlite3_global_recover)
 * but it is a bug to use a sqlite3* in a thread other than that
 * in which it was created
 *
 * not sure if that's going to be a problem - I doubt it though.
 */
static void
lock (GConfSource *source,
      GError **err)
{

}

static void
unlock (GConfSource *source,
        GError **err)
{


}

static gboolean
readable (GConfSource *source,
          const char  *key,
          GError     **err)
{
  return TRUE;
}

static gboolean
writable (GConfSource  *source,
          const char   *key,
          GError      **err)
{
  return TRUE;
}

static GConfValue*
query_value (GConfSource *source,
             const char  *key,
             const char **locales,
             char       **schema_name,
             GError     **err)
{
	gconf_log (GCL_DEBUG, "query_value: %s", key);

	return NULL;
}

static GConfMetaInfo*
query_metainfo (GConfSource *source,
                const char  *key,
                GError     **err)
{
	gconf_log (GCL_DEBUG, "query_metainfo: %s", key);

	return NULL;
}

static void
set_value (GConfSource      *source,
           const char       *key,
           const GConfValue *value,
           GError          **err)
{
	char *parent;
	SqliteDirectory *dir;

	gconf_log (GCL_DEBUG, "set_value: %s", key);

	parent = gconf_key_directory (key);
	dir = ss_ensure_directory_exists ((SqliteSource *)source, parent, err);
	if (!dir) {
		update_failure (err);
		return;
	}

	g_free (parent);
	g_free (dir);
	g_assert (parent != NULL);
}

/* all entries in the directory pointed to by key */
static GSList*
all_entries (GConfSource *source,
             const char  *key,
             const char **locales,
             GError     **err)
{
	gconf_log (GCL_DEBUG, "all_entries: %s", key);

	return NULL;
}

/* all subdirectories of the directory pointed to by key */
static GSList*
all_subdirs (GConfSource *source,
             const char  *key,
             GError     **err)
{
	gconf_log (GCL_DEBUG, "all_subdirs: %s", key);

	return NULL;
}

static void
unset_value (GConfSource *source,
             const char  *key,
             const char  *locale,
             GError     **err)
{
	gconf_log (GCL_DEBUG, "unset_value: %s", key);

}

static gboolean
dir_exists (GConfSource *source,
            const char  *key,
            GError     **err)
{
	gconf_log (GCL_DEBUG, "dir_exists: %s", key);

	return FALSE;
}

static void
remove_dir (GConfSource *source,
            const char  *key,
            GError     **err)
{
	gconf_log (GCL_DEBUG, "remove_dir: %s", key);

}

static void
set_schema (GConfSource *source,
            const char  *key,
            const char  *schema_name,
            GError     **err)
{
	gconf_log (GCL_DEBUG, "set_schema: %s", key);

}

static gboolean
sync_all (GConfSource *source,
          GError     **err)
{

	return FALSE;
}

static void
destroy_source (GConfSource *source)
{
	ss_destroy ((SqliteSource *)source);
}

static void
clear_cache (GConfSource *source)
{

}

static void
blow_away_locks (const char *address)
{

}

static GConfSource*
resolve_address (const char *address,
                 GError    **err)
{
	SqliteSource *new_source;
	GConfLock *lock;
	char *dbfile;

	gconf_log (GCL_DEBUG, "sqlite module initialising, address=%s", address);

	dbfile = gconf_address_resource (address);
	if (!dbfile)
		return NULL;

	/* for now, let's ignore locking; sqlite does it anyway
	 * and probably better than we'd manage
	 */
	lock = NULL;

	new_source = ss_new (dbfile, lock);
	if (!new_source) {
		return NULL;
	}
	((GConfSource *)new_source)->flags = 0;
	gconf_log (GCL_ERR, "sqlite dbfile at: %s\n", dbfile);

	g_free (dbfile);

	return (GConfSource *)new_source;
}

/**************** EXPORT *********************/

/* Initializer */

G_MODULE_EXPORT const char*
g_module_check_init (GModule *module)
{
  gconf_log (GCL_DEBUG, _("Initializing Markup backend module"));

  return NULL;
}

G_MODULE_EXPORT GConfBackendVTable*
gconf_backend_get_vtable (void)
{
  return &markup_vtable;
}