]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/cats/mysql.c
ebl Cleanup batch code
[bacula/bacula] / bacula / src / cats / mysql.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2000-2007 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 two of the GNU 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 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 John Walker.
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  *    Version $Id$
36  */
37
38
39 /* The following is necessary so that we do not include
40  * the dummy external definition of DB.
41  */
42 #define __SQL_C                       /* indicate that this is sql.c */
43
44 #include "bacula.h"
45 #include "cats.h"
46
47 #ifdef HAVE_MYSQL
48
49 /* -----------------------------------------------------------------------
50  *
51  *   MySQL dependent defines and subroutines
52  *
53  * -----------------------------------------------------------------------
54  */
55
56 /* List of open databases */
57 static BQUEUE db_list = {&db_list, &db_list};
58
59 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
60
61 /*
62  * Retrieve database type
63  */
64 const char *
65 db_get_type(void)
66 {
67    return "MySQL";
68 }
69
70 /*
71  * Initialize database data structure. In principal this should
72  * never have errors, or it is really fatal.
73  */
74 B_DB *
75 db_init_database(JCR *jcr, const char *db_name, const char *db_user, const char *db_password,
76                  const char *db_address, int db_port, const char *db_socket,
77                  int mult_db_connections)
78 {
79    B_DB *mdb;
80
81    if (!db_user) {
82       Jmsg(jcr, M_FATAL, 0, _("A user name for MySQL must be supplied.\n"));
83       return NULL;
84    }
85    P(mutex);                          /* lock DB queue */
86    /* Look to see if DB already open */
87    if (!mult_db_connections) {
88       for (mdb=NULL; (mdb=(B_DB *)qnext(&db_list, &mdb->bq)); ) {
89          if (bstrcmp(mdb->db_name, db_name) &&
90              bstrcmp(mdb->db_address, db_address) &&
91              mdb->db_port == db_port) {
92             Dmsg2(100, "DB REopen %d %s\n", mdb->ref_count, db_name);
93             mdb->ref_count++;
94             V(mutex);
95             Dmsg3(100, "initdb ref=%d connected=%d db=%p\n", mdb->ref_count,
96                   mdb->connected, mdb->db);
97             return mdb;                  /* already open */
98          }
99       }
100    }
101    Dmsg0(100, "db_open first time\n");
102    mdb = (B_DB *)malloc(sizeof(B_DB));
103    memset(mdb, 0, sizeof(B_DB));
104    mdb->db_name = bstrdup(db_name);
105    mdb->db_user = bstrdup(db_user);
106    if (db_password) {
107       mdb->db_password = bstrdup(db_password);
108    }
109    if (db_address) {
110       mdb->db_address = bstrdup(db_address);
111    }
112    if (db_socket) {
113       mdb->db_socket = bstrdup(db_socket);
114    }
115    mdb->db_port = db_port;
116    mdb->have_insert_id = true;
117    mdb->errmsg = get_pool_memory(PM_EMSG); /* get error message buffer */
118    *mdb->errmsg = 0;
119    mdb->cmd = get_pool_memory(PM_EMSG);    /* get command buffer */
120    mdb->cached_path = get_pool_memory(PM_FNAME);
121    mdb->cached_path_id = 0;
122    mdb->ref_count = 1;
123    mdb->fname = get_pool_memory(PM_FNAME);
124    mdb->path = get_pool_memory(PM_FNAME);
125    mdb->esc_name = get_pool_memory(PM_FNAME);
126    mdb->esc_path = get_pool_memory(PM_FNAME);
127    qinsert(&db_list, &mdb->bq);            /* put db in list */
128    Dmsg3(100, "initdb ref=%d connected=%d db=%p\n", mdb->ref_count,
129          mdb->connected, mdb->db);
130    V(mutex);
131    return mdb;
132 }
133
134 /*
135  * Now actually open the database.  This can generate errors,
136  *  which are returned in the errmsg
137  *
138  * DO NOT close the database or free(mdb) here !!!!
139  */
140 int
141 db_open_database(JCR *jcr, B_DB *mdb)
142 {
143    int errstat;
144
145    P(mutex);
146    if (mdb->connected) {
147       V(mutex);
148       return 1;
149    }
150
151    if ((errstat=rwl_init(&mdb->lock)) != 0) {
152       Mmsg1(&mdb->errmsg, _("Unable to initialize DB lock. ERR=%s\n"),
153             strerror(errstat));
154       V(mutex);
155       return 0;
156    }
157
158    /* connect to the database */
159 #ifdef xHAVE_EMBEDDED_MYSQL
160 // mysql_server_init(0, NULL, NULL);
161 #endif
162    mysql_init(&mdb->mysql);
163
164    Dmsg0(50, "mysql_init done\n");
165    /* If connection fails, try at 5 sec intervals for 30 seconds. */
166    for (int retry=0; retry < 6; retry++) {
167       mdb->db = mysql_real_connect(
168            &(mdb->mysql),                /* db */
169            mdb->db_address,              /* default = localhost */
170            mdb->db_user,                 /*  login name */
171            mdb->db_password,             /*  password */
172            mdb->db_name,                 /* database name */
173            mdb->db_port,                 /* default port */
174            mdb->db_socket,               /* default = socket */
175            CLIENT_FOUND_ROWS);           /* flags */
176
177       /* If no connect, try once more in case it is a timing problem */
178       if (mdb->db != NULL) {
179          break;
180       }
181       bmicrosleep(5,0);
182    }
183
184    mdb->mysql.reconnect = 1;             /* so connection does not timeout */
185    Dmsg0(50, "mysql_real_connect done\n");
186    Dmsg3(50, "db_user=%s db_name=%s db_password=%s\n", mdb->db_user, mdb->db_name,
187             mdb->db_password==NULL?"(NULL)":mdb->db_password);
188
189    if (mdb->db == NULL) {
190       Mmsg2(&mdb->errmsg, _("Unable to connect to MySQL server.\n"
191 "Database=%s User=%s\n"
192 "MySQL connect failed either server not running or your authorization is incorrect.\n"),
193          mdb->db_name, mdb->db_user);
194       V(mutex);
195       return 0;
196    }
197
198    mdb->connected = true;
199    if (!check_tables_version(jcr, mdb)) {
200       V(mutex);
201       return 0;
202    }
203
204    Dmsg3(100, "opendb ref=%d connected=%d db=%p\n", mdb->ref_count,
205          mdb->connected, mdb->db);
206
207    V(mutex);
208    return 1;
209 }
210
211 void
212 db_close_database(JCR *jcr, B_DB *mdb)
213 {
214    if (!mdb) {
215       return;
216    }
217    db_end_transaction(jcr, mdb);
218    P(mutex);
219    sql_free_result(mdb);
220    mdb->ref_count--;
221    Dmsg3(100, "closedb ref=%d connected=%d db=%p\n", mdb->ref_count,
222          mdb->connected, mdb->db);
223    if (mdb->ref_count == 0) {
224       qdchain(&mdb->bq);
225       if (mdb->connected) {
226          Dmsg1(100, "close db=%p\n", mdb->db);
227          mysql_close(&mdb->mysql);
228
229 #ifdef xHAVE_EMBEDDED_MYSQL
230 //       mysql_server_end();
231 #endif
232       }
233       rwl_destroy(&mdb->lock);
234       free_pool_memory(mdb->errmsg);
235       free_pool_memory(mdb->cmd);
236       free_pool_memory(mdb->cached_path);
237       free_pool_memory(mdb->fname);
238       free_pool_memory(mdb->path);
239       free_pool_memory(mdb->esc_name);
240       free_pool_memory(mdb->esc_path);
241       if (mdb->db_name) {
242          free(mdb->db_name);
243       }
244       if (mdb->db_user) {
245          free(mdb->db_user);
246       }
247       if (mdb->db_password) {
248          free(mdb->db_password);
249       }
250       if (mdb->db_address) {
251          free(mdb->db_address);
252       }
253       if (mdb->db_socket) {
254          free(mdb->db_socket);
255       }
256       free(mdb);
257    }
258    V(mutex);
259 }
260
261 /*
262  * This call is needed because the message channel thread
263  *  opens a database on behalf of a jcr that was created in
264  *  a different thread. MySQL then allocates thread specific
265  *  data, which is NOT freed when the original jcr thread
266  *  closes the database.  Thus the msgchan must call here
267  *  to cleanup any thread specific data that it created.
268  */
269 void db_thread_cleanup()
270
271 #ifndef HAVE_WIN32
272    my_thread_end();
273 #endif
274 }
275
276 /*
277  * Return the next unique index (auto-increment) for
278  * the given table.  Return NULL on error.
279  *
280  * For MySQL, NULL causes the auto-increment value
281  *  to be updated.
282  */
283 int db_next_index(JCR *jcr, B_DB *mdb, char *table, char *index)
284 {
285    strcpy(index, "NULL");
286    return 1;
287 }
288
289
290 /*
291  * Escape strings so that MySQL is happy
292  *
293  *   NOTE! len is the length of the old string. Your new
294  *         string must be long enough (max 2*old+1) to hold
295  *         the escaped output.
296  */
297 void
298 db_escape_string(JCR *jcr, B_DB *mdb, char *snew, char *old, int len)
299 {
300    mysql_real_escape_string(mdb->db, snew, old, len);
301 }
302
303 /*
304  * Submit a general SQL command (cmd), and for each row returned,
305  *  the sqlite_handler is called with the ctx.
306  */
307 int db_sql_query(B_DB *mdb, const char *query, DB_RESULT_HANDLER *result_handler, void *ctx)
308 {
309    SQL_ROW row;
310    bool send = true;
311
312    db_lock(mdb);
313    if (sql_query(mdb, query) != 0) {
314       Mmsg(mdb->errmsg, _("Query failed: %s: ERR=%s\n"), query, sql_strerror(mdb));
315       db_unlock(mdb);
316       return 0;
317    }
318    if (result_handler != NULL) {
319       if ((mdb->result = sql_use_result(mdb)) != NULL) {
320          int num_fields = sql_num_fields(mdb);
321
322          /* We *must* fetch all rows */
323          while ((row = sql_fetch_row(mdb)) != NULL) {
324             if (send) {
325                /* the result handler returns 1 when it has
326                 *  seen all the data it wants.  However, we
327                 *  loop to the end of the data.
328                 */
329                if (result_handler(ctx, num_fields, row)) {
330                   send = false;
331                }
332             }
333          }
334
335          sql_free_result(mdb);
336       }
337    }
338    db_unlock(mdb);
339    return 1;
340
341 }
342
343 void my_mysql_free_result(B_DB *mdb)
344 {
345    db_lock(mdb);
346    if (mdb->result) {
347       mysql_free_result(mdb->result);
348       mdb->result = NULL;
349    }
350    db_unlock(mdb);
351 }
352
353 #ifdef HAVE_BATCH_FILE_INSERT
354 char *my_mysql_batch_lock_path_query = "LOCK TABLES Path write," 
355                                                    "batch write," 
356                                                    "Path as p write";
357
358
359 char *my_mysql_batch_lock_filename_query = "LOCK TABLES Filename write,"
360                                                        "batch write,"
361                                                        "Filename as f write";
362
363 char *my_mysql_batch_unlock_tables_query = "UNLOCK TABLES";
364
365 char *my_mysql_batch_fill_path_query = "INSERT INTO Path (Path) "
366                                          "SELECT a.Path FROM " 
367                                           "(SELECT DISTINCT Path "
368                                              "FROM batch) AS a " 
369                                             "WHERE NOT EXISTS "
370                                                "(SELECT Path "
371                                                   "FROM Path AS p "
372                                                  "WHERE p.Path = a.Path)";     
373
374 char *my_mysql_batch_fill_filename_query = "INSERT INTO Filename (Name) "
375                                               "SELECT a.Name FROM " 
376                                                 "(SELECT DISTINCT Name "
377                                                    "FROM batch) AS a " 
378                                                   "WHERE NOT EXISTS "
379                                                      "(SELECT Name "
380                                                         "FROM Filename AS f "
381                                                         "WHERE f.Name = a.Name)";
382 #endif /* HAVE_BATCH_FILE_INSERT */
383
384 #endif /* HAVE_MYSQL */