--- /dev/null
+/*
+ * Buxton
+ *
+ * Copyright (C) 2015 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <assert.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stdbool.h>
+
+#include <glib.h>
+#include <sqlite3.h>
+
+#include "backend.h"
+#include "log.h"
+
+#define QUERY_MAX_LEN 8192
+#define QUERY_CREATE_TABLE_BUXTON "create table if not exists buxton " \
+ "(key text, " \
+ "data blob, " \
+ "PRIMARY KEY(key)) "
+
+static GHashTable *dbs;
+
+static void free_db(sqlite3 *db)
+{
+ if (!db)
+ return;
+
+ sqlite3_close(db);
+}
+
+static sqlite3 *open_sqlite3(const char *dbpath, bool readonly)
+{
+ sqlite3 *db;
+ char *nm;
+ int r;
+ bool db_exist = false;
+
+ assert(dbpath);
+
+ if (!dbs) {
+ errno = ENODEV;
+ return NULL;
+ }
+
+ db = g_hash_table_lookup(dbs, dbpath);
+ if (db)
+ return db;
+
+ nm = strdup(dbpath);
+ if (!nm) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ if (access(nm, F_OK) == 0)
+ db_exist = true;
+
+ r = sqlite3_open_v2(dbpath, &db,
+ readonly ? SQLITE_OPEN_READONLY :
+ SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
+ if (r) {
+ bxt_err("Open '%s' failed: %s", dbpath, sqlite3_errmsg(db));
+ errno = EIO;
+ free(nm);
+ return NULL;
+ }
+
+ if (!db_exist) {
+ r = sqlite3_exec(db, "PRAGMA journal_mode = WAL",
+ NULL, NULL, NULL);
+ if (r) {
+ bxt_err("change journal mode '%s' failed: %s",
+ dbpath, sqlite3_errmsg(db));
+ errno = EIO;
+ free(nm);
+ return NULL;
+ }
+
+ r = sqlite3_exec(db, QUERY_CREATE_TABLE_BUXTON,
+ NULL, NULL, NULL);
+ if (r != SQLITE_OK) {
+ bxt_err("Create tables '%s' failed: %s",
+ dbpath, sqlite3_errmsg(db));
+ errno = EIO;
+ free(nm);
+ return NULL;
+ }
+ }
+
+ g_hash_table_insert(dbs, nm, db);
+
+ bxt_dbg("Open '%s'", dbpath);
+
+ return db;
+}
+
+static int open_db(const char *dbpath, bool readonly)
+{
+ sqlite3 *db;
+
+ if (!dbpath || !*dbpath) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ db = open_sqlite3(dbpath, readonly);
+ if (!db)
+ return -1;
+
+ return 0;
+}
+
+static int close_db(const char *dbpath)
+{
+ sqlite3 *db;
+
+ if (!dbpath || !*dbpath) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!dbs) {
+ errno = ENODEV;
+ return -1;
+ }
+
+ db = g_hash_table_lookup(dbs, dbpath);
+
+ if (!db)
+ return 0;
+
+ g_hash_table_remove(dbs, dbpath);
+
+ bxt_info("close '%s'", dbpath);
+
+ return 0;
+}
+
+static int remove_db(const char *dbpath)
+{
+ sqlite3 *db;
+ int r;
+
+ if (!dbpath || !*dbpath) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!dbs) {
+ errno = ENODEV;
+ return -1;
+ }
+
+ db = g_hash_table_lookup(dbs, dbpath);
+ if (db)
+ g_hash_table_remove(dbs, dbpath);
+
+ r = unlink(dbpath);
+ if (r == -1) {
+ bxt_err("Remove '%s' failed: %d", dbpath, errno);
+ return -1;
+ }
+
+ bxt_dbg("Remove '%s'", dbpath);
+
+ return 0;
+}
+
+static int set_value(const char *dbpath, const char *key, const void *data,
+ int dlen)
+{
+ sqlite3 *db;
+ int r;
+ int ret = 0;
+ const char insert_query[] =
+ "insert or replace into buxton(key, data) values(?,?)";
+ sqlite3_stmt *stmt;
+
+ if (!dbpath || !*dbpath || !key || !*key || !data || dlen <= 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ db = open_sqlite3(dbpath, false);
+ if (!db)
+ return -1;
+
+ r = sqlite3_prepare_v2(db, insert_query, strlen(insert_query),
+ &stmt, NULL);
+ if (r != SQLITE_OK) {
+ bxt_err("prepare error, ret = %d, extended = %d\n",
+ r, sqlite3_extended_errcode(db));
+ return -1;
+ }
+
+ r = sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC);
+ if (r) {
+ bxt_err("Sqlite3 error [%d] : <%s> preparing <%s> querry\n", r,
+ sqlite3_errmsg(db), insert_query);
+ ret = -1;
+ goto end;
+ }
+ r = sqlite3_bind_blob(stmt, 2, data, dlen, SQLITE_STATIC);
+ if (r) {
+ bxt_err("Sqlite3 error [%d] : <%s> preparing <%s> querry\n", r,
+ sqlite3_errmsg(db), insert_query);
+ ret = -1;
+ goto end;
+ }
+
+ r = sqlite3_step(stmt);
+ if (r != SQLITE_DONE) {
+ bxt_err("Sqlite3 error [%d] : <%s> executing statement\n", r,
+ sqlite3_errmsg(db));
+ ret = -1;
+ }
+
+end:
+ r = sqlite3_finalize(stmt);
+ if (r != SQLITE_OK) {
+ bxt_err("Sqlite3 error [%d] : <%s> finalizing statement\n", r,
+ sqlite3_errmsg(db));
+ ret = -1;
+ }
+
+ bxt_dbg("Set '%s' Key '%s'", dbpath, key);
+
+ return ret;
+}
+
+static int get_value(const char *dbpath, const char *key, void **data,
+ int *dlen, bool readonly)
+{
+ int r;
+ int ret = 0;
+ sqlite3 *db;
+ sqlite3_stmt *stmt;
+ const char select_query[] = "select data from buxton where key = ?";
+
+ if (!dbpath || !*dbpath || !key || !*key || !data || !dlen) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ db = open_sqlite3(dbpath, readonly);
+ if (!db)
+ return -1;
+
+ r = sqlite3_prepare_v2(db, select_query, strlen(select_query),
+ &stmt, NULL);
+ if (r != SQLITE_OK) {
+ bxt_err("prepare error, ret = %d, extended = %d\n",
+ r, sqlite3_extended_errcode(db));
+ return -1;
+ }
+
+ r = sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC);
+ if (r) {
+ bxt_err("Sqlite3 error [%d] : <%s> preparing <%s> querry\n", r,
+ sqlite3_errmsg(db), select_query);
+ ret = -1;
+ goto end;
+ }
+
+ r = sqlite3_step(stmt);
+ if (r != SQLITE_ROW) {
+ errno = ENOENT;
+ ret = -1;
+ goto end;
+ }
+
+ *dlen = sqlite3_column_bytes(stmt, 0);
+ if (*dlen > 0) {
+ *data = malloc(sizeof(void *) * (*dlen + 1));
+ if (*data == NULL) {
+ errno = ENOMEM;
+ ret = -1;
+ goto end;
+ }
+ memcpy(*data, (void *)sqlite3_column_blob(stmt, 0), *dlen);
+ } else {
+ *data = NULL;
+ }
+
+end:
+ r = sqlite3_finalize(stmt);
+ if (r != SQLITE_OK) {
+ bxt_err("Sqlite3 error [%d] : <%s> finalizing statement\n", r,
+ sqlite3_errmsg(db));
+ ret = -1;
+ }
+
+ bxt_dbg("Get '%s' Key '%s'", dbpath, key);
+
+ return ret;
+}
+
+static int unset_value(const char *dbpath, const char *key)
+{
+ sqlite3 *db;
+ sqlite3_stmt *stmt;
+ int r;
+ int ret = 0;
+ const char delete_query[] = "delete from buxton where key = ?;";
+
+ if (!dbpath || !*dbpath || !key || !*key) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ db = open_sqlite3(dbpath, false);
+ if (!db)
+ return -1;
+
+ r = sqlite3_prepare_v2(db, delete_query, strlen(delete_query),
+ &stmt, NULL);
+ if (r != SQLITE_OK) {
+ bxt_err("prepare error, ret = %d, extended = %d\n",
+ r, sqlite3_extended_errcode(db));
+ return -1;
+ }
+
+ r = sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC);
+ if (r) {
+ bxt_err("Sqlite3 error [%d] : <%s> preparing <%s> querry\n", r,
+ sqlite3_errmsg(db), delete_query);
+ ret = -1;
+ goto end;
+ }
+
+ r = sqlite3_step(stmt);
+ if (r != SQLITE_DONE) {
+ bxt_err("Sqlite3 error [%d] : <%s> executing statement\n", r,
+ sqlite3_errmsg(db));
+ ret = -1;
+ }
+
+end:
+ r = sqlite3_finalize(stmt);
+ if (r != SQLITE_OK) {
+ bxt_err("Sqlite3 error [%d] : <%s> finalizing statement\n", r,
+ sqlite3_errmsg(db));
+ ret = -1;
+ }
+
+ bxt_dbg("Unset '%s' Key '%s'", dbpath, key);
+
+ return ret;
+}
+
+static int list_keys(const char *dbpath, char ***keys, unsigned int *klen)
+{
+ sqlite3 *db;
+ GList *list;
+ GList *l;
+ int r;
+ int ret = 0;
+ int i;
+ unsigned int _klen;
+ char *_key;
+ char **_keys;
+ const char list_query[] = "select key from buxton";
+ sqlite3_stmt *stmt;
+
+ if (!dbpath || !*dbpath || !keys) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ db = open_sqlite3(dbpath, true);
+ if (!db)
+ return -1;
+
+ r = sqlite3_prepare_v2(db, list_query, strlen(list_query),
+ &stmt, NULL);
+ if (r != SQLITE_OK) {
+ bxt_err("prepare error, ret = %d, extended = %d\n",
+ r, sqlite3_extended_errcode(db));
+ return -1;
+ }
+
+ _klen = 0;
+ list = NULL;
+ while (sqlite3_step(stmt) == SQLITE_ROW) {
+ _key = (char *)sqlite3_column_text(stmt, 0);
+ list = g_list_append(list, strdup(_key));
+ _klen++;
+ }
+
+ /* +1 for NULL terminated */
+ _keys = malloc(sizeof(void *) * (_klen + 1));
+ if (!_keys) {
+ g_list_free_full(list, (GDestroyNotify)free);
+ errno = ENOMEM;
+ ret = -1;
+ goto end;
+ }
+
+ for (i = 0, l = list; l && i < _klen; l = g_list_next(l), i++)
+ _keys[i] = l->data;
+
+ _keys[i] = NULL;
+
+ g_list_free(list);
+
+ *keys = _keys;
+
+ if (klen)
+ *klen = _klen;
+
+end:
+ r = sqlite3_finalize(stmt);
+ if (r != SQLITE_OK) {
+ bxt_err("Sqlite3 error [%d] : <%s> finalizing statement\n", r,
+ sqlite3_errmsg(db));
+ ret = -1;
+ }
+
+ bxt_dbg("List '%s'", dbpath);
+
+ return ret;
+}
+
+static void module_exit(void)
+{
+ g_hash_table_destroy(dbs);
+ dbs = NULL;
+}
+
+static int module_init(void)
+{
+ dbs = g_hash_table_new_full(g_str_hash, g_str_equal,
+ (GDestroyNotify)free, (GDestroyNotify)free_db);
+ if (!dbs) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ return 0;
+}
+
+DEFINE_BUXTON_BACKEND = {
+ .name = "sqlite",
+
+ .module_init = module_init,
+ .module_exit = module_exit,
+
+ .open_db = open_db,
+ .close_db = close_db,
+ .remove_db = remove_db,
+ .set_value = set_value,
+ .get_value = get_value,
+ .unset_value = unset_value,
+ .list_keys = list_keys,
+};
+