diff options
Diffstat (limited to 'main/db.c')
-rw-r--r-- | main/db.c | 618 |
1 files changed, 377 insertions, 241 deletions
@@ -20,11 +20,11 @@ * * \brief ASTdb Management * - * \author Mark Spencer <markster@digium.com> + * \author Mark Spencer <markster@digium.com> * * \note DB3 is licensed under Sleepycat Public License and is thus incompatible - * with GPL. To avoid having to make another exception (and complicate - * licensing even further) we elect to use DB1 which is BSD licensed + * with GPL. To avoid having to make another exception (and complicate + * licensing even further) we elect to use DB1 which is BSD licensed */ #include "asterisk.h" @@ -34,8 +34,12 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/_private.h" #include "asterisk/paths.h" /* use ast_config_AST_DB */ #include <sys/time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> #include <signal.h> #include <dirent.h> +#include <sqlite3.h> #include "asterisk/channel.h" #include "asterisk/file.h" @@ -44,9 +48,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/astdb.h" #include "asterisk/cli.h" #include "asterisk/utils.h" -#include "asterisk/lock.h" #include "asterisk/manager.h" -#include "db1-ast/include/db.h" /*** DOCUMENTATION <manager name="DBGet" language="en_US"> @@ -101,242 +103,362 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") ***/ #define MAX_DB_FIELD 256 - -static DB *astdb; AST_MUTEX_DEFINE_STATIC(dblock); static ast_cond_t dbcond; -typedef int (*process_keys_cb)(DBT *key, DBT *value, const char *filter, void *data); +static sqlite3 *astdb; +static pthread_t syncthread; +static int doexit; static void db_sync(void); -static int dbinit(void) +#define DEFINE_SQL_STATEMENT(stmt,sql) static sqlite3_stmt *stmt; \ + const char stmt##_sql[] = sql; + +DEFINE_SQL_STATEMENT(put_stmt, "INSERT OR REPLACE INTO astdb (key, value) VALUES (?, ?)") +DEFINE_SQL_STATEMENT(get_stmt, "SELECT value FROM astdb WHERE key=?") +DEFINE_SQL_STATEMENT(del_stmt, "DELETE FROM astdb WHERE key=?") +DEFINE_SQL_STATEMENT(deltree_stmt, "DELETE FROM astdb WHERE key LIKE ? || '/' || '%'") +DEFINE_SQL_STATEMENT(deltree_all_stmt, "DELETE FROM astdb") +DEFINE_SQL_STATEMENT(gettree_stmt, "SELECT key, value FROM astdb WHERE key LIKE ? || '/' || '%'") +DEFINE_SQL_STATEMENT(gettree_all_stmt, "SELECT key, value FROM astdb") +DEFINE_SQL_STATEMENT(showkey_stmt, "SELECT key, value FROM astdb WHERE key LIKE '%' || '/' || ?") +DEFINE_SQL_STATEMENT(create_astdb_stmt, "CREATE TABLE IF NOT EXISTS astdb(key VARCHAR(256), value VARCHAR(256), PRIMARY KEY(key))") + +static int init_stmt(sqlite3_stmt **stmt, const char *sql, size_t len) { - if (!astdb && !(astdb = dbopen(ast_config_AST_DB, O_CREAT | O_RDWR, AST_FILE_MODE, DB_BTREE, NULL))) { - ast_log(LOG_WARNING, "Unable to open Asterisk database '%s': %s\n", ast_config_AST_DB, strerror(errno)); + ast_mutex_lock(&dblock); + if (sqlite3_prepare_v2(astdb, sql, len, stmt, NULL) != SQLITE_OK) { + ast_log(LOG_WARNING, "Couldn't prepare statement '%s': %s\n", sql, sqlite3_errmsg(astdb)); + ast_mutex_unlock(&dblock); return -1; } + ast_mutex_unlock(&dblock); + return 0; } - -static inline int keymatch(const char *key, const char *prefix) +static int init_statements(void) { - int preflen = strlen(prefix); - if (!preflen) - return 1; - if (!strcasecmp(key, prefix)) - return 1; - if ((strlen(key) > preflen) && !strncasecmp(key, prefix, preflen)) { - if (key[preflen] == '/') - return 1; - } - return 0; + /* Don't initialize create_astdb_statment here as the astdb table needs to exist + * brefore these statments can be initialized */ + return init_stmt(&get_stmt, get_stmt_sql, sizeof(get_stmt_sql)) + || init_stmt(&del_stmt, del_stmt_sql, sizeof(del_stmt_sql)) + || init_stmt(&deltree_stmt, deltree_stmt_sql, sizeof(deltree_stmt_sql)) + || init_stmt(&deltree_all_stmt, deltree_all_stmt_sql, sizeof(deltree_all_stmt_sql)) + || init_stmt(&gettree_stmt, gettree_stmt_sql, sizeof(gettree_stmt_sql)) + || init_stmt(&gettree_all_stmt, gettree_all_stmt_sql, sizeof(gettree_all_stmt_sql)) + || init_stmt(&showkey_stmt, showkey_stmt_sql, sizeof(showkey_stmt_sql)) + || init_stmt(&put_stmt, put_stmt_sql, sizeof(put_stmt_sql)); } -static inline int subkeymatch(const char *key, const char *suffix) +static int convert_bdb_to_sqlite3(void) { - int suffixlen = strlen(suffix); - if (suffixlen) { - const char *subkey = key + strlen(key) - suffixlen; - if (subkey < key) - return 0; - if (!strcasecmp(subkey, suffix)) - return 1; - } - return 0; + char *cmd; + int res; + + ast_asprintf(&cmd, "astdb2sqlite3 '%s'\n", ast_config_AST_DB); + res = ast_safe_system(cmd); + ast_free(cmd); + + return res; } -static const char *dbt_data2str(DBT *dbt) +static int db_create_astdb(void) { - char *data = ""; + int res = 0; - if (dbt->size) { - data = dbt->data; - data[dbt->size - 1] = '\0'; + if (!create_astdb_stmt) { + init_stmt(&create_astdb_stmt, create_astdb_stmt_sql, sizeof(create_astdb_stmt_sql)); } - return data; -} + ast_mutex_lock(&dblock); + if (sqlite3_step(create_astdb_stmt) != SQLITE_DONE) { + ast_log(LOG_WARNING, "Couldn't create astdb table: %s\n", sqlite3_errmsg(astdb)); + res = -1; + } + sqlite3_reset(create_astdb_stmt); + db_sync(); + ast_mutex_unlock(&dblock); -static inline const char *dbt_data2str_full(DBT *dbt, const char *def) -{ - return S_OR(dbt_data2str(dbt), def); + return res; } -static int process_db_keys(process_keys_cb cb, void *data, const char *filter, int sync) +static int db_open(void) { - DBT key = { 0, }, value = { 0, }, last_key = { 0, }; - int counter = 0; - int res, last = 0; - char last_key_s[MAX_DB_FIELD]; + char *dbname; + struct stat dont_care; + + if (!(dbname = alloca(strlen(ast_config_AST_DB) + sizeof(".sqlite3")))) { + return -1; + } + strcpy(dbname, ast_config_AST_DB); + strcat(dbname, ".sqlite3"); + + if (stat(dbname, &dont_care) && !stat(ast_config_AST_DB, &dont_care)) { + if (convert_bdb_to_sqlite3()) { + ast_log(LOG_ERROR, "*** Database conversion failed!\n"); + ast_log(LOG_ERROR, "*** Asterisk now uses SQLite3 for its internal\n"); + ast_log(LOG_ERROR, "*** database. Conversion from the old astdb\n"); + ast_log(LOG_ERROR, "*** failed. Most likely the astdb2sqlite3 utility\n"); + ast_log(LOG_ERROR, "*** was not selected for build. To convert the\n"); + ast_log(LOG_ERROR, "*** old astdb, please delete '%s'\n", dbname); + ast_log(LOG_ERROR, "*** and re-run 'make menuselect' and select astdb2sqlite3\n"); + ast_log(LOG_ERROR, "*** in the Utilities section, then 'make && make install'.\n"); + sleep(5); + } else { + ast_log(LOG_NOTICE, "Database conversion succeeded!\n"); + } + } ast_mutex_lock(&dblock); - if (dbinit()) { + if (sqlite3_open_v2(dbname, &astdb, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX, NULL) != SQLITE_OK) { + ast_log(LOG_WARNING, "Unable to open Asterisk database '%s': %s\n", dbname, sqlite3_errmsg(astdb)); + sqlite3_close(astdb); ast_mutex_unlock(&dblock); return -1; } + ast_mutex_unlock(&dblock); - /* Somehow, the database can become corrupted such that astdb->seq will continue looping through - * the database indefinitely. The pointer to last_key.data ends up getting re-used by the BDB lib - * so this specifically queries for the last entry, makes a copy of the key, and then uses it as - * a sentinel to avoid repeatedly looping over the list. */ + return 0; +} - if (astdb->seq(astdb, &last_key, &value, R_LAST)) { - /* Empty database */ - ast_mutex_unlock(&dblock); +static int db_init(void) +{ + if (astdb) { return 0; } - memcpy(last_key_s, last_key.data, MIN(last_key.size - 1, sizeof(last_key_s))); - last_key_s[last_key.size - 1] = '\0'; - for (res = astdb->seq(astdb, &key, &value, R_FIRST); - !res; - res = astdb->seq(astdb, &key, &value, R_NEXT)) { - /* The callback might delete the key, so we have to check it before calling */ - last = !strcmp(dbt_data2str_full(&key, "<bad key>"), last_key_s); - counter += cb(&key, &value, filter, data); - if (last) { - break; - } + if (db_open() || db_create_astdb() || init_statements()) { + return -1; } - if (sync) { - db_sync(); + return 0; +} + +/* We purposely don't lock around the sqlite3 call because the transaction + * calls will be called with the database lock held. For any other use, make + * sure to take the dblock yourself. */ +static int db_execute_sql(const char *sql, int (*callback)(void *, int, char **, char **), void *arg) +{ + char *errmsg = NULL; + int res =0; + + sqlite3_exec(astdb, sql, callback, arg, &errmsg); + if (errmsg) { + ast_log(LOG_WARNING, "Error executing SQL: %s\n", errmsg); + sqlite3_free(errmsg); + res = -1; } - ast_mutex_unlock(&dblock); + return res; +} - return counter; +static int ast_db_begin_transaction(void) +{ + return db_execute_sql("BEGIN TRANSACTION", NULL, NULL); } -static int db_deltree_cb(DBT *key, DBT *value, const char *filter, void *data) +static int ast_db_commit_transaction(void) { - int res = 0; + return db_execute_sql("COMMIT", NULL, NULL); +} - if (keymatch(dbt_data2str_full(key, "<bad key>"), filter)) { - astdb->del(astdb, key, 0); - res = 1; - } - return res; +static int ast_db_rollback_transaction(void) +{ + return db_execute_sql("ROLLBACK", NULL, NULL); } -int ast_db_deltree(const char *family, const char *keytree) +int ast_db_put(const char *family, const char *key, const char *value) { - char prefix[MAX_DB_FIELD]; + char fullkey[MAX_DB_FIELD]; + size_t fullkey_len; + int res = 0; - if (family) { - if (keytree) { - snprintf(prefix, sizeof(prefix), "/%s/%s", family, keytree); - } else { - snprintf(prefix, sizeof(prefix), "/%s", family); - } - } else if (keytree) { + if (strlen(family) + strlen(key) + 2 > sizeof(fullkey) - 1) { + ast_log(LOG_WARNING, "Family and key length must be less than %zu bytes\n", sizeof(fullkey) - 3); return -1; - } else { - prefix[0] = '\0'; } - return process_db_keys(db_deltree_cb, NULL, prefix, 1); + fullkey_len = snprintf(fullkey, sizeof(fullkey), "/%s/%s", family, key); + + ast_mutex_lock(&dblock); + if (sqlite3_bind_text(put_stmt, 1, fullkey, fullkey_len, SQLITE_STATIC) != SQLITE_OK) { + ast_log(LOG_WARNING, "Couldn't bind key to stmt: %s\n", sqlite3_errmsg(astdb)); + res = -1; + } else if (sqlite3_bind_text(put_stmt, 2, value, -1, SQLITE_STATIC) != SQLITE_OK) { + ast_log(LOG_WARNING, "Couldn't bind value to stmt: %s\n", sqlite3_errmsg(astdb)); + res = -1; + } else if (sqlite3_step(put_stmt) != SQLITE_DONE) { + ast_log(LOG_WARNING, "Couldn't execute statment: %s\n", sqlite3_errmsg(astdb)); + res = -1; + } + + sqlite3_reset(put_stmt); + db_sync(); + ast_mutex_unlock(&dblock); + + return res; } -int ast_db_put(const char *family, const char *keys, const char *value) +int ast_db_get(const char *family, const char *key, char *value, int valuelen) { + const unsigned char *result; char fullkey[MAX_DB_FIELD]; - DBT key, data; - int res, fullkeylen; + size_t fullkey_len; + int res = 0; - ast_mutex_lock(&dblock); - if (dbinit()) { - ast_mutex_unlock(&dblock); + if (strlen(family) + strlen(key) + 2 > sizeof(fullkey) - 1) { + ast_log(LOG_WARNING, "Family and key length must be less than %zu bytes\n", sizeof(fullkey) - 3); return -1; } - fullkeylen = snprintf(fullkey, sizeof(fullkey), "/%s/%s", family, keys); - memset(&key, 0, sizeof(key)); - memset(&data, 0, sizeof(data)); - key.data = fullkey; - key.size = fullkeylen + 1; - data.data = (char *) value; - data.size = strlen(value) + 1; - res = astdb->put(astdb, &key, &data, 0); - db_sync(); + fullkey_len = snprintf(fullkey, sizeof(fullkey), "/%s/%s", family, key); + + ast_mutex_lock(&dblock); + if (sqlite3_bind_text(get_stmt, 1, fullkey, fullkey_len, SQLITE_STATIC) != SQLITE_OK) { + ast_log(LOG_WARNING, "Couldn't bind key to stmt: %s\n", sqlite3_errmsg(astdb)); + res = -1; + } else if (sqlite3_step(get_stmt) != SQLITE_ROW) { + ast_debug(1, "Unable to find key '%s' in family '%s'\n", key, family); + res = -1; + } else if (!(result = sqlite3_column_text(get_stmt, 0))) { + ast_log(LOG_WARNING, "Couldn't get value\n"); + res = -1; + } else { + strncpy(value, (const char *) result, valuelen); + } + sqlite3_reset(get_stmt); ast_mutex_unlock(&dblock); - if (res) - ast_log(LOG_WARNING, "Unable to put value '%s' for key '%s' in family '%s'\n", value, keys, family); return res; } -int ast_db_get(const char *family, const char *keys, char *value, int valuelen) +int ast_db_del(const char *family, const char *key) { - char fullkey[MAX_DB_FIELD] = ""; - DBT key, data; - int res, fullkeylen; + char fullkey[MAX_DB_FIELD]; + size_t fullkey_len; + int res = 0; - ast_mutex_lock(&dblock); - if (dbinit()) { - ast_mutex_unlock(&dblock); + if (strlen(family) + strlen(key) + 2 > sizeof(fullkey) - 1) { + ast_log(LOG_WARNING, "Family and key length must be less than %zu bytes\n", sizeof(fullkey) - 3); return -1; } - fullkeylen = snprintf(fullkey, sizeof(fullkey), "/%s/%s", family, keys); - memset(&key, 0, sizeof(key)); - memset(&data, 0, sizeof(data)); - memset(value, 0, valuelen); - key.data = fullkey; - key.size = fullkeylen + 1; + fullkey_len = snprintf(fullkey, sizeof(fullkey), "/%s/%s", family, key); - res = astdb->get(astdb, &key, &data, 0); + ast_mutex_lock(&dblock); + if (sqlite3_bind_text(del_stmt, 1, fullkey, fullkey_len, SQLITE_STATIC) != SQLITE_OK) { + ast_log(LOG_WARNING, "Couldn't bind key to stmt: %s\n", sqlite3_errmsg(astdb)); + res = -1; + } else if (sqlite3_step(del_stmt) != SQLITE_DONE) { + ast_debug(1, "Unable to find key '%s' in family '%s'\n", key, family); + res = -1; + } + sqlite3_reset(del_stmt); + db_sync(); + ast_mutex_unlock(&dblock); - /* Be sure to NULL terminate our data either way */ - if (res) { - ast_debug(1, "Unable to find key '%s' in family '%s'\n", keys, family); - } else { -#if 0 - printf("Got value of size %d\n", data.size); -#endif - if (data.size) { - ((char *)data.data)[data.size - 1] = '\0'; - /* Make sure that we don't write too much to the dst pointer or we don't read too much from the source pointer */ - ast_copy_string(value, data.data, (valuelen > data.size) ? data.size : valuelen); + return res; +} + +int ast_db_deltree(const char *family, const char *subfamily) +{ + sqlite3_stmt *stmt = deltree_stmt; + char prefix[MAX_DB_FIELD]; + int res = 0; + + if (!ast_strlen_zero(family)) { + if (!ast_strlen_zero(subfamily)) { + /* Family and key tree */ + snprintf(prefix, sizeof(prefix), "/%s/%s", family, subfamily); } else { - ast_log(LOG_NOTICE, "Strange, empty value for /%s/%s\n", family, keys); + /* Family only */ + snprintf(prefix, sizeof(prefix), "/%s", family); } + } else { + prefix[0] = '\0'; + stmt = deltree_all_stmt; } - /* Data is not fully isolated for concurrency, so the lock must be extended - * to after the copy to the output buffer. */ + ast_mutex_lock(&dblock); + if (!ast_strlen_zero(prefix) && (sqlite3_bind_text(stmt, 1, prefix, -1, SQLITE_STATIC) != SQLITE_OK)) { + ast_log(LOG_WARNING, "Could bind %s to stmt: %s\n", prefix, sqlite3_errmsg(astdb)); + res = -1; + } else if (sqlite3_step(stmt) != SQLITE_DONE) { + ast_log(LOG_WARNING, "Couldn't execute stmt: %s\n", sqlite3_errmsg(astdb)); + res = -1; + } + res = sqlite3_changes(astdb); + sqlite3_reset(stmt); + db_sync(); ast_mutex_unlock(&dblock); return res; } -int ast_db_del(const char *family, const char *keys) +struct ast_db_entry *ast_db_gettree(const char *family, const char *subfamily) { - char fullkey[MAX_DB_FIELD]; - DBT key; - int res, fullkeylen; + char prefix[MAX_DB_FIELD]; + sqlite3_stmt *stmt = gettree_stmt; + struct ast_db_entry *cur, *last = NULL, *ret = NULL; + + if (!ast_strlen_zero(family)) { + if (!ast_strlen_zero(subfamily)) { + /* Family and key tree */ + snprintf(prefix, sizeof(prefix), "/%s/%s", family, subfamily); + } else { + /* Family only */ + snprintf(prefix, sizeof(prefix), "/%s", family); + } + } else { + prefix[0] = '\0'; + stmt = gettree_all_stmt; + } ast_mutex_lock(&dblock); - if (dbinit()) { + if (!ast_strlen_zero(prefix) && (sqlite3_bind_text(stmt, 1, prefix, -1, SQLITE_STATIC) != SQLITE_OK)) { + ast_log(LOG_WARNING, "Could bind %s to stmt: %s\n", prefix, sqlite3_errmsg(astdb)); + sqlite3_reset(stmt); ast_mutex_unlock(&dblock); - return -1; + return NULL; } - - fullkeylen = snprintf(fullkey, sizeof(fullkey), "/%s/%s", family, keys); - memset(&key, 0, sizeof(key)); - key.data = fullkey; - key.size = fullkeylen + 1; - - res = astdb->del(astdb, &key, 0); - db_sync(); - + + while (sqlite3_step(stmt) == SQLITE_ROW) { + const char *key_s, *value_s; + if (!(key_s = (const char *) sqlite3_column_text(stmt, 0))) { + break; + } + if (!(value_s = (const char *) sqlite3_column_text(stmt, 1))) { + break; + } + if (!(cur = ast_malloc(sizeof(*cur) + strlen(key_s) + strlen(value_s) + 2))) { + break; + } + cur->next = NULL; + cur->key = cur->data + strlen(value_s) + 1; + strcpy(cur->data, value_s); + strcpy(cur->key, key_s); + if (last) { + last->next = cur; + } else { + ret = cur; + } + last = cur; + } + sqlite3_reset(stmt); ast_mutex_unlock(&dblock); - if (res) { - ast_debug(1, "Unable to find key '%s' in family '%s'\n", keys, family); + return ret; +} + +void ast_db_freetree(struct ast_db_entry *dbe) +{ + struct ast_db_entry *last; + while (dbe) { + last = dbe; + dbe = dbe->next; + ast_free(last); } - return res; } static char *handle_cli_database_put(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) @@ -429,8 +551,8 @@ static char *handle_cli_database_deltree(struct ast_cli_entry *e, int cmd, struc case CLI_INIT: e->command = "database deltree"; e->usage = - "Usage: database deltree <family> [keytree]\n" - " Deletes a family or specific keytree within a family\n" + "Usage: database deltree <family> [subfamily]\n" + " Deletes a family or specific subfamily within a family\n" " in the Asterisk database.\n"; return NULL; case CLI_GENERATE: @@ -452,32 +574,19 @@ static char *handle_cli_database_deltree(struct ast_cli_entry *e, int cmd, struc return CLI_SUCCESS; } -static int db_show_cb(DBT *key, DBT *value, const char *filter, void *data) -{ - struct ast_cli_args *a = data; - const char *key_s = dbt_data2str_full(key, "<bad key>"); - const char *value_s = dbt_data2str_full(value, "<bad value>"); - - if (keymatch(key_s, filter)) { - ast_cli(a->fd, "%-50s: %-25s\n", key_s, value_s); - return 1; - } - - return 0; -} - static char *handle_cli_database_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { char prefix[MAX_DB_FIELD]; int counter = 0; + sqlite3_stmt *stmt = gettree_stmt; switch (cmd) { case CLI_INIT: e->command = "database show"; e->usage = - "Usage: database show [family [keytree]]\n" + "Usage: database show [family [subfamily]]\n" " Shows Asterisk database contents, optionally restricted\n" - " to a given family, or family and keytree.\n"; + " to a given family, or family and subfamily.\n"; return NULL; case CLI_GENERATE: return NULL; @@ -492,118 +601,123 @@ static char *handle_cli_database_show(struct ast_cli_entry *e, int cmd, struct a } else if (a->argc == 2) { /* Neither */ prefix[0] = '\0'; + stmt = gettree_all_stmt; + } else { return CLI_SHOWUSAGE; } - if((counter = process_db_keys(db_show_cb, a, prefix, 0)) < 0) { - ast_cli(a->fd, "Database unavailable\n"); - return CLI_SUCCESS; + ast_mutex_lock(&dblock); + if (!ast_strlen_zero(prefix) && (sqlite3_bind_text(stmt, 1, prefix, -1, SQLITE_STATIC) != SQLITE_OK)) { + ast_log(LOG_WARNING, "Could bind %s to stmt: %s\n", prefix, sqlite3_errmsg(astdb)); + sqlite3_reset(stmt); + ast_mutex_unlock(&dblock); + return NULL; } - ast_cli(a->fd, "%d results found.\n", counter); - return CLI_SUCCESS; -} - -static int db_showkey_cb(DBT *key, DBT *value, const char *filter, void *data) -{ - struct ast_cli_args *a = data; - const char *key_s = dbt_data2str_full(key, "<bad key>"); - const char *value_s = dbt_data2str_full(value, "<bad value>"); - - if (subkeymatch(key_s, filter)) { + while (sqlite3_step(stmt) == SQLITE_ROW) { + const char *key_s, *value_s; + if (!(key_s = (const char *) sqlite3_column_text(stmt, 0))) { + ast_log(LOG_WARNING, "Skipping invalid key!\n"); + continue; + } + if (!(value_s = (const char *) sqlite3_column_text(stmt, 1))) { + ast_log(LOG_WARNING, "Skipping invalid value!\n"); + continue; + } + ++counter; ast_cli(a->fd, "%-50s: %-25s\n", key_s, value_s); - return 1; } - return 0; + sqlite3_reset(stmt); + ast_mutex_unlock(&dblock); + + ast_cli(a->fd, "%d results found.\n", counter); + return CLI_SUCCESS; } static char *handle_cli_database_showkey(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { - char suffix[MAX_DB_FIELD]; int counter = 0; switch (cmd) { case CLI_INIT: e->command = "database showkey"; e->usage = - "Usage: database showkey <keytree>\n" + "Usage: database showkey <subfamily>\n" " Shows Asterisk database contents, restricted to a given key.\n"; return NULL; case CLI_GENERATE: return NULL; } - if (a->argc == 3) { - /* Key only */ - snprintf(suffix, sizeof(suffix), "/%s", a->argv[2]); - } else { + if (a->argc != 3) { return CLI_SHOWUSAGE; } - if ((counter = process_db_keys(db_showkey_cb, a, suffix, 0)) < 0) { - ast_cli(a->fd, "Database unavailable\n"); - return CLI_SUCCESS; + ast_mutex_lock(&dblock); + if (!ast_strlen_zero(a->argv[2]) && (sqlite3_bind_text(showkey_stmt, 1, a->argv[2], -1, SQLITE_STATIC) != SQLITE_OK)) { + ast_log(LOG_WARNING, "Could bind %s to stmt: %s\n", a->argv[2], sqlite3_errmsg(astdb)); + sqlite3_reset(showkey_stmt); + ast_mutex_unlock(&dblock); + return NULL; + } + + while (sqlite3_step(showkey_stmt) == SQLITE_ROW) { + const char *key_s, *value_s; + if (!(key_s = (const char *) sqlite3_column_text(showkey_stmt, 0))) { + break; + } + if (!(value_s = (const char *) sqlite3_column_text(showkey_stmt, 1))) { + break; + } + ++counter; + ast_cli(a->fd, "%-50s: %-25s\n", key_s, value_s); } + sqlite3_reset(showkey_stmt); + ast_mutex_unlock(&dblock); ast_cli(a->fd, "%d results found.\n", counter); return CLI_SUCCESS; } -static int db_gettree_cb(DBT *key, DBT *value, const char *filter, void *data) +static int display_results(void *arg, int columns, char **values, char **colnames) { - struct ast_db_entry **ret = data; - struct ast_db_entry *cur; - const char *key_s = dbt_data2str_full(key, "<bad key>"); - const char *value_s = dbt_data2str_full(value, "<bad value>"); - size_t key_slen = strlen(key_s) + 1, value_slen = strlen(value_s) + 1; + struct ast_cli_args *a = arg; + size_t x; - if (keymatch(key_s, filter) && (cur = ast_malloc(sizeof(*cur) + key_slen + value_slen))) { - cur->next = *ret; - cur->key = cur->data + value_slen; - strcpy(cur->data, value_s); - strcpy(cur->key, key_s); - *ret = cur; - return 1; + for (x = 0; x < columns; x++) { + ast_cli(a->fd, "%-5s: %-50s\n", colnames[x], values[x]); } + ast_cli(a->fd, "\n"); return 0; } -struct ast_db_entry *ast_db_gettree(const char *family, const char *keytree) +static char *handle_cli_database_query(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { - char prefix[MAX_DB_FIELD]; - struct ast_db_entry *ret = NULL; - if (!ast_strlen_zero(family)) { - if (!ast_strlen_zero(keytree)) { - /* Family and key tree */ - snprintf(prefix, sizeof(prefix), "/%s/%s", family, keytree); - } else { - /* Family only */ - snprintf(prefix, sizeof(prefix), "/%s", family); - } - } else { - prefix[0] = '\0'; + switch (cmd) { + case CLI_INIT: + e->command = "database query"; + e->usage = + "Usage: database query \"<SQL Statement>\"\n" + " Run a user-specified SQL query on the database. Be careful.\n"; + return NULL; + case CLI_GENERATE: + return NULL; } - if (process_db_keys(db_gettree_cb, &ret, prefix, 0) < 0) { - ast_log(LOG_WARNING, "Database unavailable\n"); - return NULL; + if (a->argc != 3) { + return CLI_SHOWUSAGE; } - return ret; -} + ast_mutex_lock(&dblock); + db_execute_sql(a->argv[2], display_results, a); + db_sync(); /* Go ahead and sync the db in case they write */ + ast_mutex_unlock(&dblock); -void ast_db_freetree(struct ast_db_entry *dbe) -{ - struct ast_db_entry *last; - while (dbe) { - last = dbe; - dbe = dbe->next; - ast_free(last); - } + return CLI_SUCCESS; } static struct ast_cli_entry cli_database[] = { @@ -612,7 +726,8 @@ static struct ast_cli_entry cli_database[] = { AST_CLI_DEFINE(handle_cli_database_get, "Gets database value"), AST_CLI_DEFINE(handle_cli_database_put, "Adds/updates database value"), AST_CLI_DEFINE(handle_cli_database_del, "Removes database key/value"), - AST_CLI_DEFINE(handle_cli_database_deltree, "Removes database keytree/values") + AST_CLI_DEFINE(handle_cli_database_deltree, "Removes database subfamily/values"), + AST_CLI_DEFINE(handle_cli_database_query, "Run a user-specified query on the astdb"), }; static int manager_dbput(struct mansession *s, const struct message *m) @@ -726,7 +841,7 @@ static int manager_dbdeltree(struct mansession *s, const struct message *m) astman_send_error(s, m, "Database entry not found"); else astman_send_ack(s, m, "Key tree deleted successfully"); - + return 0; } @@ -754,27 +869,48 @@ static void db_sync(void) static void *db_sync_thread(void *data) { ast_mutex_lock(&dblock); + ast_db_begin_transaction(); for (;;) { + /* We're ok with spurious wakeups, so we don't worry about a predicate */ ast_cond_wait(&dbcond, &dblock); + if (ast_db_commit_transaction()) { + ast_db_rollback_transaction(); + } + if (doexit) { + ast_mutex_unlock(&dblock); + break; + } + ast_db_begin_transaction(); ast_mutex_unlock(&dblock); sleep(1); ast_mutex_lock(&dblock); - astdb->sync(astdb, 0); } return NULL; } +static void astdb_atexit(void) +{ + doexit = 1; + db_sync(); + pthread_join(syncthread, NULL); + ast_mutex_lock(&dblock); + sqlite3_close(astdb); + ast_mutex_unlock(&dblock); +} + int astdb_init(void) { - pthread_t dont_care; + if (db_init()) { + return -1; + } ast_cond_init(&dbcond, NULL); - if (ast_pthread_create_background(&dont_care, NULL, db_sync_thread, NULL)) { + if (ast_pthread_create_background(&syncthread, NULL, db_sync_thread, NULL)) { return -1; } - dbinit(); + ast_register_atexit(astdb_atexit); ast_cli_register_multiple(cli_database, ARRAY_LEN(cli_database)); ast_manager_register_xml("DBGet", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, manager_dbget); ast_manager_register_xml("DBPut", EVENT_FLAG_SYSTEM, manager_dbput); |