]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/cats/mysql.c
Split messages line by line before sending it to syslog() fix #3325
[bacula/bacula] / bacula / src / cats / mysql.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2000-2011 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version three of the GNU Affero General Public
10    License as published by the Free Software Foundation and included
11    in the file LICENSE.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU Affero General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22
23    Bacula® is a registered trademark of Kern Sibbald.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28 /*
29  * Bacula Catalog Database routines specific to MySQL
30  *   These are MySQL specific routines -- hopefully all
31  *    other files are generic.
32  *
33  *    Kern Sibbald, March 2000
34  *
35  * Major rewrite by Marco van Wieringen, January 2010 for catalog refactoring.
36  */
37
38 #include "bacula.h"
39
40 #ifdef HAVE_MYSQL
41
42 #include "cats.h"
43 #include "bdb_priv.h"
44 #include <mysql.h>
45 #include <bdb_mysql.h>
46
47 /* -----------------------------------------------------------------------
48  *
49  *   MySQL dependent defines and subroutines
50  *
51  * -----------------------------------------------------------------------
52  */
53
54 /*
55  * List of open databases
56  */
57 static dlist *db_list = NULL;
58
59 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
60
61 B_DB_MYSQL::B_DB_MYSQL(JCR *jcr,
62                        const char *db_driver,
63                        const char *db_name,
64                        const char *db_user,
65                        const char *db_password,
66                        const char *db_address,
67                        int db_port,
68                        const char *db_socket,
69                        bool mult_db_connections,
70                        bool disable_batch_insert)
71 {
72    /*
73     * Initialize the parent class members.
74     */
75    m_db_interface_type = SQL_INTERFACE_TYPE_MYSQL;
76    m_db_type = SQL_TYPE_MYSQL;
77    m_db_driver = bstrdup("MySQL");
78    m_db_name = bstrdup(db_name);
79    m_db_user = bstrdup(db_user);
80    if (db_password) {
81       m_db_password = bstrdup(db_password);
82    }
83    if (db_address) {
84       m_db_address = bstrdup(db_address);
85    }
86    if (db_socket) {
87       m_db_socket = bstrdup(db_socket);
88    }
89    m_db_port = db_port;
90
91    if (disable_batch_insert) {
92       m_disabled_batch_insert = true;
93       m_have_batch_insert = false;
94    } else {
95       m_disabled_batch_insert = false;
96 #if defined(USE_BATCH_FILE_INSERT)
97 # if defined(HAVE_MYSQL_THREAD_SAFE)
98       m_have_batch_insert = mysql_thread_safe();
99 # else
100       m_have_batch_insert = false;
101 # endif /* HAVE_MYSQL_THREAD_SAFE */
102 #else
103       m_have_batch_insert = false;
104 #endif /* USE_BATCH_FILE_INSERT */
105    }
106    errmsg = get_pool_memory(PM_EMSG); /* get error message buffer */
107    *errmsg = 0;
108    cmd = get_pool_memory(PM_EMSG);    /* get command buffer */
109    cached_path = get_pool_memory(PM_FNAME);
110    cached_path_id = 0;
111    m_ref_count = 1;
112    fname = get_pool_memory(PM_FNAME);
113    path = get_pool_memory(PM_FNAME);
114    esc_name = get_pool_memory(PM_FNAME);
115    esc_path = get_pool_memory(PM_FNAME);
116    esc_obj = get_pool_memory(PM_FNAME);
117    m_allow_transactions = mult_db_connections;
118
119    /*
120     * Initialize the private members.
121     */
122    m_db_handle = NULL;
123    m_result = NULL;
124
125    /*
126     * Put the db in the list.
127     */
128    if (db_list == NULL) {
129       db_list = New(dlist(this, &this->m_link));
130    }
131    db_list->append(this);
132 }
133
134 B_DB_MYSQL::~B_DB_MYSQL()
135 {
136 }
137
138 /*
139  * Now actually open the database.  This can generate errors,
140  *  which are returned in the errmsg
141  *
142  * DO NOT close the database or delete mdb here !!!!
143  */
144 bool B_DB_MYSQL::db_open_database(JCR *jcr)
145 {
146    bool retval = false;
147    int errstat;
148
149    P(mutex);
150    if (m_connected) {
151       retval = true;
152       goto bail_out;
153    }
154
155    if ((errstat=rwl_init(&m_lock)) != 0) {
156       berrno be;
157       Mmsg1(&errmsg, _("Unable to initialize DB lock. ERR=%s\n"),
158             be.bstrerror(errstat));
159       goto bail_out;
160    }
161
162    /*
163     * Connect to the database
164     */
165 #ifdef xHAVE_EMBEDDED_MYSQL
166 // mysql_server_init(0, NULL, NULL);
167 #endif
168    mysql_init(&m_instance);
169
170    Dmsg0(50, "mysql_init done\n");
171    /*
172     * If connection fails, try at 5 sec intervals for 30 seconds.
173     */
174    for (int retry=0; retry < 6; retry++) {
175       m_db_handle = mysql_real_connect(
176            &(m_instance),           /* db */
177            m_db_address,            /* default = localhost */
178            m_db_user,               /* login name */
179            m_db_password,           /* password */
180            m_db_name,               /* database name */
181            m_db_port,               /* default port */
182            m_db_socket,             /* default = socket */
183            CLIENT_FOUND_ROWS);      /* flags */
184
185       /*
186        * If no connect, try once more in case it is a timing problem
187        */
188       if (m_db_handle != NULL) {
189          break;
190       }
191       bmicrosleep(5,0);
192    }
193
194    m_instance.reconnect = 1;             /* so connection does not timeout */
195    Dmsg0(50, "mysql_real_connect done\n");
196    Dmsg3(50, "db_user=%s db_name=%s db_password=%s\n", m_db_user, m_db_name,
197         (m_db_password == NULL) ? "(NULL)" : m_db_password);
198
199    if (m_db_handle == NULL) {
200       Mmsg2(&errmsg, _("Unable to connect to MySQL server.\n"
201 "Database=%s User=%s\n"
202 "MySQL connect failed either server not running or your authorization is incorrect.\n"),
203          m_db_name, m_db_user);
204 #if MYSQL_VERSION_ID >= 40101
205       Dmsg3(50, "Error %u (%s): %s\n",
206             mysql_errno(&(m_instance)), mysql_sqlstate(&(m_instance)),
207             mysql_error(&(m_instance)));
208 #else
209       Dmsg2(50, "Error %u: %s\n",
210             mysql_errno(&(m_instance)), mysql_error(&(m_instance)));
211 #endif
212       goto bail_out;
213    }
214
215    m_connected = true;
216    if (!check_tables_version(jcr, this)) {
217       goto bail_out;
218    }
219
220    Dmsg3(100, "opendb ref=%d connected=%d db=%p\n", m_ref_count, m_connected, m_db_handle);
221
222    /*
223     * Set connection timeout to 8 days specialy for batch mode
224     */
225    sql_query("SET wait_timeout=691200");
226    sql_query("SET interactive_timeout=691200");
227
228    retval = true;
229
230 bail_out:
231    V(mutex);
232    return retval;
233 }
234
235 void B_DB_MYSQL::db_close_database(JCR *jcr)
236 {
237    db_end_transaction(jcr);
238    P(mutex);
239    m_ref_count--;
240    Dmsg3(100, "closedb ref=%d connected=%d db=%p\n", m_ref_count, m_connected, m_db_handle);
241    if (m_ref_count == 0) {
242       sql_free_result();
243       db_list->remove(this);
244       if (m_connected) {
245          Dmsg1(100, "close db=%p\n", m_db_handle);
246          mysql_close(&m_instance);
247
248 #ifdef xHAVE_EMBEDDED_MYSQL
249 //       mysql_server_end();
250 #endif
251       }
252       rwl_destroy(&m_lock);
253       free_pool_memory(errmsg);
254       free_pool_memory(cmd);
255       free_pool_memory(cached_path);
256       free_pool_memory(fname);
257       free_pool_memory(path);
258       free_pool_memory(esc_name);
259       free_pool_memory(esc_path);
260       free_pool_memory(esc_obj);
261       if (m_db_driver) {
262          free(m_db_driver);
263       }
264       if (m_db_name) {
265          free(m_db_name);
266       }
267       if (m_db_user) {
268          free(m_db_user);
269       }
270       if (m_db_password) {
271          free(m_db_password);
272       }
273       if (m_db_address) {
274          free(m_db_address);
275       }
276       if (m_db_socket) {
277          free(m_db_socket);
278       }
279       delete this;
280       if (db_list->size() == 0) {
281          delete db_list;
282          db_list = NULL;
283       }
284    }
285    V(mutex);
286 }
287
288 /*
289  * This call is needed because the message channel thread
290  *  opens a database on behalf of a jcr that was created in
291  *  a different thread. MySQL then allocates thread specific
292  *  data, which is NOT freed when the original jcr thread
293  *  closes the database.  Thus the msgchan must call here
294  *  to cleanup any thread specific data that it created.
295  */
296 void B_DB_MYSQL::db_thread_cleanup(void)
297
298 #ifndef HAVE_WIN32
299    my_thread_end();
300 #endif
301 }
302
303 /*
304  * Escape strings so that MySQL is happy
305  *
306  *   NOTE! len is the length of the old string. Your new
307  *         string must be long enough (max 2*old+1) to hold
308  *         the escaped output.
309  */
310 void B_DB_MYSQL::db_escape_string(JCR *jcr, char *snew, char *old, int len)
311 {
312    mysql_real_escape_string(m_db_handle, snew, old, len);
313 }
314
315 /*
316  * Escape binary object so that MySQL is happy
317  * Memory is stored in B_DB struct, no need to free it
318  */
319 char *B_DB_MYSQL::db_escape_object(JCR *jcr, char *old, int len)
320 {
321    esc_obj = check_pool_memory_size(esc_obj, len*2+1);
322    mysql_real_escape_string(m_db_handle, esc_obj, old, len);
323    return esc_obj;
324 }
325
326 /*
327  * Unescape binary object so that MySQL is happy
328  */
329 void B_DB_MYSQL::db_unescape_object(JCR *jcr, char *from, int32_t expected_len,
330                                     POOLMEM **dest, int32_t *dest_len)
331 {
332    if (!from) {
333       *dest[0] = 0;
334       *dest_len = 0;
335       return;
336    }
337    *dest = check_pool_memory_size(*dest, expected_len+1);
338    *dest_len = expected_len;
339    memcpy(*dest, from, expected_len);
340    (*dest)[expected_len]=0;
341 }
342
343 void B_DB_MYSQL::db_start_transaction(JCR *jcr)
344 {
345    if (!jcr->attr) {
346       jcr->attr = get_pool_memory(PM_FNAME);
347    }
348    if (!jcr->ar) {
349       jcr->ar = (ATTR_DBR *)malloc(sizeof(ATTR_DBR));
350    }
351 }
352
353 void B_DB_MYSQL::db_end_transaction(JCR *jcr)
354 {
355    if (jcr && jcr->cached_attribute) {
356       Dmsg0(400, "Flush last cached attribute.\n");
357       if (!db_create_attributes_record(jcr, this, jcr->ar)) {
358          Jmsg1(jcr, M_FATAL, 0, _("Attribute create error. %s"), db_strerror(jcr->db));
359       }
360       jcr->cached_attribute = false;
361    }
362 }
363
364 /*
365  * Submit a general SQL command (cmd), and for each row returned,
366  * the result_handler is called with the ctx.
367  */
368 bool B_DB_MYSQL::db_sql_query(const char *query, DB_RESULT_HANDLER *result_handler, void *ctx)
369 {
370    int ret;
371    SQL_ROW row;
372    bool send = true;
373    bool retval = false;
374
375    Dmsg1(500, "db_sql_query starts with %s\n", query);
376
377    db_lock(this);
378    ret = mysql_query(m_db_handle, query);
379    if (ret != 0) {
380       Mmsg(errmsg, _("Query failed: %s: ERR=%s\n"), query, sql_strerror());
381       Dmsg0(500, "db_sql_query failed\n");
382       goto bail_out;
383    }
384
385    Dmsg0(500, "db_sql_query succeeded. checking handler\n");
386
387    if (result_handler != NULL) {
388       if ((m_result = mysql_use_result(m_db_handle)) != NULL) {
389          m_num_fields = mysql_num_fields(m_result);
390
391          /*
392           * We *must* fetch all rows
393           */
394          while ((row = mysql_fetch_row(m_result)) != NULL) {
395             if (send) {
396                /* the result handler returns 1 when it has
397                 *  seen all the data it wants.  However, we
398                 *  loop to the end of the data.
399                 */
400                if (result_handler(ctx, m_num_fields, row)) {
401                   send = false;
402                }
403             }
404          }
405          sql_free_result();
406       }
407    }
408
409    Dmsg0(500, "db_sql_query finished\n");
410    retval = true;
411
412 bail_out:
413    db_unlock(this);
414    return retval;
415 }
416
417 bool B_DB_MYSQL::sql_query(const char *query, int flags)
418 {
419    int ret;
420    bool retval = true;
421
422    Dmsg1(500, "sql_query starts with '%s'\n", query);
423    /*
424     * We are starting a new query. reset everything.
425     */
426    m_num_rows     = -1;
427    m_row_number   = -1;
428    m_field_number = -1;
429
430    if (m_result) {
431       mysql_free_result(m_result);
432       m_result = NULL;
433    }
434
435    ret = mysql_query(m_db_handle, query);
436    if (ret == 0) {
437       Dmsg0(500, "we have a result\n");
438       if (flags & QF_STORE_RESULT) {
439          m_result = mysql_store_result(m_db_handle);
440          if (m_result != NULL) {
441             m_num_fields = mysql_num_fields(m_result);
442             Dmsg1(500, "we have %d fields\n", m_num_fields);
443             m_num_rows = mysql_num_rows(m_result);
444             Dmsg1(500, "we have %d rows\n", m_num_rows);
445          } else {
446             m_num_fields = 0;
447             m_num_rows = mysql_affected_rows(m_db_handle);
448             Dmsg1(500, "we have %d rows\n", m_num_rows);
449          }
450       } else {
451          m_num_fields = 0;
452          m_num_rows = mysql_affected_rows(m_db_handle);
453          Dmsg1(500, "we have %d rows\n", m_num_rows);
454       }
455    } else {
456       Dmsg0(500, "we failed\n");
457       m_status = 1;                   /* failed */
458       retval = false;
459    }
460    return retval;
461 }
462
463 void B_DB_MYSQL::sql_free_result(void)
464 {
465    db_lock(this);
466    if (m_result) {
467       mysql_free_result(m_result);
468       m_result = NULL;
469    }
470    if (m_fields) {
471       free(m_fields);
472       m_fields = NULL;
473    }
474    m_num_rows = m_num_fields = 0;
475    db_unlock(this);
476 }
477
478 SQL_ROW B_DB_MYSQL::sql_fetch_row(void)
479 {
480    if (!m_result) {
481       return NULL;
482    } else {
483       return mysql_fetch_row(m_result);
484    }
485 }
486
487 const char *B_DB_MYSQL::sql_strerror(void)
488 {
489    return mysql_error(m_db_handle);
490 }
491
492 void B_DB_MYSQL::sql_data_seek(int row)
493 {
494    return mysql_data_seek(m_result, row);
495 }
496
497 int B_DB_MYSQL::sql_affected_rows(void)
498 {
499    return mysql_affected_rows(m_db_handle);
500 }
501
502 uint64_t B_DB_MYSQL::sql_insert_autokey_record(const char *query, const char *table_name)
503 {
504    /*
505     * First execute the insert query and then retrieve the currval.
506     */
507    if (mysql_query(m_db_handle, query) != 0) {
508       return 0;
509    }
510
511    m_num_rows = mysql_affected_rows(m_db_handle);
512    if (m_num_rows != 1) {
513       return 0;
514    }
515
516    changes++;
517
518    return mysql_insert_id(m_db_handle);
519 }
520
521 SQL_FIELD *B_DB_MYSQL::sql_fetch_field(void)
522 {
523    int i;
524    MYSQL_FIELD *field;
525
526    if (!m_fields || m_fields_size < m_num_fields) {
527       if (m_fields) {
528          free(m_fields);
529          m_fields = NULL;
530       }
531       Dmsg1(500, "allocating space for %d fields\n", m_num_fields);
532       m_fields = (SQL_FIELD *)malloc(sizeof(SQL_FIELD) * m_num_fields);
533       m_fields_size = m_num_fields;
534
535       for (i = 0; i < m_num_fields; i++) {
536          Dmsg1(500, "filling field %d\n", i);
537          if ((field = mysql_fetch_field(m_result)) != NULL) {
538             m_fields[i].name = field->name;
539             m_fields[i].max_length = field->max_length;
540             m_fields[i].type = field->type;
541             m_fields[i].flags = field->flags;
542
543             Dmsg4(500, "sql_fetch_field finds field '%s' has length='%d' type='%d' and IsNull=%d\n",
544                   m_fields[i].name, m_fields[i].max_length, m_fields[i].type, m_fields[i].flags);
545          }
546       }
547    }
548
549    /*
550     * Increment field number for the next time around
551     */
552    return &m_fields[m_field_number++];
553 }
554
555 bool B_DB_MYSQL::sql_field_is_not_null(int field_type)
556 {
557    return IS_NOT_NULL(field_type);
558 }
559
560 bool B_DB_MYSQL::sql_field_is_numeric(int field_type)
561 {
562    return IS_NUM(field_type);
563 }
564
565 /* 
566  * Returns true if OK
567  *         false if failed
568  */
569 bool B_DB_MYSQL::sql_batch_start(JCR *jcr)
570 {
571    bool retval;
572
573    db_lock(this);
574    retval = sql_query("CREATE TEMPORARY TABLE batch ("
575                               "FileIndex integer,"
576                               "JobId integer,"
577                               "Path blob,"
578                               "Name blob,"
579                               "LStat tinyblob,"
580                               "MD5 tinyblob,"
581                               "DeltaSeq integer)");
582    db_unlock(this);
583
584    return retval;
585 }
586
587 /* set error to something to abort operation */
588 /* 
589  * Returns true if OK
590  *         false if failed
591  */
592 bool B_DB_MYSQL::sql_batch_end(JCR *jcr, const char *error)
593 {
594    m_status = 0;
595
596    return true;
597 }
598
599 /* 
600  * Returns true if OK
601  *         false if failed
602  */
603 bool B_DB_MYSQL::sql_batch_insert(JCR *jcr, ATTR_DBR *ar)
604 {
605    size_t len;
606    const char *digest;
607    char ed1[50];
608
609    esc_name = check_pool_memory_size(esc_name, fnl*2+1);
610    db_escape_string(jcr, esc_name, fname, fnl);
611
612    esc_path = check_pool_memory_size(esc_path, pnl*2+1);
613    db_escape_string(jcr, esc_path, path, pnl);
614
615    if (ar->Digest == NULL || ar->Digest[0] == 0) {
616       digest = "0";
617    } else {
618       digest = ar->Digest;
619    }
620
621    len = Mmsg(cmd, "INSERT INTO batch VALUES "
622                    "(%u,%s,'%s','%s','%s','%s',%u)",
623                    ar->FileIndex, edit_int64(ar->JobId,ed1), esc_path,
624                    esc_name, ar->attr, digest, ar->DeltaSeq);
625
626    return sql_query(cmd);
627 }
628
629 /*
630  * Initialize database data structure. In principal this should
631  * never have errors, or it is really fatal.
632  */
633 B_DB *db_init_database(JCR *jcr, const char *db_driver, const char *db_name, const char *db_user,
634                        const char *db_password, const char *db_address, int db_port, const char *db_socket,
635                        bool mult_db_connections, bool disable_batch_insert)
636 {
637    B_DB_MYSQL *mdb = NULL;
638
639    if (!db_user) {
640       Jmsg(jcr, M_FATAL, 0, _("A user name for MySQL must be supplied.\n"));
641       return NULL;
642    }
643    P(mutex);                          /* lock DB queue */
644
645    /*
646     * Look to see if DB already open
647     */
648    if (db_list && !mult_db_connections) {
649       foreach_dlist(mdb, db_list) {
650          if (mdb->db_match_database(db_driver, db_name, db_address, db_port)) {
651             Dmsg1(100, "DB REopen %s\n", db_name);
652             mdb->increment_refcount();
653             goto bail_out;
654          }
655       }
656    }
657    Dmsg0(100, "db_init_database first time\n");
658    mdb = New(B_DB_MYSQL(jcr, db_driver, db_name, db_user, db_password, db_address,
659                         db_port, db_socket, mult_db_connections, disable_batch_insert));
660
661 bail_out:
662    V(mutex);
663    return mdb;
664 }
665
666 #endif /* HAVE_MYSQL */