]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/cats/mysql.c
Fix bug #1893
[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    /* At this time, when mult_db_connections == true, this is for 
120     * specific console command such as bvfs or batch mode, and we don't
121     * want to share a batch mode or bvfs. In the future, we can change
122     * the creation function to add this parameter.
123     */
124    m_dedicated = mult_db_connections; 
125
126    /*
127     * Initialize the private members.
128     */
129    m_db_handle = NULL;
130    m_result = NULL;
131
132    /*
133     * Put the db in the list.
134     */
135    if (db_list == NULL) {
136       db_list = New(dlist(this, &this->m_link));
137    }
138    db_list->append(this);
139 }
140
141 B_DB_MYSQL::~B_DB_MYSQL()
142 {
143 }
144
145 /*
146  * Now actually open the database.  This can generate errors,
147  *  which are returned in the errmsg
148  *
149  * DO NOT close the database or delete mdb here !!!!
150  */
151 bool B_DB_MYSQL::db_open_database(JCR *jcr)
152 {
153    bool retval = false;
154    int errstat;
155
156    P(mutex);
157    if (m_connected) {
158       retval = true;
159       goto bail_out;
160    }
161
162    if ((errstat=rwl_init(&m_lock)) != 0) {
163       berrno be;
164       Mmsg1(&errmsg, _("Unable to initialize DB lock. ERR=%s\n"),
165             be.bstrerror(errstat));
166       goto bail_out;
167    }
168
169    /*
170     * Connect to the database
171     */
172 #ifdef xHAVE_EMBEDDED_MYSQL
173 // mysql_server_init(0, NULL, NULL);
174 #endif
175    mysql_init(&m_instance);
176
177    Dmsg0(50, "mysql_init done\n");
178    /*
179     * If connection fails, try at 5 sec intervals for 30 seconds.
180     */
181    for (int retry=0; retry < 6; retry++) {
182       m_db_handle = mysql_real_connect(
183            &(m_instance),           /* db */
184            m_db_address,            /* default = localhost */
185            m_db_user,               /* login name */
186            m_db_password,           /* password */
187            m_db_name,               /* database name */
188            m_db_port,               /* default port */
189            m_db_socket,             /* default = socket */
190            CLIENT_FOUND_ROWS);      /* flags */
191
192       /*
193        * If no connect, try once more in case it is a timing problem
194        */
195       if (m_db_handle != NULL) {
196          break;
197       }
198       bmicrosleep(5,0);
199    }
200
201    m_instance.reconnect = 1;             /* so connection does not timeout */
202    Dmsg0(50, "mysql_real_connect done\n");
203    Dmsg3(50, "db_user=%s db_name=%s db_password=%s\n", m_db_user, m_db_name,
204         (m_db_password == NULL) ? "(NULL)" : m_db_password);
205
206    if (m_db_handle == NULL) {
207       Mmsg2(&errmsg, _("Unable to connect to MySQL server.\n"
208 "Database=%s User=%s\n"
209 "MySQL connect failed either server not running or your authorization is incorrect.\n"),
210          m_db_name, m_db_user);
211 #if MYSQL_VERSION_ID >= 40101
212       Dmsg3(50, "Error %u (%s): %s\n",
213             mysql_errno(&(m_instance)), mysql_sqlstate(&(m_instance)),
214             mysql_error(&(m_instance)));
215 #else
216       Dmsg2(50, "Error %u: %s\n",
217             mysql_errno(&(m_instance)), mysql_error(&(m_instance)));
218 #endif
219       goto bail_out;
220    }
221
222    m_connected = true;
223    if (!check_tables_version(jcr, this)) {
224       goto bail_out;
225    }
226
227    Dmsg3(100, "opendb ref=%d connected=%d db=%p\n", m_ref_count, m_connected, m_db_handle);
228
229    /*
230     * Set connection timeout to 8 days specialy for batch mode
231     */
232    sql_query("SET wait_timeout=691200");
233    sql_query("SET interactive_timeout=691200");
234
235    retval = true;
236
237 bail_out:
238    V(mutex);
239    return retval;
240 }
241
242 void B_DB_MYSQL::db_close_database(JCR *jcr)
243 {
244    if (m_connected) {
245       db_end_transaction(jcr);
246    }
247    P(mutex);
248    m_ref_count--;
249    Dmsg3(100, "closedb ref=%d connected=%d db=%p\n", m_ref_count, m_connected, m_db_handle);
250    if (m_ref_count == 0) {
251       sql_free_result();
252       db_list->remove(this);
253       if (m_connected) {
254          Dmsg1(100, "close db=%p\n", m_db_handle);
255          mysql_close(&m_instance);
256
257 #ifdef xHAVE_EMBEDDED_MYSQL
258 //       mysql_server_end();
259 #endif
260       }
261       if (rwl_is_init(&m_lock)) {
262          rwl_destroy(&m_lock);
263       }
264       free_pool_memory(errmsg);
265       free_pool_memory(cmd);
266       free_pool_memory(cached_path);
267       free_pool_memory(fname);
268       free_pool_memory(path);
269       free_pool_memory(esc_name);
270       free_pool_memory(esc_path);
271       free_pool_memory(esc_obj);
272       if (m_db_driver) {
273          free(m_db_driver);
274       }
275       if (m_db_name) {
276          free(m_db_name);
277       }
278       if (m_db_user) {
279          free(m_db_user);
280       }
281       if (m_db_password) {
282          free(m_db_password);
283       }
284       if (m_db_address) {
285          free(m_db_address);
286       }
287       if (m_db_socket) {
288          free(m_db_socket);
289       }
290       delete this;
291       if (db_list->size() == 0) {
292          delete db_list;
293          db_list = NULL;
294       }
295    }
296    V(mutex);
297 }
298
299 /*
300  * This call is needed because the message channel thread
301  *  opens a database on behalf of a jcr that was created in
302  *  a different thread. MySQL then allocates thread specific
303  *  data, which is NOT freed when the original jcr thread
304  *  closes the database.  Thus the msgchan must call here
305  *  to cleanup any thread specific data that it created.
306  */
307 void B_DB_MYSQL::db_thread_cleanup(void)
308
309 #ifndef HAVE_WIN32
310    mysql_thread_end();
311 #endif
312 }
313
314 /*
315  * Escape strings so that MySQL is happy
316  *
317  *   NOTE! len is the length of the old string. Your new
318  *         string must be long enough (max 2*old+1) to hold
319  *         the escaped output.
320  */
321 void B_DB_MYSQL::db_escape_string(JCR *jcr, char *snew, char *old, int len)
322 {
323    mysql_real_escape_string(m_db_handle, snew, old, len);
324 }
325
326 /*
327  * Escape binary object so that MySQL is happy
328  * Memory is stored in B_DB struct, no need to free it
329  */
330 char *B_DB_MYSQL::db_escape_object(JCR *jcr, char *old, int len)
331 {
332    esc_obj = check_pool_memory_size(esc_obj, len*2+1);
333    mysql_real_escape_string(m_db_handle, esc_obj, old, len);
334    return esc_obj;
335 }
336
337 /*
338  * Unescape binary object so that MySQL is happy
339  */
340 void B_DB_MYSQL::db_unescape_object(JCR *jcr, char *from, int32_t expected_len,
341                                     POOLMEM **dest, int32_t *dest_len)
342 {
343    if (!from) {
344       *dest[0] = 0;
345       *dest_len = 0;
346       return;
347    }
348    *dest = check_pool_memory_size(*dest, expected_len+1);
349    *dest_len = expected_len;
350    memcpy(*dest, from, expected_len);
351    (*dest)[expected_len]=0;
352 }
353
354 void B_DB_MYSQL::db_start_transaction(JCR *jcr)
355 {
356    if (!jcr->attr) {
357       jcr->attr = get_pool_memory(PM_FNAME);
358    }
359    if (!jcr->ar) {
360       jcr->ar = (ATTR_DBR *)malloc(sizeof(ATTR_DBR));
361    }
362 }
363
364 void B_DB_MYSQL::db_end_transaction(JCR *jcr)
365 {
366    if (jcr && jcr->cached_attribute) {
367       Dmsg0(400, "Flush last cached attribute.\n");
368       if (!db_create_attributes_record(jcr, this, jcr->ar)) {
369          Jmsg1(jcr, M_FATAL, 0, _("Attribute create error. %s"), db_strerror(jcr->db));
370       }
371       jcr->cached_attribute = false;
372    }
373 }
374
375 /*
376  * Submit a general SQL command (cmd), and for each row returned,
377  * the result_handler is called with the ctx.
378  */
379 bool B_DB_MYSQL::db_sql_query(const char *query, DB_RESULT_HANDLER *result_handler, void *ctx)
380 {
381    int ret;
382    SQL_ROW row;
383    bool send = true;
384    bool retval = false;
385
386    Dmsg1(500, "db_sql_query starts with %s\n", query);
387
388    db_lock(this);
389    ret = mysql_query(m_db_handle, query);
390    if (ret != 0) {
391       Mmsg(errmsg, _("Query failed: %s: ERR=%s\n"), query, sql_strerror());
392       Dmsg0(500, "db_sql_query failed\n");
393       goto bail_out;
394    }
395
396    Dmsg0(500, "db_sql_query succeeded. checking handler\n");
397
398    if (result_handler != NULL) {
399       if ((m_result = mysql_use_result(m_db_handle)) != NULL) {
400          m_num_fields = mysql_num_fields(m_result);
401
402          /*
403           * We *must* fetch all rows
404           */
405          while ((row = mysql_fetch_row(m_result)) != NULL) {
406             if (send) {
407                /* the result handler returns 1 when it has
408                 *  seen all the data it wants.  However, we
409                 *  loop to the end of the data.
410                 */
411                if (result_handler(ctx, m_num_fields, row)) {
412                   send = false;
413                }
414             }
415          }
416          sql_free_result();
417       }
418    }
419
420    Dmsg0(500, "db_sql_query finished\n");
421    retval = true;
422
423 bail_out:
424    db_unlock(this);
425    return retval;
426 }
427
428 bool B_DB_MYSQL::sql_query(const char *query, int flags)
429 {
430    int ret;
431    bool retval = true;
432
433    Dmsg1(500, "sql_query starts with '%s'\n", query);
434    /*
435     * We are starting a new query. reset everything.
436     */
437    m_num_rows     = -1;
438    m_row_number   = -1;
439    m_field_number = -1;
440
441    if (m_result) {
442       mysql_free_result(m_result);
443       m_result = NULL;
444    }
445
446    ret = mysql_query(m_db_handle, query);
447    if (ret == 0) {
448       Dmsg0(500, "we have a result\n");
449       if (flags & QF_STORE_RESULT) {
450          m_result = mysql_store_result(m_db_handle);
451          if (m_result != NULL) {
452             m_num_fields = mysql_num_fields(m_result);
453             Dmsg1(500, "we have %d fields\n", m_num_fields);
454             m_num_rows = mysql_num_rows(m_result);
455             Dmsg1(500, "we have %d rows\n", m_num_rows);
456          } else {
457             m_num_fields = 0;
458             m_num_rows = mysql_affected_rows(m_db_handle);
459             Dmsg1(500, "we have %d rows\n", m_num_rows);
460          }
461       } else {
462          m_num_fields = 0;
463          m_num_rows = mysql_affected_rows(m_db_handle);
464          Dmsg1(500, "we have %d rows\n", m_num_rows);
465       }
466    } else {
467       Dmsg0(500, "we failed\n");
468       m_status = 1;                   /* failed */
469       retval = false;
470    }
471    return retval;
472 }
473
474 void B_DB_MYSQL::sql_free_result(void)
475 {
476    db_lock(this);
477    if (m_result) {
478       mysql_free_result(m_result);
479       m_result = NULL;
480    }
481    if (m_fields) {
482       free(m_fields);
483       m_fields = NULL;
484    }
485    m_num_rows = m_num_fields = 0;
486    db_unlock(this);
487 }
488
489 SQL_ROW B_DB_MYSQL::sql_fetch_row(void)
490 {
491    if (!m_result) {
492       return NULL;
493    } else {
494       return mysql_fetch_row(m_result);
495    }
496 }
497
498 const char *B_DB_MYSQL::sql_strerror(void)
499 {
500    return mysql_error(m_db_handle);
501 }
502
503 void B_DB_MYSQL::sql_data_seek(int row)
504 {
505    return mysql_data_seek(m_result, row);
506 }
507
508 int B_DB_MYSQL::sql_affected_rows(void)
509 {
510    return mysql_affected_rows(m_db_handle);
511 }
512
513 uint64_t B_DB_MYSQL::sql_insert_autokey_record(const char *query, const char *table_name)
514 {
515    /*
516     * First execute the insert query and then retrieve the currval.
517     */
518    if (mysql_query(m_db_handle, query) != 0) {
519       return 0;
520    }
521
522    m_num_rows = mysql_affected_rows(m_db_handle);
523    if (m_num_rows != 1) {
524       return 0;
525    }
526
527    changes++;
528
529    return mysql_insert_id(m_db_handle);
530 }
531
532 SQL_FIELD *B_DB_MYSQL::sql_fetch_field(void)
533 {
534    int i;
535    MYSQL_FIELD *field;
536
537    if (!m_fields || m_fields_size < m_num_fields) {
538       if (m_fields) {
539          free(m_fields);
540          m_fields = NULL;
541       }
542       Dmsg1(500, "allocating space for %d fields\n", m_num_fields);
543       m_fields = (SQL_FIELD *)malloc(sizeof(SQL_FIELD) * m_num_fields);
544       m_fields_size = m_num_fields;
545
546       for (i = 0; i < m_num_fields; i++) {
547          Dmsg1(500, "filling field %d\n", i);
548          if ((field = mysql_fetch_field(m_result)) != NULL) {
549             m_fields[i].name = field->name;
550             m_fields[i].max_length = field->max_length;
551             m_fields[i].type = field->type;
552             m_fields[i].flags = field->flags;
553
554             Dmsg4(500, "sql_fetch_field finds field '%s' has length='%d' type='%d' and IsNull=%d\n",
555                   m_fields[i].name, m_fields[i].max_length, m_fields[i].type, m_fields[i].flags);
556          }
557       }
558    }
559
560    /*
561     * Increment field number for the next time around
562     */
563    return &m_fields[m_field_number++];
564 }
565
566 bool B_DB_MYSQL::sql_field_is_not_null(int field_type)
567 {
568    return IS_NOT_NULL(field_type);
569 }
570
571 bool B_DB_MYSQL::sql_field_is_numeric(int field_type)
572 {
573    return IS_NUM(field_type);
574 }
575
576 /* 
577  * Returns true if OK
578  *         false if failed
579  */
580 bool B_DB_MYSQL::sql_batch_start(JCR *jcr)
581 {
582    bool retval;
583
584    db_lock(this);
585    retval = sql_query("CREATE TEMPORARY TABLE batch ("
586                               "FileIndex integer,"
587                               "JobId integer,"
588                               "Path blob,"
589                               "Name blob,"
590                               "LStat tinyblob,"
591                               "MD5 tinyblob,"
592                               "DeltaSeq integer)");
593    db_unlock(this);
594
595    /*
596     * Keep track of the number of changes in batch mode.
597     */
598    changes = 0;
599
600    return retval;
601 }
602
603 /* set error to something to abort operation */
604 /* 
605  * Returns true if OK
606  *         false if failed
607  */
608 bool B_DB_MYSQL::sql_batch_end(JCR *jcr, const char *error)
609 {
610    m_status = 0;
611
612    /*
613     * Flush any pending inserts.
614     */
615    if (changes) {
616       return sql_query(cmd);
617    }
618
619    return true;
620 }
621
622 /* 
623  * Returns true if OK
624  *         false if failed
625  */
626 bool B_DB_MYSQL::sql_batch_insert(JCR *jcr, ATTR_DBR *ar)
627 {
628    const char *digest;
629    char ed1[50];
630
631    esc_name = check_pool_memory_size(esc_name, fnl*2+1);
632    db_escape_string(jcr, esc_name, fname, fnl);
633
634    esc_path = check_pool_memory_size(esc_path, pnl*2+1);
635    db_escape_string(jcr, esc_path, path, pnl);
636
637    if (ar->Digest == NULL || ar->Digest[0] == 0) {
638       digest = "0";
639    } else {
640       digest = ar->Digest;
641    }
642
643    /*
644     * Try to batch up multiple inserts using multi-row inserts.
645     */
646    if (changes == 0) {
647       Mmsg(cmd, "INSERT INTO batch VALUES "
648            "(%u,%s,'%s','%s','%s','%s',%u)",
649            ar->FileIndex, edit_int64(ar->JobId,ed1), esc_path,
650            esc_name, ar->attr, digest, ar->DeltaSeq);
651       changes++;
652    } else {
653       /*
654        * We use the esc_obj for temporary storage otherwise
655        * we keep on copying data.
656        */
657       Mmsg(esc_obj, ",(%u,%s,'%s','%s','%s','%s',%u)",
658            ar->FileIndex, edit_int64(ar->JobId,ed1), esc_path,
659            esc_name, ar->attr, digest, ar->DeltaSeq);
660       pm_strcat(cmd, esc_obj);
661       changes++;
662    }
663
664    /*
665     * See if we need to flush the query buffer filled
666     * with multi-row inserts.
667     */
668    if ((changes % MYSQL_CHANGES_PER_BATCH_INSERT) == 0) {
669       if (!sql_query(cmd)) {
670          changes = 0;
671          return false;
672       } else {
673          changes = 0;
674       }
675    }
676    return true;
677 }
678
679 /*
680  * Initialize database data structure. In principal this should
681  * never have errors, or it is really fatal.
682  */
683 B_DB *db_init_database(JCR *jcr, const char *db_driver, const char *db_name, const char *db_user,
684                        const char *db_password, const char *db_address, int db_port, const char *db_socket,
685                        bool mult_db_connections, bool disable_batch_insert)
686 {
687    B_DB_MYSQL *mdb = NULL;
688
689    if (!db_user) {
690       Jmsg(jcr, M_FATAL, 0, _("A user name for MySQL must be supplied.\n"));
691       return NULL;
692    }
693    P(mutex);                          /* lock DB queue */
694
695    /*
696     * Look to see if DB already open
697     */
698    if (db_list && !mult_db_connections) {
699       foreach_dlist(mdb, db_list) {
700          if (mdb->db_match_database(db_driver, db_name, db_address, db_port)) {
701             Dmsg1(100, "DB REopen %s\n", db_name);
702             mdb->increment_refcount();
703             goto bail_out;
704          }
705       }
706    }
707    Dmsg0(100, "db_init_database first time\n");
708    mdb = New(B_DB_MYSQL(jcr, db_driver, db_name, db_user, db_password, db_address,
709                         db_port, db_socket, mult_db_connections, disable_batch_insert));
710
711 bail_out:
712    V(mutex);
713    return mdb;
714 }
715
716 #endif /* HAVE_MYSQL */