X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=bacula%2Fsrc%2Fcats%2Fmysql.c;h=d722bf4d8ea0e443837707f417662483900d3c2e;hb=513c2c6cf9c7991273cf3330404575aafae6d8a2;hp=2a860263400016201376272457a53d85528f5c2c;hpb=e5f5d52c7ca02da4a5264a6fa2967d4a7958143f;p=bacula%2Fbacula diff --git a/bacula/src/cats/mysql.c b/bacula/src/cats/mysql.c index 2a86026340..d722bf4d8e 100644 --- a/bacula/src/cats/mysql.c +++ b/bacula/src/cats/mysql.c @@ -1,446 +1,779 @@ -/* - * Bacula Catalog Database routines specific to MySQL - * These are MySQL specific routines -- hopefully all - * other files are generic. +/* + Bacula(R) - The Network Backup Solution + + Copyright (C) 2000-2016 Kern Sibbald + + The original author of Bacula is Kern Sibbald, with contributions + from many others, a complete list can be found in the file AUTHORS. + + You may use this file and others of this release according to the + license defined in the LICENSE file, which includes the Affero General + Public License, v3.0 ("AGPLv3") and some additional permissions and + terms pursuant to its AGPLv3 Section 7. + + This notice must be preserved when any source code is + conveyed and/or propagated. + + Bacula(R) is a registered trademark of Kern Sibbald. +*/ +/* + * Bacula Catalog Database routines specific to MySQL + * These are MySQL specific routines -- hopefully all + * other files are generic. + * + * Written by Kern Sibbald, March 2000 * - * Kern Sibbald, March 2000 - * - * Version $Id$ - */ - -/* - Copyright (C) 2000-2003 Kern Sibbald and John Walker - - This program 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 2 of - the License, or (at your option) any later version. - - This program 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 this program; if not, write to the Free - Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, - MA 02111-1307, USA. - - */ - - -/* The following is necessary so that we do not include - * the dummy external definition of DB. - */ -#define __SQL_C /* indicate that this is sql.c */ - -#include "bacula.h" -#include "cats.h" - -#ifdef HAVE_MYSQL - -/* ----------------------------------------------------------------------- - * - * MySQL dependent defines and subroutines - * - * ----------------------------------------------------------------------- - */ - -/* List of open databases */ -static BQUEUE db_list = {&db_list, &db_list}; - -static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; - -/* - * Initialize database data structure. In principal this should - * never have errors, or it is really fatal. - */ -B_DB * -db_init_database(JCR *jcr, char *db_name, char *db_user, char *db_password, - char *db_address, int db_port, char *db_socket) -{ - B_DB *mdb; - - P(mutex); /* lock DB queue */ - /* Look to see if DB already open */ - for (mdb=NULL; (mdb=(B_DB *)qnext(&db_list, &mdb->bq)); ) { - if (strcmp(mdb->db_name, db_name) == 0) { - Dmsg2(100, "DB REopen %d %s\n", mdb->ref_count, db_name); - mdb->ref_count++; - V(mutex); - return mdb; /* already open */ - } - } - Dmsg0(100, "db_open first time\n"); - mdb = (B_DB *) malloc(sizeof(B_DB)); - memset(mdb, 0, sizeof(B_DB)); - mdb->db_name = bstrdup(db_name); - mdb->db_user = bstrdup(db_user); - mdb->db_password = bstrdup(db_password); - if (db_address) { - mdb->db_address = bstrdup(db_address); - } + * Note: at one point, this file was changed to class based by a certain + * programmer, and other than "wrapping" in a class, which is a trivial + * change for a C++ programmer, nothing substantial was done, yet all the + * code was recommitted under this programmer's name. Consequently, we + * undo those changes here. + * + */ + +#include "bacula.h" + +#ifdef HAVE_MYSQL + +#include "cats.h" +#include +#define __BDB_MYSQL_H_ 1 +#include "bdb_mysql.h" + +/* ----------------------------------------------------------------------- + * + * MySQL dependent defines and subroutines + * + * ----------------------------------------------------------------------- + */ + +/* List of open databases */ +static dlist *db_list = NULL; + +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +BDB_MYSQL::BDB_MYSQL() +{ + BDB_MYSQL *mdb = this; + + if (db_list == NULL) { + db_list = New(dlist(this, &this->m_link)); + } + mdb->m_db_driver_type = SQL_DRIVER_TYPE_MYSQL; + mdb->m_db_type = SQL_TYPE_MYSQL; + mdb->m_db_driver = bstrdup("MySQL"); + mdb->errmsg = get_pool_memory(PM_EMSG); /* get error message buffer */ + mdb->errmsg[0] = 0; + mdb->cmd = get_pool_memory(PM_EMSG); /* get command buffer */ + mdb->cached_path = get_pool_memory(PM_FNAME); + mdb->cached_path_id = 0; + mdb->m_ref_count = 1; + mdb->fname = get_pool_memory(PM_FNAME); + mdb->path = get_pool_memory(PM_FNAME); + mdb->esc_name = get_pool_memory(PM_FNAME); + mdb->esc_path = get_pool_memory(PM_FNAME); + mdb->esc_obj = get_pool_memory(PM_FNAME); + mdb->m_use_fatal_jmsg = true; + + /* Initialize the private members. */ + mdb->m_db_handle = NULL; + mdb->m_result = NULL; + + db_list->append(this); +} + +BDB_MYSQL::~BDB_MYSQL() +{ +} + +/* + * Initialize database data structure. In principal this should + * never have errors, or it is really fatal. + */ +BDB *db_init_database(JCR *jcr, const char *db_driver, const char *db_name, const char *db_user, + const char *db_password, const char *db_address, int db_port, const char *db_socket, + const char *db_ssl_key, const char *db_ssl_cert, const char *db_ssl_ca, + const char *db_ssl_capath, const char *db_ssl_cipher, + bool mult_db_connections, bool disable_batch_insert) +{ + BDB_MYSQL *mdb = NULL; + + if (!db_user) { + Jmsg(jcr, M_FATAL, 0, _("A user name for MySQL must be supplied.\n")); + return NULL; + } + P(mutex); /* lock DB queue */ + + /* + * Look to see if DB already open + */ + if (db_list && !mult_db_connections) { + foreach_dlist(mdb, db_list) { + if (mdb->bdb_match_database(db_driver, db_name, db_address, db_port)) { + Dmsg1(100, "DB REopen %s\n", db_name); + mdb->increment_refcount(); + goto get_out; + } + } + } + Dmsg0(100, "db_init_database first time\n"); + mdb = New(BDB_MYSQL()); + if (!mdb) goto get_out; + + /* + * Initialize the parent class members. + */ + mdb->m_db_name = bstrdup(db_name); + mdb->m_db_user = bstrdup(db_user); + if (db_password) { + mdb->m_db_password = bstrdup(db_password); + } + if (db_address) { + mdb->m_db_address = bstrdup(db_address); + } if (db_socket) { - mdb->db_socket = bstrdup(db_socket); + mdb->m_db_socket = bstrdup(db_socket); + } + if (db_ssl_key) { + mdb->m_db_ssl_key = bstrdup(db_ssl_key); } - mdb->db_port = db_port; - mdb->have_insert_id = TRUE; - mdb->errmsg = get_pool_memory(PM_EMSG); /* get error message buffer */ - *mdb->errmsg = 0; - mdb->cmd = get_pool_memory(PM_EMSG); /* get command buffer */ - mdb->cached_path = get_pool_memory(PM_FNAME); - mdb->cached_path_id = 0; - mdb->ref_count = 1; - mdb->fname = get_pool_memory(PM_FNAME); - mdb->path = get_pool_memory(PM_FNAME); - mdb->esc_name = get_pool_memory(PM_FNAME); - qinsert(&db_list, &mdb->bq); /* put db in list */ - V(mutex); - return mdb; -} - -/* - * Now actually open the database. This can generate errors, - * which are returned in the errmsg - */ -int -db_open_database(JCR *jcr, B_DB *mdb) -{ - int errstat; - - P(mutex); - if (mdb->connected) { - V(mutex); - return 1; + if (db_ssl_cert) { + mdb->m_db_ssl_cert = bstrdup(db_ssl_cert); } - mdb->connected = FALSE; - - if ((errstat=rwl_init(&mdb->lock)) != 0) { - Mmsg1(&mdb->errmsg, _("Unable to initialize DB lock. ERR=%s\n"), - strerror(errstat)); - V(mutex); - return 0; + if (db_ssl_ca) { + mdb->m_db_ssl_ca = bstrdup(db_ssl_ca); } - - /* connect to the database */ -#ifdef HAVE_EMBEDDED_MYSQL - mysql_server_init(0, NULL, NULL); -#endif - mysql_init(&(mdb->mysql)); - Dmsg0(50, "mysql_init done\n"); - mdb->db = mysql_real_connect( - &(mdb->mysql), /* db */ - mdb->db_address, /* default = localhost */ - mdb->db_user, /* login name */ - mdb->db_password, /* password */ - mdb->db_name, /* database name */ - mdb->db_port, /* default port */ - mdb->db_socket, /* default = socket */ - CLIENT_FOUND_ROWS); /* flags */ - - /* If no connect, try once more incase it is a timing problem */ - if (mdb->db == NULL) { - mdb->db = mysql_real_connect( - &(mdb->mysql), /* db */ - mdb->db_address, /* default = localhost */ - mdb->db_user, /* login name */ - mdb->db_password, /* password */ - mdb->db_name, /* database name */ - mdb->db_port, /* default port */ - mdb->db_socket, /* default = socket */ - 0); /* flags = none */ + if (db_ssl_capath) { + mdb->m_db_ssl_capath = bstrdup(db_ssl_capath); } - - Dmsg0(50, "mysql_real_connect done\n"); - Dmsg3(50, "db_user=%s db_name=%s db_password=%s\n", mdb->db_user, mdb->db_name, - mdb->db_password==NULL?"(NULL)":mdb->db_password); - - if (mdb->db == NULL) { - Mmsg2(&mdb->errmsg, _("Unable to connect to MySQL server. \n\ -Database=%s User=%s\n\ -It is probably not running or your password is incorrect.\n"), - mdb->db_name, mdb->db_user); - V(mutex); - return 0; + if (db_ssl_cipher) { + mdb->m_db_ssl_cipher = bstrdup(db_ssl_cipher); } - - if (!check_tables_version(jcr, mdb)) { - V(mutex); - db_close_database(jcr, mdb); - return 0; + mdb->m_db_port = db_port; + + if (disable_batch_insert) { + mdb->m_disabled_batch_insert = true; + mdb->m_have_batch_insert = false; + } else { + mdb->m_disabled_batch_insert = false; +#ifdef USE_BATCH_FILE_INSERT +#ifdef HAVE_MYSQL_THREAD_SAFE + mdb->m_have_batch_insert = mysql_thread_safe(); +#else + mdb->m_have_batch_insert = false; +#endif /* HAVE_MYSQL_THREAD_SAFE */ +#else + mdb->m_have_batch_insert = false; +#endif /* USE_BATCH_FILE_INSERT */ + } + + mdb->m_allow_transactions = mult_db_connections; + + /* At this time, when mult_db_connections == true, this is for + * specific console command such as bvfs or batch mode, and we don't + * want to share a batch mode or bvfs. In the future, we can change + * the creation function to add this parameter. + */ + mdb->m_dedicated = mult_db_connections; + +get_out: + V(mutex); + return mdb; +} + + +/* + * Now actually open the database. This can generate errors, + * which are returned in the errmsg + * + * DO NOT close the database or delete mdb here !!!! + */ +bool BDB_MYSQL::bdb_open_database(JCR *jcr) +{ + BDB_MYSQL *mdb = this; + bool retval = false; + int errstat; + + P(mutex); + if (mdb->m_connected) { + retval = true; + goto get_out; + } + + if ((errstat=rwl_init(&mdb->m_lock)) != 0) { + berrno be; + Mmsg1(&mdb->errmsg, _("Unable to initialize DB lock. ERR=%s\n"), + be.bstrerror(errstat)); + goto get_out; + } + + /* + * Connect to the database + */ +#ifdef xHAVE_EMBEDDED_MYSQL +// mysql_server_init(0, NULL, NULL); +#endif + mysql_init(&mdb->m_instance); + + Dmsg0(50, "mysql_init done\n"); + + /* + * Sets the appropriate certificate options for + * establishing secure connection using SSL to the database. + */ + if (mdb->m_db_ssl_key) { + mysql_ssl_set(&(mdb->m_instance), + mdb->m_db_ssl_key, + mdb->m_db_ssl_cert, + mdb->m_db_ssl_ca, + mdb->m_db_ssl_capath, + mdb->m_db_ssl_cipher); } - my_thread_init(); - - mdb->connected = TRUE; - V(mutex); - return 1; -} - -void -db_close_database(JCR *jcr, B_DB *mdb) -{ - P(mutex); - mdb->ref_count--; - my_thread_end(); - if (mdb->ref_count == 0) { - qdchain(&mdb->bq); - if (mdb->connected && mdb->db) { - sql_close(mdb); -#ifdef HAVE_EMBEDDED_MYSQL - mysql_server_end(); -#endif - } - rwl_destroy(&mdb->lock); - free_pool_memory(mdb->errmsg); - free_pool_memory(mdb->cmd); - free_pool_memory(mdb->cached_path); - free_pool_memory(mdb->fname); - free_pool_memory(mdb->path); - free_pool_memory(mdb->esc_name); - if (mdb->db_name) { - free(mdb->db_name); - } - if (mdb->db_user) { - free(mdb->db_user); - } - if (mdb->db_password) { - free(mdb->db_password); - } - if (mdb->db_address) { - free(mdb->db_address); + /* + * If connection fails, try at 5 sec intervals for 30 seconds. + */ + for (int retry=0; retry < 6; retry++) { + mdb->m_db_handle = mysql_real_connect( + &(mdb->m_instance), /* db */ + mdb->m_db_address, /* default = localhost */ + mdb->m_db_user, /* login name */ + mdb->m_db_password, /* password */ + mdb->m_db_name, /* database name */ + mdb->m_db_port, /* default port */ + mdb->m_db_socket, /* default = socket */ + CLIENT_FOUND_ROWS); /* flags */ + + /* + * If no connect, try once more in case it is a timing problem + */ + if (mdb->m_db_handle != NULL) { + break; + } + bmicrosleep(5,0); + } + + mdb->m_instance.reconnect = 1; /* so connection does not timeout */ + Dmsg0(50, "mysql_real_connect done\n"); + Dmsg3(50, "db_user=%s db_name=%s db_password=%s\n", mdb->m_db_user, mdb->m_db_name, + (mdb->m_db_password == NULL) ? "(NULL)" : mdb->m_db_password); + + if (mdb->m_db_handle == NULL) { + Mmsg2(&mdb->errmsg, _("Unable to connect to MySQL server.\n" +"Database=%s User=%s\n" +"MySQL connect failed either server not running or your authorization is incorrect.\n"), + mdb->m_db_name, mdb->m_db_user); +#if MYSQL_VERSION_ID >= 40101 + Dmsg3(50, "Error %u (%s): %s\n", + mysql_errno(&(mdb->m_instance)), mysql_sqlstate(&(mdb->m_instance)), + mysql_error(&(mdb->m_instance))); +#else + Dmsg2(50, "Error %u: %s\n", + mysql_errno(&(mdb->m_instance)), mysql_error(&(mdb->m_instance))); +#endif + goto get_out; + } + + /* get the current cipher used for SSL connection */ + if (mdb->m_db_ssl_key) { + const char *cipher; + if (mdb->m_db_ssl_cipher) { + free(mdb->m_db_ssl_cipher); } - if (mdb->db_socket) { - free(mdb->db_socket); + cipher = (const char *)mysql_get_ssl_cipher(&(mdb->m_instance)); + if (cipher) { + mdb->m_db_ssl_cipher = bstrdup(cipher); } - free(mdb); + Dmsg1(50, "db_ssl_ciper=%s\n", (mdb->m_db_ssl_cipher == NULL) ? "(NULL)" : mdb->m_db_ssl_cipher); } - V(mutex); -} -/* - * Return the next unique index (auto-increment) for - * the given table. Return NULL on error. - * - * For MySQL, NULL causes the auto-increment value - * to be updated. - */ -int db_next_index(JCR *jcr, B_DB *mdb, char *table, char *index) -{ - strcpy(index, "NULL"); - return 1; -} - - -/* - * Escape strings so that MySQL is happy - * - * NOTE! len is the length of the old string. Your new - * string must be long enough (max 2*old) to hold - * the escaped output. - */ -void -db_escape_string(char *snew, char *old, int len) -{ - mysql_escape_string(snew, old, len); - -#ifdef DO_IT_MYSELF - -/* Should use mysql_real_escape_string ! */ -unsigned long mysql_real_escape_string(MYSQL *mysql, char *to, const char *from, unsigned long length); - - char *n, *o; - - n = snew; - o = old; - while (len--) { - switch (*o) { - case 0: - *n++= '\\'; - *n++= '0'; - o++; - break; - case '\n': - *n++= '\\'; - *n++= 'n'; - o++; - break; - case '\r': - *n++= '\\'; - *n++= 'r'; - o++; - break; - case '\\': - *n++= '\\'; - *n++= '\\'; - o++; - break; - case '\'': - *n++= '\\'; - *n++= '\''; - o++; - break; - case '"': - *n++= '\\'; - *n++= '"'; - o++; - break; - case '\032': - *n++= '\\'; - *n++= 'Z'; - o++; - break; - default: - *n++= *o++; + mdb->m_connected = true; + if (!bdb_check_version(jcr)) { + goto get_out; + } + + Dmsg3(100, "opendb ref=%d connected=%d db=%p\n", mdb->m_ref_count, mdb->m_connected, mdb->m_db_handle); + + /* + * Set connection timeout to 8 days specialy for batch mode + */ + sql_query("SET wait_timeout=691200"); + sql_query("SET interactive_timeout=691200"); + + retval = true; + +get_out: + V(mutex); + return retval; +} + +void BDB_MYSQL::bdb_close_database(JCR *jcr) +{ + BDB_MYSQL *mdb = this; + + if (mdb->m_connected) { + bdb_end_transaction(jcr); + } + P(mutex); + mdb->m_ref_count--; + Dmsg3(100, "closedb ref=%d connected=%d db=%p\n", mdb->m_ref_count, mdb->m_connected, mdb->m_db_handle); + if (mdb->m_ref_count == 0) { + if (mdb->m_connected) { + sql_free_result(); + } + db_list->remove(mdb); + if (mdb->m_connected) { + Dmsg1(100, "close db=%p\n", mdb->m_db_handle); + mysql_close(&mdb->m_instance); + } + if (is_rwl_valid(&mdb->m_lock)) { + rwl_destroy(&mdb->m_lock); + } + free_pool_memory(mdb->errmsg); + free_pool_memory(mdb->cmd); + free_pool_memory(mdb->cached_path); + free_pool_memory(mdb->fname); + free_pool_memory(mdb->path); + free_pool_memory(mdb->esc_name); + free_pool_memory(mdb->esc_path); + free_pool_memory(mdb->esc_obj); + if (mdb->m_db_driver) { + free(mdb->m_db_driver); + } + if (mdb->m_db_name) { + free(mdb->m_db_name); + } + if (mdb->m_db_user) { + free(mdb->m_db_user); + } + if (mdb->m_db_password) { + free(mdb->m_db_password); + } + if (mdb->m_db_address) { + free(mdb->m_db_address); + } + if (mdb->m_db_socket) { + free(mdb->m_db_socket); } - } - *n = 0; -#endif -} - -/* - * Submit a general SQL command (cmd), and for each row returned, - * the sqlite_handler is called with the ctx. - */ -int db_sql_query(B_DB *mdb, char *query, DB_RESULT_HANDLER *result_handler, void *ctx) -{ - SQL_ROW row; - - db_lock(mdb); - if (sql_query(mdb, query) != 0) { - Mmsg(&mdb->errmsg, _("Query failed: %s: ERR=%s\n"), query, sql_strerror(mdb)); - db_unlock(mdb); - return 0; - } - if (result_handler != NULL) { - if ((mdb->result = sql_store_result(mdb)) != NULL) { - int num_fields = sql_num_fields(mdb); - - while ((row = sql_fetch_row(mdb)) != NULL) { - if (result_handler(ctx, num_fields, row)) - break; - } - - sql_free_result(mdb); + if (mdb->m_db_ssl_key) { + free(mdb->m_db_ssl_key); } - } - db_unlock(mdb); - return 1; - -} - - -static void -list_dashes(B_DB *mdb, DB_LIST_HANDLER *send, void *ctx) -{ - SQL_FIELD *field; - unsigned int i, j; - - sql_field_seek(mdb, 0); - send(ctx, "+"); - for (i = 0; i < sql_num_fields(mdb); i++) { - field = sql_fetch_field(mdb); - for (j = 0; j < field->max_length + 2; j++) - send(ctx, "-"); - send(ctx, "+"); - } - send(ctx, "\n"); -} - -/* - * If full_list is set, we list vertically, otherwise, we - * list on one line horizontally. - */ -void -list_result(B_DB *mdb, DB_LIST_HANDLER *send, void *ctx, int full_list) -{ - SQL_FIELD *field; - SQL_ROW row; - unsigned int i, col_len, max_len = 0; - char buf[2000], ewc[30]; - - if (mdb->result == NULL) { - send(ctx, _("No results to list.\n")); - return; - } - /* determine column display widths */ - sql_field_seek(mdb, 0); - for (i = 0; i < sql_num_fields(mdb); i++) { - field = sql_fetch_field(mdb); - col_len = strlen(field->name); - if (full_list) { - if (col_len > max_len) { - max_len = col_len; - } - } else { - if (IS_NUM(field->type) && field->max_length > 0) { /* fixup for commas */ - field->max_length += (field->max_length - 1) / 3; - } - if (col_len < field->max_length) { - col_len = field->max_length; - } - if (col_len < 4 && !IS_NOT_NULL(field->flags)) { - col_len = 4; /* 4 = length of the word "NULL" */ - } - field->max_length = col_len; /* reset column info */ + if (mdb->m_db_ssl_cert) { + free(mdb->m_db_ssl_cert); } - } - - if (full_list) { - goto horizontal_list; - } - - list_dashes(mdb, send, ctx); - send(ctx, "|"); - sql_field_seek(mdb, 0); - for (i = 0; i < sql_num_fields(mdb); i++) { - field = sql_fetch_field(mdb); - bsnprintf(buf, sizeof(buf), " %-*s |", (int)field->max_length, field->name); - send(ctx, buf); - } - send(ctx, "\n"); - list_dashes(mdb, send, ctx); - - while ((row = sql_fetch_row(mdb)) != NULL) { - sql_field_seek(mdb, 0); - send(ctx, "|"); - for (i = 0; i < sql_num_fields(mdb); i++) { - field = sql_fetch_field(mdb); - if (row[i] == NULL) { - bsnprintf(buf, sizeof(buf), " %-*s |", (int)field->max_length, "NULL"); - } else if (IS_NUM(field->type)) { - bsnprintf(buf, sizeof(buf), " %*s |", (int)field->max_length, - add_commas(row[i], ewc)); - } else { - bsnprintf(buf, sizeof(buf), " %-*s |", (int)field->max_length, row[i]); - } - send(ctx, buf); + if (mdb->m_db_ssl_ca) { + free(mdb->m_db_ssl_ca); } - send(ctx, "\n"); - } - list_dashes(mdb, send, ctx); - return; - -horizontal_list: - - while ((row = sql_fetch_row(mdb)) != NULL) { - sql_field_seek(mdb, 0); - for (i = 0; i < sql_num_fields(mdb); i++) { - field = sql_fetch_field(mdb); - if (row[i] == NULL) { - bsnprintf(buf, sizeof(buf), " %*s: %s\n", max_len, field->name, "NULL"); - } else if (IS_NUM(field->type)) { - bsnprintf(buf, sizeof(buf), " %*s: %s\n", max_len, field->name, - add_commas(row[i], ewc)); - } else { - bsnprintf(buf, sizeof(buf), " %*s: %s\n", max_len, field->name, row[i]); - } - send(ctx, buf); + if (mdb->m_db_ssl_capath) { + free(mdb->m_db_ssl_capath); } - send(ctx, "\n"); - } - return; -} - - -#endif /* HAVE_MYSQL */ + if (mdb->m_db_ssl_cipher) { + free(mdb->m_db_ssl_cipher); + } + delete mdb; + if (db_list->size() == 0) { + delete db_list; + db_list = NULL; + } + } + V(mutex); +} + +/* + * This call is needed because the message channel thread + * opens a database on behalf of a jcr that was created in + * a different thread. MySQL then allocates thread specific + * data, which is NOT freed when the original jcr thread + * closes the database. Thus the msgchan must call here + * to cleanup any thread specific data that it created. + */ +void BDB_MYSQL::bdb_thread_cleanup(void) +{ +#ifndef HAVE_WIN32 + mysql_thread_end(); /* Cleanup thread specific data */ +#endif +} + +/* + * Escape strings so MySQL is happy + * + * len is the length of the old string. Your new + * string must be long enough (max 2*old+1) to hold + * the escaped output. + */ +void BDB_MYSQL::bdb_escape_string(JCR *jcr, char *snew, char *old, int len) +{ + BDB_MYSQL *mdb = this; + mysql_real_escape_string(mdb->m_db_handle, snew, old, len); +} + +/* + * Escape binary object so that MySQL is happy + * Memory is stored in BDB struct, no need to free it + */ +char *BDB_MYSQL::bdb_escape_object(JCR *jcr, char *old, int len) +{ + BDB_MYSQL *mdb = this; + mdb->esc_obj = check_pool_memory_size(mdb->esc_obj, len*2+1); + mysql_real_escape_string(mdb->m_db_handle, mdb->esc_obj, old, len); + return mdb->esc_obj; +} + +/* + * Unescape binary object so that MySQL is happy + */ +void BDB_MYSQL::bdb_unescape_object(JCR *jcr, char *from, int32_t expected_len, + POOLMEM **dest, int32_t *dest_len) +{ + if (!from) { + *dest[0] = 0; + *dest_len = 0; + return; + } + *dest = check_pool_memory_size(*dest, expected_len+1); + *dest_len = expected_len; + memcpy(*dest, from, expected_len); + (*dest)[expected_len]=0; +} + +void BDB_MYSQL::bdb_start_transaction(JCR *jcr) +{ + if (!jcr->attr) { + jcr->attr = get_pool_memory(PM_FNAME); + } + if (!jcr->ar) { + jcr->ar = (ATTR_DBR *)malloc(sizeof(ATTR_DBR)); + } +} + +void BDB_MYSQL::bdb_end_transaction(JCR *jcr) +{ + if (jcr && jcr->cached_attribute) { + Dmsg0(400, "Flush last cached attribute.\n"); + if (!bdb_create_attributes_record(jcr, jcr->ar)) { + Jmsg1(jcr, M_FATAL, 0, _("Attribute create error. %s"), jcr->db->bdb_strerror()); + } + jcr->cached_attribute = false; + } +} + +/* + * Submit a general SQL command (cmd), and for each row returned, + * the result_handler is called with the ctx. + */ +bool BDB_MYSQL::bdb_sql_query(const char *query, DB_RESULT_HANDLER *result_handler, void *ctx) +{ + int ret; + SQL_ROW row; + bool send = true; + bool retval = false; + BDB_MYSQL *mdb = this; + + Dmsg1(500, "db_sql_query starts with %s\n", query); + + bdb_lock(); + errmsg[0] = 0; + ret = mysql_query(m_db_handle, query); + if (ret != 0) { + Mmsg(mdb->errmsg, _("Query failed: %s: ERR=%s\n"), query, sql_strerror()); + Dmsg0(500, "db_sql_query failed\n"); + goto get_out; + } + + Dmsg0(500, "db_sql_query succeeded. checking handler\n"); + + if (result_handler) { + if ((mdb->m_result = mysql_use_result(mdb->m_db_handle)) != NULL) { + mdb->m_num_fields = mysql_num_fields(mdb->m_result); + + /* + * We *must* fetch all rows + */ + while ((row = mysql_fetch_row(m_result))) { + if (send) { + /* the result handler returns 1 when it has + * seen all the data it wants. However, we + * loop to the end of the data. + */ + if (result_handler(ctx, mdb->m_num_fields, row)) { + send = false; + } + } + } + sql_free_result(); + } + } + + Dmsg0(500, "db_sql_query finished\n"); + retval = true; + +get_out: + bdb_unlock(); + return retval; +} + +bool BDB_MYSQL::sql_query(const char *query, int flags) +{ + int ret; + bool retval = true; + BDB_MYSQL *mdb = this; + + Dmsg1(500, "sql_query starts with '%s'\n", query); + /* + * We are starting a new query. reset everything. + */ + mdb->m_num_rows = -1; + mdb->m_row_number = -1; + mdb->m_field_number = -1; + + if (mdb->m_result) { + mysql_free_result(mdb->m_result); + mdb->m_result = NULL; + } + + ret = mysql_query(mdb->m_db_handle, query); + if (ret == 0) { + Dmsg0(500, "we have a result\n"); + if (flags & QF_STORE_RESULT) { + mdb->m_result = mysql_store_result(mdb->m_db_handle); + if (mdb->m_result != NULL) { + mdb->m_num_fields = mysql_num_fields(mdb->m_result); + Dmsg1(500, "we have %d fields\n", mdb->m_num_fields); + mdb->m_num_rows = mysql_num_rows(mdb->m_result); + Dmsg1(500, "we have %d rows\n", mdb->m_num_rows); + } else { + mdb->m_num_fields = 0; + mdb->m_num_rows = mysql_affected_rows(mdb->m_db_handle); + Dmsg1(500, "we have %d rows\n", mdb->m_num_rows); + } + } else { + mdb->m_num_fields = 0; + mdb->m_num_rows = mysql_affected_rows(mdb->m_db_handle); + Dmsg1(500, "we have %d rows\n", mdb->m_num_rows); + } + } else { + Dmsg0(500, "we failed\n"); + mdb->m_status = 1; /* failed */ + retval = false; + } + return retval; +} + +void BDB_MYSQL::sql_free_result(void) +{ + BDB_MYSQL *mdb = this; + bdb_lock(); + if (mdb->m_result) { + mysql_free_result(mdb->m_result); + mdb->m_result = NULL; + } + if (mdb->m_fields) { + free(mdb->m_fields); + mdb->m_fields = NULL; + } + mdb->m_num_rows = mdb->m_num_fields = 0; + bdb_unlock(); +} + +SQL_ROW BDB_MYSQL::sql_fetch_row(void) +{ + BDB_MYSQL *mdb = this; + if (!mdb->m_result) { + return NULL; + } else { + return mysql_fetch_row(mdb->m_result); + } +} + +const char *BDB_MYSQL::sql_strerror(void) +{ + BDB_MYSQL *mdb = this; + return mysql_error(mdb->m_db_handle); +} + +void BDB_MYSQL::sql_data_seek(int row) +{ + BDB_MYSQL *mdb = this; + return mysql_data_seek(mdb->m_result, row); +} + +int BDB_MYSQL::sql_affected_rows(void) +{ + BDB_MYSQL *mdb = this; + return mysql_affected_rows(mdb->m_db_handle); +} + +uint64_t BDB_MYSQL::sql_insert_autokey_record(const char *query, const char *table_name) +{ + BDB_MYSQL *mdb = this; + /* + * First execute the insert query and then retrieve the currval. + */ + if (mysql_query(mdb->m_db_handle, query) != 0) { + return 0; + } + + mdb->m_num_rows = mysql_affected_rows(mdb->m_db_handle); + if (mdb->m_num_rows != 1) { + return 0; + } + + mdb->changes++; + + return mysql_insert_id(mdb->m_db_handle); +} + +SQL_FIELD *BDB_MYSQL::sql_fetch_field(void) +{ + int i; + MYSQL_FIELD *field; + BDB_MYSQL *mdb = this; + + if (!mdb->m_fields || mdb->m_fields_size < mdb->m_num_fields) { + if (mdb->m_fields) { + free(mdb->m_fields); + mdb->m_fields = NULL; + } + Dmsg1(500, "allocating space for %d fields\n", mdb->m_num_fields); + mdb->m_fields = (SQL_FIELD *)malloc(sizeof(SQL_FIELD) * mdb->m_num_fields); + mdb->m_fields_size = mdb->m_num_fields; + + for (i = 0; i < mdb->m_num_fields; i++) { + Dmsg1(500, "filling field %d\n", i); + if ((field = mysql_fetch_field(mdb->m_result)) != NULL) { + mdb->m_fields[i].name = field->name; + mdb->m_fields[i].max_length = field->max_length; + mdb->m_fields[i].type = field->type; + mdb->m_fields[i].flags = field->flags; + + Dmsg4(500, "sql_fetch_field finds field '%s' has length='%d' type='%d' and IsNull=%d\n", + mdb->m_fields[i].name, mdb->m_fields[i].max_length, mdb->m_fields[i].type, mdb->m_fields[i].flags); + } + } + } + + /* + * Increment field number for the next time around + */ + return &mdb->m_fields[mdb->m_field_number++]; +} + +bool BDB_MYSQL::sql_field_is_not_null(int field_type) +{ + return IS_NOT_NULL(field_type); +} + +bool BDB_MYSQL::sql_field_is_numeric(int field_type) +{ + return IS_NUM(field_type); +} + +/* + * Returns true if OK + * false if failed + */ +bool BDB_MYSQL::sql_batch_start(JCR *jcr) +{ + BDB_MYSQL *mdb = this; + bool retval; + + bdb_lock(); + retval = sql_query("CREATE TEMPORARY TABLE batch (" + "FileIndex integer," + "JobId integer," + "Path blob," + "Name blob," + "LStat tinyblob," + "MD5 tinyblob," + "DeltaSeq integer)"); + bdb_unlock(); + + /* + * Keep track of the number of changes in batch mode. + */ + mdb->changes = 0; + + return retval; +} + +/* set error to something to abort operation */ +/* + * Returns true if OK + * false if failed + */ +bool BDB_MYSQL::sql_batch_end(JCR *jcr, const char *error) +{ + BDB_MYSQL *mdb = this; + + mdb->m_status = 0; + + /* + * Flush any pending inserts. + */ + if (mdb->changes) { + return sql_query(mdb->cmd); + } + + return true; +} + +/* + * Returns true if OK + * false if failed + */ +bool BDB_MYSQL::sql_batch_insert(JCR *jcr, ATTR_DBR *ar) +{ + BDB_MYSQL *mdb = this; + const char *digest; + char ed1[50]; + + mdb->esc_name = check_pool_memory_size(mdb->esc_name, mdb->fnl*2+1); + bdb_escape_string(jcr, mdb->esc_name, mdb->fname, mdb->fnl); + + mdb->esc_path = check_pool_memory_size(mdb->esc_path, mdb->pnl*2+1); + bdb_escape_string(jcr, mdb->esc_path, mdb->path, mdb->pnl); + + if (ar->Digest == NULL || ar->Digest[0] == 0) { + digest = "0"; + } else { + digest = ar->Digest; + } + + /* + * Try to batch up multiple inserts using multi-row inserts. + */ + if (mdb->changes == 0) { + Mmsg(cmd, "INSERT INTO batch VALUES " + "(%u,%s,'%s','%s','%s','%s',%u)", + ar->FileIndex, edit_int64(ar->JobId,ed1), mdb->esc_path, + mdb->esc_name, ar->attr, digest, ar->DeltaSeq); + mdb->changes++; + } else { + /* + * We use the esc_obj for temporary storage otherwise + * we keep on copying data. + */ + Mmsg(mdb->esc_obj, ",(%u,%s,'%s','%s','%s','%s',%u)", + ar->FileIndex, edit_int64(ar->JobId,ed1), mdb->esc_path, + mdb->esc_name, ar->attr, digest, ar->DeltaSeq); + pm_strcat(mdb->cmd, mdb->esc_obj); + mdb->changes++; + } + + /* + * See if we need to flush the query buffer filled + * with multi-row inserts. + */ + if ((mdb->changes % MYSQL_CHANGES_PER_BATCH_INSERT) == 0) { + if (!sql_query(mdb->cmd)) { + mdb->changes = 0; + return false; + } else { + mdb->changes = 0; + } + } + return true; +} + + +#endif /* HAVE_MYSQL */