diff options
Diffstat (limited to 'src/mysql/mysql.c')
-rw-r--r-- | src/mysql/mysql.c | 698 |
1 files changed, 698 insertions, 0 deletions
diff --git a/src/mysql/mysql.c b/src/mysql/mysql.c new file mode 100644 index 0000000..098213b --- /dev/null +++ b/src/mysql/mysql.c @@ -0,0 +1,698 @@ +/* + This file is part of GNUnet + (C) 2012 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + GNUnet 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ +/** + * @file mysql/mysql.c + * @brief library to help with access to a MySQL database + * @author Christian Grothoff + */ +#include "platform.h" +#include <mysql/mysql.h> +#include "gnunet_mysql_lib.h" + +/** + * Maximum number of supported parameters for a prepared + * statement. Increase if needed. + */ +#define MAX_PARAM 16 + + +/** + * Die with an error message that indicates + * a failure of the command 'cmd' with the message given + * by strerror(errno). + */ +#define DIE_MYSQL(cmd, dbh) do { GNUNET_log_from (GNUNET_ERROR_TYPE__ERROR, "mysql", _("`%s' failed at %s:%d with error: %s\n"), cmd, __FILE__, __LINE__, mysql_error((dbh)->dbf)); GNUNET_abort(); } while(0); + +/** + * Log an error message at log-level 'level' that indicates + * a failure of the command 'cmd' on file 'filename' + * with the message given by strerror(errno). + */ +#define LOG_MYSQL(level, cmd, dbh) do { GNUNET_log_from (level, "mysql", _("`%s' failed at %s:%d with error: %s\n"), cmd, __FILE__, __LINE__, mysql_error((dbh)->dbf)); } while(0); + + +/** + * Mysql context. + */ +struct GNUNET_MYSQL_Context +{ + + /** + * Our configuration. + */ + const struct GNUNET_CONFIGURATION_Handle *cfg; + + /** + * Our section. + */ + const char *section; + + /** + * Handle to the mysql database. + */ + MYSQL *dbf; + + /** + * Head of list of our prepared statements. + */ + struct GNUNET_MYSQL_StatementHandle *shead; + + /** + * Tail of list of our prepared statements. + */ + struct GNUNET_MYSQL_StatementHandle *stail; + + /** + * Filename of "my.cnf" (msyql configuration). + */ + char *cnffile; + +}; + + +/** + * Handle for a prepared statement. + */ +struct GNUNET_MYSQL_StatementHandle +{ + + /** + * Kept in a DLL. + */ + struct GNUNET_MYSQL_StatementHandle *next; + + /** + * Kept in a DLL. + */ + struct GNUNET_MYSQL_StatementHandle *prev; + + /** + * Original query string. + */ + char *query; + + /** + * Handle to MySQL prepared statement. + */ + MYSQL_STMT *statement; + + /** + * Is the MySQL prepared statement valid, or do we need to re-initialize it? + */ + int valid; + +}; + + +/** + * Obtain the location of ".my.cnf". + * + * @param cfg our configuration + * @param section the section + * @return NULL on error + */ +static char * +get_my_cnf_path (const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *section) +{ + char *cnffile; + char *home_dir; + struct stat st; + +#ifndef WINDOWS + struct passwd *pw; +#endif + int configured; + +#ifndef WINDOWS + pw = getpwuid (getuid ()); + if (!pw) + { + GNUNET_log_from_strerror (GNUNET_ERROR_TYPE_ERROR, "mysql", "getpwuid"); + return NULL; + } + if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (cfg, section, "CONFIG")) + { + GNUNET_assert (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_filename (cfg, section, + "CONFIG", + &cnffile)); + configured = GNUNET_YES; + } + else + { + home_dir = GNUNET_strdup (pw->pw_dir); + GNUNET_asprintf (&cnffile, "%s/.my.cnf", home_dir); + GNUNET_free (home_dir); + configured = GNUNET_NO; + } +#else + home_dir = (char *) GNUNET_malloc (_MAX_PATH + 1); + plibc_conv_to_win_path ("~/", home_dir); + GNUNET_asprintf (&cnffile, "%s/.my.cnf", home_dir); + GNUNET_free (home_dir); + configured = GNUNET_NO; +#endif + GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, "mysql", + _("Trying to use file `%s' for MySQL configuration.\n"), + cnffile); + if ((0 != STAT (cnffile, &st)) || (0 != ACCESS (cnffile, R_OK)) || + (!S_ISREG (st.st_mode))) + { + if (configured == GNUNET_YES) + GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "mysql", + _("Could not access file `%s': %s\n"), cnffile, + STRERROR (errno)); + GNUNET_free (cnffile); + return NULL; + } + return cnffile; +} + + +/** + * Open the connection with the database (and initialize + * our default options). + * + * @param mc database context to initialze + * @return GNUNET_OK on success + */ +static int +iopen (struct GNUNET_MYSQL_Context *mc) +{ + char *mysql_dbname; + char *mysql_server; + char *mysql_user; + char *mysql_password; + unsigned long long mysql_port; + my_bool reconnect; + unsigned int timeout; + + mc->dbf = mysql_init (NULL); + if (mc->dbf == NULL) + return GNUNET_SYSERR; + if (mc->cnffile != NULL) + mysql_options (mc->dbf, MYSQL_READ_DEFAULT_FILE, mc->cnffile); + mysql_options (mc->dbf, MYSQL_READ_DEFAULT_GROUP, "client"); + reconnect = 0; + mysql_options (mc->dbf, MYSQL_OPT_RECONNECT, &reconnect); + mysql_options (mc->dbf, MYSQL_OPT_CONNECT_TIMEOUT, (const void *) &timeout); + mysql_options (mc->dbf, MYSQL_SET_CHARSET_NAME, "UTF8"); + timeout = 60; /* in seconds */ + mysql_options (mc->dbf, MYSQL_OPT_READ_TIMEOUT, (const void *) &timeout); + mysql_options (mc->dbf, MYSQL_OPT_WRITE_TIMEOUT, (const void *) &timeout); + mysql_dbname = NULL; + if (GNUNET_YES == + GNUNET_CONFIGURATION_have_value (mc->cfg, mc->section, "DATABASE")) + GNUNET_assert (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_string (mc->cfg, mc->section, + "DATABASE", + &mysql_dbname)); + else + mysql_dbname = GNUNET_strdup ("gnunet"); + mysql_user = NULL; + if (GNUNET_YES == + GNUNET_CONFIGURATION_have_value (mc->cfg, mc->section, "USER")) + { + GNUNET_assert (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_string (mc->cfg, mc->section, + "USER", &mysql_user)); + } + mysql_password = NULL; + if (GNUNET_YES == + GNUNET_CONFIGURATION_have_value (mc->cfg, mc->section, "PASSWORD")) + { + GNUNET_assert (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_string (mc->cfg, mc->section, + "PASSWORD", + &mysql_password)); + } + mysql_server = NULL; + if (GNUNET_YES == + GNUNET_CONFIGURATION_have_value (mc->cfg, mc->section, "HOST")) + { + GNUNET_assert (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_string (mc->cfg, mc->section, + "HOST", + &mysql_server)); + } + mysql_port = 0; + if (GNUNET_YES == + GNUNET_CONFIGURATION_have_value (mc->cfg, mc->section, "PORT")) + { + GNUNET_assert (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_number (mc->cfg, mc->section, + "PORT", &mysql_port)); + } + + GNUNET_assert (mysql_dbname != NULL); + mysql_real_connect (mc->dbf, mysql_server, mysql_user, mysql_password, + mysql_dbname, (unsigned int) mysql_port, NULL, + CLIENT_IGNORE_SIGPIPE); + GNUNET_free_non_null (mysql_server); + GNUNET_free_non_null (mysql_user); + GNUNET_free_non_null (mysql_password); + GNUNET_free (mysql_dbname); + if (mysql_error (mc->dbf)[0]) + { + LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR, "mysql_real_connect", mc); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Create a mysql context. + * + * @param cfg configuration + * @param section configuration section to use to get MySQL configuration options + * @return the mysql context + */ +struct GNUNET_MYSQL_Context * +GNUNET_MYSQL_context_create (const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *section) +{ + struct GNUNET_MYSQL_Context *mc; + + mc = GNUNET_malloc (sizeof (struct GNUNET_MYSQL_Context)); + mc->cfg = cfg; + mc->section = section; + mc->cnffile = get_my_cnf_path (cfg, section); + + return mc; +} + + +/** + * Close database connection and all prepared statements (we got a DB + * error). + * + * @param mc mysql context + */ +void +GNUNET_MYSQL_statements_invalidate (struct GNUNET_MYSQL_Context *mc) +{ + struct GNUNET_MYSQL_StatementHandle *sh; + + for (sh = mc->shead; NULL != sh; sh = sh->next) + { + if (GNUNET_YES == sh->valid) + { + mysql_stmt_close (sh->statement); + sh->valid = GNUNET_NO; + } + sh->statement = NULL; + } + if (NULL != mc->dbf) + { + mysql_close (mc->dbf); + mc->dbf = NULL; + } +} + + +/** + * Destroy a mysql context. Also frees all associated prepared statements. + * + * @param mc context to destroy + */ +void +GNUNET_MYSQL_context_destroy (struct GNUNET_MYSQL_Context *mc) +{ + struct GNUNET_MYSQL_StatementHandle *sh; + + GNUNET_MYSQL_statements_invalidate (mc); + while (NULL != (sh = mc->shead)) + { + GNUNET_CONTAINER_DLL_remove (mc->shead, mc->stail, sh); + GNUNET_free (sh->query); + GNUNET_free (sh); + } + GNUNET_free (mc); + mysql_library_end (); +} + + +/** + * Prepare a statement. Prepared statements are automatically discarded + * when the MySQL context is destroyed. + * + * @param mc mysql context + * @param query query text + * @return prepared statement, NULL on error + */ +struct GNUNET_MYSQL_StatementHandle * +GNUNET_MYSQL_statement_prepare (struct GNUNET_MYSQL_Context *mc, + const char *query) +{ + struct GNUNET_MYSQL_StatementHandle *sh; + + sh = GNUNET_malloc (sizeof (struct GNUNET_MYSQL_StatementHandle)); + sh->query = GNUNET_strdup (query); + GNUNET_CONTAINER_DLL_insert (mc->shead, mc->stail, sh); + return sh; +} + + +/** + * Run a SQL statement. + * + * @param mc mysql context + * @param sql SQL statement to run + * @return GNUNET_OK on success + * GNUNET_SYSERR if there was a problem + */ +int +GNUNET_MYSQL_statement_run (struct GNUNET_MYSQL_Context *mc, const char *sql) +{ + if ((NULL == mc->dbf) && (GNUNET_OK != iopen (mc))) + return GNUNET_SYSERR; + mysql_query (mc->dbf, sql); + if (mysql_error (mc->dbf)[0]) + { + LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR, "mysql_query", mc); + GNUNET_MYSQL_statements_invalidate (mc); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Prepare a statement for running. + * + * @param mc mysql context + * @param sh statement handle to prepare + * @return GNUNET_OK on success + */ +static int +prepare_statement (struct GNUNET_MYSQL_Context *mc, + struct GNUNET_MYSQL_StatementHandle *sh) +{ + if (GNUNET_YES == sh->valid) + return GNUNET_OK; + if ((NULL == mc->dbf) && (GNUNET_OK != iopen (mc))) + return GNUNET_SYSERR; + sh->statement = mysql_stmt_init (mc->dbf); + if (NULL == sh->statement) + { + GNUNET_MYSQL_statements_invalidate (mc); + return GNUNET_SYSERR; + } + if (0 != mysql_stmt_prepare (sh->statement, sh->query, strlen (sh->query))) + { + LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR, "mysql_stmt_prepare", mc); + mysql_stmt_close (sh->statement); + sh->statement = NULL; + GNUNET_MYSQL_statements_invalidate (mc); + return GNUNET_SYSERR; + } + sh->valid = GNUNET_YES; + return GNUNET_OK; +} + + +/** + * Get internal handle for a prepared statement. This function should rarely + * be used, and if, with caution! On failures during the interaction with + * the handle, you must call 'GNUNET_MYSQL_statements_invalidate'! + * + * @param mc mysql context + * @param sh prepared statement to introspect + * @return MySQL statement handle, NULL on error + */ +MYSQL_STMT * +GNUNET_MYSQL_statement_get_stmt (struct GNUNET_MYSQL_Context * mc, + struct GNUNET_MYSQL_StatementHandle * sh) +{ + (void) prepare_statement (mc, sh); + return sh->statement; +} + + +/** + * Bind the parameters for the given MySQL statement + * and run it. + * + * @param mc mysql context + * @param sh statement to bind and run + * @param ap arguments for the binding + * @return GNUNET_SYSERR on error, GNUNET_OK on success + */ +static int +init_params (struct GNUNET_MYSQL_Context *mc, + struct GNUNET_MYSQL_StatementHandle *sh, va_list ap) +{ + MYSQL_BIND qbind[MAX_PARAM]; + unsigned int pc; + unsigned int off; + enum enum_field_types ft; + + pc = mysql_stmt_param_count (sh->statement); + if (pc > MAX_PARAM) + { + /* increase internal constant! */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + memset (qbind, 0, sizeof (qbind)); + off = 0; + ft = 0; + while ((pc > 0) && (-1 != (int) (ft = va_arg (ap, enum enum_field_types)))) + { + qbind[off].buffer_type = ft; + switch (ft) + { + case MYSQL_TYPE_FLOAT: + qbind[off].buffer = va_arg (ap, float *); + + break; + case MYSQL_TYPE_LONGLONG: + qbind[off].buffer = va_arg (ap, unsigned long long *); + qbind[off].is_unsigned = va_arg (ap, int); + + break; + case MYSQL_TYPE_LONG: + qbind[off].buffer = va_arg (ap, unsigned int *); + qbind[off].is_unsigned = va_arg (ap, int); + + break; + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_BLOB: + qbind[off].buffer = va_arg (ap, void *); + qbind[off].buffer_length = va_arg (ap, unsigned long); + qbind[off].length = va_arg (ap, unsigned long *); + + break; + default: + /* unsupported type */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + pc--; + off++; + } + if (!((pc == 0) && (-1 != (int) ft) && (va_arg (ap, int) == -1))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (mysql_stmt_bind_param (sh->statement, qbind)) + { + GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "mysql", + _("`%s' failed at %s:%d with error: %s\n"), + "mysql_stmt_bind_param", __FILE__, __LINE__, + mysql_stmt_error (sh->statement)); + GNUNET_MYSQL_statements_invalidate (mc); + return GNUNET_SYSERR; + } + if (mysql_stmt_execute (sh->statement)) + { + GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "mysql", + _("`%s' failed at %s:%d with error: %s\n"), + "mysql_stmt_execute", __FILE__, __LINE__, + mysql_stmt_error (sh->statement)); + GNUNET_MYSQL_statements_invalidate (mc); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + + +/** + * Run a prepared SELECT statement. + * + * @param mc mysql context + * @param s statement to run + * @param result_size number of elements in results array + * @param results pointer to already initialized MYSQL_BIND + * array (of sufficient size) for passing results + * @param processor function to call on each result + * @param processor_cls extra argument to processor + * @param ap pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective + * values (size + buffer-reference for pointers); terminated + * with "-1" + * @return GNUNET_SYSERR on error, otherwise + * the number of successfully affected (or queried) rows + */ +int +GNUNET_MYSQL_statement_run_prepared_select_va (struct GNUNET_MYSQL_Context *mc, + struct + GNUNET_MYSQL_StatementHandle *s, + unsigned int result_size, + MYSQL_BIND * results, + GNUNET_MYSQL_DataProcessor + processor, void *processor_cls, + va_list ap) +{ + int ret; + unsigned int rsize; + int total; + + if (GNUNET_OK != prepare_statement (mc, s)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != init_params (mc, s, ap)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + rsize = mysql_stmt_field_count (s->statement); + if (rsize > result_size) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (mysql_stmt_bind_result (s->statement, results)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("`%s' failed at %s:%d with error: %s\n"), + "mysql_stmt_bind_result", __FILE__, __LINE__, + mysql_stmt_error (s->statement)); + GNUNET_MYSQL_statements_invalidate (mc); + return GNUNET_SYSERR; + } + + total = 0; + while (1) + { + ret = mysql_stmt_fetch (s->statement); + if (ret == MYSQL_NO_DATA) + break; + if (ret != 0) + { + GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "mysql", + _("`%s' failed at %s:%d with error: %s\n"), + "mysql_stmt_fetch", __FILE__, __LINE__, + mysql_stmt_error (s->statement)); + GNUNET_MYSQL_statements_invalidate (mc); + return GNUNET_SYSERR; + } + total++; + if ((NULL == processor) || + (GNUNET_OK != processor (processor_cls, rsize, results))) + break; + } + mysql_stmt_reset (s->statement); + return total; +} + + +/** + * Run a prepared SELECT statement. + * + * @param mc mysql context + * @param sh handle to SELECT statment + * @param result_size number of elements in results array + * @param results pointer to already initialized MYSQL_BIND + * array (of sufficient size) for passing results + * @param processor function to call on each result + * @param processor_cls extra argument to processor + * @param ... pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective + * values (size + buffer-reference for pointers); terminated + * with "-1" + * @return GNUNET_SYSERR on error, otherwise + * the number of successfully affected (or queried) rows + */ +int +GNUNET_MYSQL_statement_run_prepared_select (struct GNUNET_MYSQL_Context *mc, + struct GNUNET_MYSQL_StatementHandle + *sh, unsigned int result_size, + MYSQL_BIND * results, + GNUNET_MYSQL_DataProcessor + processor, void *processor_cls, ...) +{ + va_list ap; + int ret; + + va_start (ap, processor_cls); + ret = + GNUNET_MYSQL_statement_run_prepared_select_va (mc, sh, result_size, + results, processor, + processor_cls, ap); + va_end (ap); + return ret; +} + + +/** + * Run a prepared statement that does NOT produce results. + * + * @param mc mysql context + * @param sh handle to statment + * @param insert_id NULL or address where to store the row ID of whatever + * was inserted (only for INSERT statements!) + * @param ... pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective + * values (size + buffer-reference for pointers); terminated + * with "-1" + * @return GNUNET_SYSERR on error, otherwise + * the number of successfully affected rows + */ +int +GNUNET_MYSQL_statement_run_prepared (struct GNUNET_MYSQL_Context *mc, + struct GNUNET_MYSQL_StatementHandle *sh, + unsigned long long *insert_id, ...) +{ + va_list ap; + int affected; + + if (GNUNET_OK != prepare_statement (mc, sh)) + return GNUNET_SYSERR; + va_start (ap, insert_id); + if (GNUNET_OK != init_params (mc, sh, ap)) + { + va_end (ap); + return GNUNET_SYSERR; + } + va_end (ap); + affected = mysql_stmt_affected_rows (sh->statement); + if (NULL != insert_id) + *insert_id = (unsigned long long) mysql_stmt_insert_id (sh->statement); + mysql_stmt_reset (sh->statement); + return affected; +} + + +/* end of mysql.c */ |