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 <unistd.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <time.h> #include <pwd.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) { return; // TODO: don't leave this commented out! 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) { long long int id; 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++; 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"); rv = FALSE; break; } } if (nrow != 1) rv = FALSE; if (rv) dir->id = id; 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 * traverse_to_directory (SqliteSource *ss, const char *path, gboolean do_create, 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 (!do_create || !create_directory (ss, rv, components[i], rv)) { g_free (rv); rv = NULL; break; } } i++; } g_strfreev (components); return rv; } static char * get_username (void) { uid_t uid; struct passwd *pw; uid = getuid(); if (uid < 0) return NULL; pw = getpwuid(uid); if (!pw) return NULL; return g_strdup(pw->pw_name); } static gboolean start_transaction (SqliteSource *ss) { gboolean rv; char *query, *err_mesg; rv = TRUE; query = "BEGIN"; if (sqlite3_exec (ss->db, query, NULL, NULL, &err_mesg) != SQLITE_OK) { gconf_log (GCL_ERR, "(start_transaction) SQL error: %s", err_mesg); sqlite3_free (err_mesg); rv = FALSE; } return rv; } static gboolean abort_transaction (SqliteSource *ss) { gboolean rv; char *query, *err_mesg; rv = TRUE; query = "ABORT"; if (sqlite3_exec (ss->db, query, NULL, NULL, &err_mesg) != SQLITE_OK) { gconf_log (GCL_ERR, "(abort_transaction) SQL error: %s", err_mesg); sqlite3_free (err_mesg); rv = FALSE; } return rv; } static gboolean commit_transaction (SqliteSource *ss) { gboolean rv; char *query, *err_mesg; rv = TRUE; query = "COMMIT"; if (sqlite3_exec (ss->db, query, NULL, NULL, &err_mesg) != SQLITE_OK) { gconf_log (GCL_ERR, "(commit_transaction) SQL error: %s", err_mesg); sqlite3_free (err_mesg); rv = FALSE; } return rv; } gboolean get_value_id_in_directory (SqliteSource *ss, SqliteDirectory *dir, const char *name, long long int *id) { long long int possible_id, nrow; char *query, *tail; gboolean rv; int res; sqlite3_stmt *statement; g_return_val_if_fail (ss != NULL, FALSE); g_return_val_if_fail (dir != NULL, FALSE); g_return_val_if_fail (name != NULL, FALSE); query = sqlite3_mprintf ("SELECT id FROM keys WHERE directory_id=%lld AND name='%q'", dir->id, name); if (sqlite3_prepare (ss->db, query, strlen(query), &statement, (const char **)&tail) != SQLITE_OK) { gconf_log (GCL_ERR, "(get_value_id_in_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++; possible_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; if (rv) *id = possible_id; sqlite3_free (query); sqlite3_finalize (statement); return rv; } gboolean delete_old_list (SqliteSource *ss, long long int id) { gboolean rv; char *query, *err_mesg; rv = TRUE; query = sqlite3_mprintf ("DELETE FROM lists WHERE key_id=%lld", id); if (sqlite3_exec (ss->db, query, NULL, NULL, &err_mesg) != SQLITE_OK) { gconf_log (GCL_ERR, "(delete_old_list) SQL error: %s", err_mesg); sqlite3_free (err_mesg); rv = FALSE; } sqlite3_free (query); return rv; } gboolean delete_old_pair (SqliteSource *ss, long long int id) { gboolean rv; char *query, *err_mesg; rv = TRUE; query = sqlite3_mprintf ("DELETE FROM pairs WHERE key_id=%lld", id); if (sqlite3_exec (ss->db, query, NULL, NULL, &err_mesg) != SQLITE_OK) { gconf_log (GCL_ERR, "(delete_old_pair) SQL error: %s", err_mesg); sqlite3_free (err_mesg); rv = FALSE; } sqlite3_free (query); return rv; } gboolean set_value_in_directory (SqliteSource *ss, SqliteDirectory *dir, const char *name, const GConfValue *value, GError **error) { char *query = NULL; GTime mod_time; gchar *mod_user; gboolean rv; long long int existing_id; g_return_val_if_fail (ss != NULL, FALSE); g_return_val_if_fail (dir != NULL, FALSE); g_return_val_if_fail (name != NULL, FALSE); /* * we need to handle several things here * * first consideration; this might be a new key, or it might be an update to an existing key * if we're updating, we need to handle non-scalar types correctly. this basically means removing * the old list / pair and then adding a new one in. * * second consideration; need to do clean insert of non-scalar types. * * third consideration; make sure we do it all in a transaction block, and abort the transaction * appropriately if something goes wrong. */ rv = TRUE; mod_time = time (NULL); mod_user = get_username (); do { if (!start_transaction (ss)) { rv = FALSE; break; } /* if there was a value in the past, clean up any possible * lists or pair. This could be more elegant, but this way is * the best; it's slower (eg. might do a pair of DELETEs we don't need) * but it does mean that there's no chance of us leaving a * pair or list dangling by mistake */ if (get_value_id_in_directory (ss, dir, name, &existing_id)) { if (!delete_old_list (ss, existing_id)) { rv = FALSE; break; } if (!delete_old_pair (ss, existing_id)) { rv = FALSE; break; } } /* if (!insert_or_update (ss, value)) { rv = FALSE; break; } */ } while (0); if (rv) { commit_transaction (ss); } else { abort_transaction (ss); } switch (value->type) { case GCONF_VALUE_STRING: query = sqlite3_mprintf ("INSERT OR REPLACE INTO keys (name, directory_id, value_type, value) VALUES ('%q', %lld, '%c', '%q')", name, dir->id, 's', gconf_value_get_string (value)); break; case GCONF_VALUE_INT: query = g_strdup("int"); break; case GCONF_VALUE_FLOAT: query = g_strdup("float"); break; case GCONF_VALUE_BOOL: query = g_strdup("bool"); break; case GCONF_VALUE_LIST: query = g_strdup("list"); break; case GCONF_VALUE_PAIR: query = g_strdup("pair"); break; case GCONF_VALUE_INVALID: default: /* invalid */ break; } if (!query) return FALSE; printf("query is: %s\n", query); sqlite3_free (query); return FALSE; } /* 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) { const char *relative_key; char *parent; SqliteDirectory *dir; gconf_log (GCL_DEBUG, "set_value: %s", key); parent = gconf_key_directory (key); relative_key = gconf_key_key (key); dir = traverse_to_directory ((SqliteSource *)source, parent, TRUE, err); do { if (!dir) { update_failure (err); break; } if (!set_value_in_directory ((SqliteSource *)source, dir, relative_key, value, err)) { update_failure (err); break; } } while (0); 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; }