/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include "backend.h" #include "log.h" #include "buxton_error.h" #define BUXTON_DEFAULT_WAL_AUTOCHECKPOINT 100 #define BASE_DB_PATH tzplatform_getenv(TZ_SYS_RO_ETC) #define MAX_PATH_LEN 512 #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 bool is_db_corrupted; static void free_db(sqlite3 *db) { if (!db) return; sqlite3_close(db); } static int open_sqlite3(const char *dbpath, bool readonly, sqlite3 **db) { sqlite3 *tmp_db; char *nm; int r; bool db_exist = false; if (!dbpath || !db) { bxt_err("Invalid parameter"); return BUXTON_ERROR_INVALID_PARAMETER; } if (!dbs) { bxt_err("dbs is not initilized."); return BUXTON_ERROR_INVALID_OPERATION; } tmp_db = g_hash_table_lookup(dbs, dbpath); if (tmp_db) { int is_ro; is_ro = sqlite3_db_readonly(tmp_db, "main"); if (is_ro != readonly) { bxt_dbg("'%s' : readonly[%d], required readonly[%d]", dbpath, is_ro, readonly); g_hash_table_remove(dbs, dbpath); } else { *db = tmp_db; return BUXTON_ERROR_NONE; } } if (access(dbpath, F_OK) == 0) db_exist = true; r = sqlite3_open_v2(dbpath, &tmp_db, readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL); if (db_exist && r == SQLITE_CORRUPT) { bxt_err("Open '%s' failed: %s", dbpath, sqlite3_errmsg(tmp_db)); if (!strncmp(BASE_DB_PATH, dbpath, strlen(BASE_DB_PATH))) { bxt_err("Invalid parameter (%s)", dbpath); return BUXTON_ERROR_INVALID_PARAMETER; } bxt_err("Try to remove %s and restore to default value.", dbpath); if (unlink(dbpath)) { bxt_err("Failed to unlink (%s)", dbpath); return BUXTON_ERROR_IO_ERROR; } db_exist = false; r = sqlite3_open_v2(dbpath, &tmp_db, readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL); } if (r) { bxt_err("Open '%s' failed: %s", dbpath, sqlite3_errmsg(tmp_db)); return BUXTON_ERROR_IO_ERROR; } if (!db_exist) { if (strncmp(BASE_DB_PATH, dbpath, strlen(BASE_DB_PATH))) { r = sqlite3_exec(tmp_db, "PRAGMA journal_mode = WAL", NULL, NULL, NULL); if (r) { bxt_err("change journal mode '%s' failed: %s", dbpath, sqlite3_errmsg(tmp_db)); sqlite3_close(tmp_db); return BUXTON_ERROR_IO_ERROR; } } r = sqlite3_exec(tmp_db, QUERY_CREATE_TABLE_BUXTON, NULL, NULL, NULL); if (r != SQLITE_OK) { bxt_err("Create tables '%s' failed: %s", dbpath, sqlite3_errmsg(tmp_db)); sqlite3_close(tmp_db); return BUXTON_ERROR_IO_ERROR; } } if (strncmp(BASE_DB_PATH, dbpath, strlen(BASE_DB_PATH))) { r = sqlite3_wal_autocheckpoint(tmp_db, BUXTON_DEFAULT_WAL_AUTOCHECKPOINT); if (r != SQLITE_OK) bxt_err("SET DEFAULT_WAL_AUTOCHECKPOINT failed : %d", r); } nm = strdup(dbpath); if (!nm) { bxt_err("out of memory"); sqlite3_close(tmp_db); return BUXTON_ERROR_OUT_OF_MEMORY; } g_hash_table_insert(dbs, nm, tmp_db); bxt_dbg("Open '%s'", dbpath); *db = tmp_db; return BUXTON_ERROR_NONE; } static int open_db(const char *dbpath, bool readonly) { sqlite3 *db; if (!dbpath || !*dbpath) { bxt_err("Invalid parameter"); return BUXTON_ERROR_INVALID_PARAMETER; } return open_sqlite3(dbpath, readonly, &db); } static int close_db(const char *dbpath) { sqlite3 *db; if (!dbpath || !*dbpath) { bxt_err("Invalid parameter"); return BUXTON_ERROR_INVALID_PARAMETER; } if (!dbs) { bxt_err("dbs is not initilized."); return BUXTON_ERROR_INVALID_OPERATION; } db = g_hash_table_lookup(dbs, dbpath); if (!db) return BUXTON_ERROR_NONE; g_hash_table_remove(dbs, dbpath); bxt_info("close '%s'", dbpath); return BUXTON_ERROR_NONE; } static int remove_db(const char *dbpath) { sqlite3 *db; int r; if (!dbpath || !*dbpath) { bxt_err("Invalid parameter"); return BUXTON_ERROR_INVALID_PARAMETER; } if (!dbs) { bxt_err("dbs is not initilized."); return BUXTON_ERROR_INVALID_OPERATION; } 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 BUXTON_ERROR_IO_ERROR; } bxt_dbg("Remove '%s'", dbpath); return BUXTON_ERROR_NONE; } static int set_value(const char *dbpath, const char *key, const void *data, int dlen) { sqlite3 *db; int r; int ret = BUXTON_ERROR_NONE; const char insert_query[] = "insert or replace into buxton(key, data) values(?,?)"; sqlite3_stmt *stmt; if (!dbpath || !*dbpath || !key || !*key || !data || dlen <= 0) { bxt_err("Invalid parameter"); return BUXTON_ERROR_INVALID_PARAMETER; } r = open_sqlite3(dbpath, false, &db); if (r != BUXTON_ERROR_NONE) return r; 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 BUXTON_ERROR_IO_ERROR; } 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 = BUXTON_ERROR_IO_ERROR; 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 = BUXTON_ERROR_IO_ERROR; goto end; } r = sqlite3_step(stmt); if (r != SQLITE_DONE) { bxt_err("Sqlite3 error [%d] : <%s> executing statement\n", r, sqlite3_errmsg(db)); ret = BUXTON_ERROR_IO_ERROR; } end: r = sqlite3_finalize(stmt); if (r != SQLITE_OK) { bxt_err("Sqlite3 error [%d] : <%s> finalizing statement\n", r, sqlite3_errmsg(db)); ret = BUXTON_ERROR_IO_ERROR; } 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) { bxt_err("Invalid parameter"); return BUXTON_ERROR_INVALID_PARAMETER; } r = open_sqlite3(dbpath, readonly, &db); if (r != BUXTON_ERROR_NONE) return r; r = sqlite3_prepare_v2(db, select_query, strlen(select_query), &stmt, NULL); if (r != SQLITE_OK) { bxt_err("prepare error, ret = %d, extended = %d %s", r, sqlite3_extended_errcode(db), sqlite3_errmsg(db)); return BUXTON_ERROR_IO_ERROR; } 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 = BUXTON_ERROR_IO_ERROR; goto end; } r = sqlite3_step(stmt); if (r != SQLITE_ROW) { bxt_dbg("Failed to sqlite3_step [%d]", r); ret = BUXTON_ERROR_NOT_EXIST; goto end; } *dlen = sqlite3_column_bytes(stmt, 0); if (*dlen > 0) { *data = malloc(sizeof(void *) * (*dlen + 1)); if (*data == NULL) { bxt_err("out of memory"); ret = BUXTON_ERROR_OUT_OF_MEMORY; 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 = BUXTON_ERROR_IO_ERROR; } 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) { bxt_err("Invalid parameter"); return BUXTON_ERROR_INVALID_PARAMETER; } r = open_sqlite3(dbpath, false, &db); if (r != BUXTON_ERROR_NONE) return r; 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 BUXTON_ERROR_IO_ERROR; } 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 = BUXTON_ERROR_IO_ERROR; goto end; } r = sqlite3_step(stmt); if (r != SQLITE_DONE) { bxt_err("Sqlite3 error [%d] : <%s> executing statement\n", r, sqlite3_errmsg(db)); ret = BUXTON_ERROR_IO_ERROR; } end: r = sqlite3_finalize(stmt); if (r != SQLITE_OK) { bxt_err("Sqlite3 error [%d] : <%s> finalizing statement\n", r, sqlite3_errmsg(db)); ret = BUXTON_ERROR_IO_ERROR; } bxt_dbg("Unset '%s' Key '%s'", dbpath, key); return ret; } static int list_keys(const char *dbpath, char ***keys, unsigned int *klen, bool readonly) { 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) { bxt_err("Invalid parameter"); return BUXTON_ERROR_INVALID_PARAMETER; } r = open_sqlite3(dbpath, readonly, &db); if (r != BUXTON_ERROR_NONE) return r; 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 BUXTON_ERROR_IO_ERROR; } _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); bxt_err("out of memory"); ret = BUXTON_ERROR_OUT_OF_MEMORY; 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 = BUXTON_ERROR_IO_ERROR; } bxt_dbg("List '%s'", dbpath); return ret; } static int get_dump(const char *dbpath, char ***keys, void ***values, int **value_len, unsigned int *klen) { sqlite3 *db; int sql_ret; int ret = 0; int index; const char list_query[] = "select * from buxton ORDER BY key"; const char count_query[] = "select count(key) from buxton"; sqlite3_stmt *stmt; int key_count = 0; char **key_list = NULL; void **value_list = NULL; int *value_len_list = NULL; if (!dbpath || !*dbpath || !keys || !values || !value_len) { bxt_err("Invalid parameter"); return BUXTON_ERROR_INVALID_PARAMETER; } ret = open_sqlite3(dbpath, true, &db); if (ret != BUXTON_ERROR_NONE) return ret; sql_ret = sqlite3_prepare_v2(db, count_query, strlen(count_query), &stmt, NULL); if (sql_ret != SQLITE_OK) { bxt_err("prepare error, ret = %d, extended = %d\n", sql_ret, sqlite3_extended_errcode(db)); return BUXTON_ERROR_IO_ERROR; } sql_ret = sqlite3_step(stmt); if (sql_ret == SQLITE_ROW) { key_count = (int)sqlite3_column_int(stmt, 0); } else { bxt_err("sqlite3_step error, ret = %d, extended = %d\n", sql_ret, sqlite3_extended_errcode(db)); ret = BUXTON_ERROR_IO_ERROR; goto end; } sqlite3_finalize(stmt); stmt = NULL; sql_ret = sqlite3_prepare_v2(db, list_query, strlen(list_query), &stmt, NULL); if (sql_ret != SQLITE_OK) { bxt_err("prepare error, ret = %d, extended = %d\n", sql_ret, sqlite3_extended_errcode(db)); ret = BUXTON_ERROR_IO_ERROR; goto end; } key_list = calloc(key_count + 1, sizeof(char *)); value_list = calloc(key_count + 1, sizeof(void *)); value_len_list = calloc(key_count + 1, sizeof(int)); if (!key_list || !value_list || !value_len_list) { bxt_err("calloc error OOM"); ret = BUXTON_ERROR_OUT_OF_MEMORY; goto end; } for (index = 0; index < key_count; index++) { sql_ret = sqlite3_step(stmt); if (sql_ret != SQLITE_ROW) { bxt_err("sqlite3_step error, ret = %d, extended = %d index = %d , count = %d", sql_ret, sqlite3_extended_errcode(db), index, key_count); ret = BUXTON_ERROR_IO_ERROR; goto end; } key_list[index] = strdup((char *)sqlite3_column_text(stmt, 0)); value_len_list[index] = sqlite3_column_bytes(stmt, 1); if (value_len_list[index] > 0) { value_list[index] = malloc(sizeof(void *) * (value_len_list[index] + 1)); if (value_list[index] == NULL) { bxt_err("malloc error"); ret = BUXTON_ERROR_OUT_OF_MEMORY; goto end; } memcpy(value_list[index], (void *)sqlite3_column_blob(stmt, 1), value_len_list[index]); } else { value_list[index] = NULL; } } *keys = key_list; *values = value_list; *value_len = value_len_list; *klen = key_count; end: if (stmt != NULL) { sql_ret = sqlite3_finalize(stmt); if (sql_ret != SQLITE_OK) { bxt_err("Sqlite3 error [%d] : <%s> finalizing statement\n", sql_ret, sqlite3_errmsg(db)); ret = BUXTON_ERROR_IO_ERROR; } } if (ret != BUXTON_ERROR_NONE) { if (key_list) { for (index = 0; index < key_count; index++) { if (key_list[index]) free(key_list[index]); } free(key_list); } if (value_list) { for (index = 0; index < key_count; index++) { if (value_list[index]) free(value_list[index]); } free(value_list); } if (value_len_list) free(value_len_list); } bxt_dbg("dump '%s'", dbpath); return ret; } static int check_integrity_cb(void *pid, int argc, char **argv, char **notUsed) { if (strcmp(argv[0], "ok")) { bxt_err("db integrity result : %s", argv[0]); is_db_corrupted = true; return -1; } bxt_dbg("db integrity result : %s", argv[0]); return 0; } static void delete_db_file(const char *dbpath) { char target_path[MAX_PATH_LEN]; int ret; bxt_info("delet db file %s", dbpath); ret = unlink(dbpath); if (ret != 0) bxt_err("unlink(%s) is failed. errno(%d)", dbpath, ret); snprintf(target_path, sizeof(target_path), "%s-shm", dbpath); ret = unlink(target_path); if (ret != 0) bxt_err("unlink(%s) is failed. errno(%d)", target_path, ret); snprintf(target_path, sizeof(target_path), "%s-wal", dbpath); ret = unlink(target_path); if (ret != 0) bxt_err("unlink(%s) is failed. errno(%d)", target_path, ret); } /* LCOV_EXCL_START */ static int recover_corrupted_db(const char *dbpath) { int sql_ret; sqlite3 *db; bxt_info("DB is corrupted, start to recover corrupted db %s", dbpath); delete_db_file(dbpath); sql_ret = sqlite3_open_v2(dbpath, &db, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, NULL); if (sql_ret != SQLITE_OK) { bxt_err("Failed to open db[%s : %d] %s", dbpath, sql_ret, sqlite3_errmsg(db)); return BUXTON_ERROR_IO_ERROR; } sql_ret = sqlite3_exec(db, "PRAGMA journal_mode = WAL", NULL, NULL, NULL); if (sql_ret) { bxt_err("change journal mode '%s' failed: %s", dbpath, sqlite3_errmsg(db)); sqlite3_close(db); return BUXTON_ERROR_IO_ERROR; } sql_ret = sqlite3_exec(db, QUERY_CREATE_TABLE_BUXTON, NULL, NULL, NULL); if (sql_ret != SQLITE_OK) { bxt_err("Create tables '%s' failed: %s", dbpath, sqlite3_errmsg(db)); sqlite3_close(db); return BUXTON_ERROR_IO_ERROR; } sql_ret = sqlite3_wal_autocheckpoint( db, BUXTON_DEFAULT_WAL_AUTOCHECKPOINT); if (sql_ret != SQLITE_OK) bxt_err("SET DEFAULT_WAL_AUTOCHECKPOINT failed : %d", sql_ret); sqlite3_close(db); return BUXTON_ERROR_NONE; } /* LCOV_EXCL_STOP */ static int check_integrity(sqlite3 *db) { int ret; ret = sqlite3_exec(db, "PRAGMA integrity_check", check_integrity_cb, NULL, NULL); if (ret != SQLITE_OK || is_db_corrupted) { bxt_err("Failed to exec query[%d][%s]", ret, sqlite3_errmsg(db)); return BUXTON_ERROR_IO_ERROR; } return BUXTON_ERROR_NONE; } static int check_table(sqlite3 *db) { int ret; int count = 0; const char query[] = "SELECT count(name) FROM sqlite_master WHERE type='table'"; sqlite3_stmt *stmt; ret = sqlite3_prepare_v2(db, query, strlen(query), &stmt, NULL); if (ret != SQLITE_OK) { bxt_err("prepare error: %s", sqlite3_errmsg(db)); return BUXTON_ERROR_IO_ERROR; } ret = sqlite3_step(stmt); if(ret == SQLITE_ROW) count = sqlite3_column_int(stmt, 0); if (count == 0) { bxt_err("table error: %d", ret); ret = BUXTON_ERROR_IO_ERROR; } else { ret = BUXTON_ERROR_NONE; } sqlite3_finalize(stmt); return ret; } static int check_owner(const char *dbpath) { struct stat info; int ret; ret = stat(dbpath, &info); if (ret < 0) { bxt_err("Failed to get file(%s) status. errno(%d)", dbpath, errno); return BUXTON_ERROR_IO_ERROR; } if (info.st_uid != getuid()) { bxt_err("invalid owner [%d : %d]", getuid(), info.st_uid); return BUXTON_ERROR_IO_ERROR; } return BUXTON_ERROR_NONE; } static int check_db(const char *dbpath) { int ret; sqlite3 *db; ret = sqlite3_open_v2(dbpath, &db, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, NULL); if (ret != SQLITE_OK) { bxt_err("Failed to open db[%s : %d] %s", dbpath, ret, sqlite3_errmsg(db)); ret = BUXTON_ERROR_IO_ERROR; goto out; } ret = check_owner(dbpath); if (ret != BUXTON_ERROR_NONE) goto out; ret = check_integrity(db); if (ret != BUXTON_ERROR_NONE) goto out; ret = check_table(db); if (ret != BUXTON_ERROR_NONE) goto out; out: if (db) sqlite3_close(db); if (ret != BUXTON_ERROR_NONE) ret = recover_corrupted_db(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) { bxt_err("out of memory"); return BUXTON_ERROR_OUT_OF_MEMORY; } return BUXTON_ERROR_NONE; } 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, .get_dump = get_dump, .check_db = check_db, };