1 /*
2 BAREOS® - Backup Archiving REcovery Open Sourced
3
4 Copyright (C) 2000-2011 Free Software Foundation Europe e.V.
5 Copyright (C) 2011-2016 Planets Communications B.V.
6 Copyright (C) 2013-2020 Bareos GmbH & Co. KG
7
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 Affero 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 /*
24 * Kern Sibbald, January 2002
25 * Major rewrite by Marco van Wieringen, January 2010 for catalog refactoring.
26 */
27 /**
28 * @file
29 * BAREOS Catalog Database routines specific to SQLite
30 */
31
32 #include "include/bareos.h"
33
34 #if HAVE_SQLITE3
35
36 # include "cats.h"
37 # include <sqlite3.h>
38 # include "bdb_sqlite.h"
39
40 /* pull in the generated queries definitions */
41 # include "sqlite_queries.inc"
42 # include "lib/edit.h"
43 # include "lib/berrno.h"
44 # include "lib/dlist.h"
45
46 /* -----------------------------------------------------------------------
47 *
48 * SQLite dependent defines and subroutines
49 *
50 * -----------------------------------------------------------------------
51 */
52
53 /*
54 * List of open databases
55 */
56 static dlist* db_list = NULL;
57
58 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
59
60 /*
61 * When using mult_db_connections = true,
62 * sqlite can be BUSY. We just need sleep a little in this case.
63 */
SqliteBusyHandler(void * arg,int calls)64 static int SqliteBusyHandler(void* arg, int calls)
65 {
66 Bmicrosleep(0, 500);
67 return 1;
68 }
69
BareosDbSqlite(JobControlRecord * 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,bool mult_db_connections,bool disable_batch_insert,bool try_reconnect,bool exit_on_fatal,bool need_private)70 BareosDbSqlite::BareosDbSqlite(JobControlRecord* jcr,
71 const char* db_driver,
72 const char* db_name,
73 const char* db_user,
74 const char* db_password,
75 const char* db_address,
76 int db_port,
77 const char* db_socket,
78 bool mult_db_connections,
79 bool disable_batch_insert,
80 bool try_reconnect,
81 bool exit_on_fatal,
82 bool need_private)
83 {
84 /*
85 * Initialize the parent class members.
86 */
87 db_interface_type_ = SQL_INTERFACE_TYPE_SQLITE3;
88 db_type_ = SQL_TYPE_SQLITE3;
89 db_driver_ = strdup("SQLite3");
90 db_name_ = strdup(db_name);
91 if (disable_batch_insert) {
92 disabled_batch_insert_ = true;
93 have_batch_insert_ = false;
94 } else {
95 disabled_batch_insert_ = false;
96 # if defined(USE_BATCH_FILE_INSERT)
97 # if defined(HAVE_SQLITE3_THREADSAFE)
98 have_batch_insert_ = sqlite3_threadsafe();
99 # else
100 have_batch_insert_ = false;
101 # endif /* HAVE_SQLITE3_THREADSAFE */
102 # else
103 have_batch_insert_ = false;
104 # endif /* USE_BATCH_FILE_INSERT */
105 }
106 errmsg = GetPoolMemory(PM_EMSG); /* get error message buffer */
107 *errmsg = 0;
108 cmd = GetPoolMemory(PM_EMSG); /* get command buffer */
109 cached_path = GetPoolMemory(PM_FNAME);
110 cached_path_id = 0;
111 ref_count_ = 1;
112 fname = GetPoolMemory(PM_FNAME);
113 path = GetPoolMemory(PM_FNAME);
114 esc_name = GetPoolMemory(PM_FNAME);
115 esc_path = GetPoolMemory(PM_FNAME);
116 esc_obj = GetPoolMemory(PM_FNAME);
117 allow_transactions_ = mult_db_connections;
118 is_private_ = need_private;
119 try_reconnect_ = try_reconnect;
120 exit_on_fatal_ = exit_on_fatal;
121
122 /*
123 * Initialize the private members.
124 */
125 db_handle_ = NULL;
126 result_ = NULL;
127 lowlevel_errmsg_ = NULL;
128
129 /*
130 * Put the db in the list.
131 */
132 if (db_list == NULL) { db_list = new dlist(this, &this->link_); }
133 db_list->append(this);
134
135 /* make the queries available using the queries variable from the parent class
136 */
137 queries = query_definitions;
138 }
139
~BareosDbSqlite()140 BareosDbSqlite::~BareosDbSqlite() {}
141
142 /**
143 * Now actually open the database. This can generate errors,
144 * which are returned in the errmsg
145 *
146 * DO NOT close the database or delete mdb here !!!!
147 */
OpenDatabase(JobControlRecord * jcr)148 bool BareosDbSqlite::OpenDatabase(JobControlRecord* jcr)
149 {
150 bool retval = false;
151 char* db_path;
152 int len;
153 struct stat statbuf;
154 int status;
155 int errstat;
156 int retry = 0;
157
158 P(mutex);
159 if (connected_) {
160 retval = true;
161 goto bail_out;
162 }
163
164 if ((errstat = RwlInit(&lock_)) != 0) {
165 BErrNo be;
166 Mmsg1(errmsg, _("Unable to initialize DB lock. ERR=%s\n"),
167 be.bstrerror(errstat));
168 goto bail_out;
169 }
170
171 /*
172 * Open the database
173 */
174 len = strlen(working_directory) + strlen(db_name_) + 5;
175 db_path = (char*)malloc(len);
176 strcpy(db_path, working_directory);
177 strcat(db_path, "/");
178 strcat(db_path, db_name_);
179 strcat(db_path, ".db");
180 if (stat(db_path, &statbuf) != 0) {
181 Mmsg1(errmsg, _("Database %s does not exist, please create it.\n"),
182 db_path);
183 free(db_path);
184 goto bail_out;
185 }
186
187 for (db_handle_ = NULL; !db_handle_ && retry++ < 10;) {
188 status = sqlite3_open(db_path, &db_handle_);
189 if (status != SQLITE_OK) {
190 lowlevel_errmsg_ = (char*)sqlite3_errmsg(db_handle_);
191 sqlite3_close(db_handle_);
192 db_handle_ = NULL;
193 } else {
194 lowlevel_errmsg_ = NULL;
195 }
196
197 Dmsg0(300, "sqlite_open\n");
198 if (!db_handle_) { Bmicrosleep(1, 0); }
199 }
200 if (db_handle_ == NULL) {
201 Mmsg2(errmsg, _("Unable to open Database=%s. ERR=%s\n"), db_path,
202 lowlevel_errmsg_ ? lowlevel_errmsg_ : _("unknown"));
203 free(db_path);
204 goto bail_out;
205 }
206 connected_ = true;
207 free(db_path);
208
209 /*
210 * Set busy handler to wait when we use mult_db_connections = true
211 */
212 sqlite3_busy_handler(db_handle_, SqliteBusyHandler, NULL);
213
214 # if defined(SQLITE3_INIT_QUERY)
215 SqlQueryWithoutHandler(SQLITE3_INIT_QUERY);
216 # endif
217
218 if (!CheckTablesVersion(jcr)) { goto bail_out; }
219
220 retval = true;
221
222 bail_out:
223 V(mutex);
224 return retval;
225 }
226
CloseDatabase(JobControlRecord * jcr)227 void BareosDbSqlite::CloseDatabase(JobControlRecord* jcr)
228 {
229 if (connected_) { EndTransaction(jcr); }
230 P(mutex);
231 ref_count_--;
232 if (ref_count_ == 0) {
233 if (connected_) { SqlFreeResult(); }
234 db_list->remove(this);
235 if (connected_ && db_handle_) { sqlite3_close(db_handle_); }
236 if (RwlIsInit(&lock_)) { RwlDestroy(&lock_); }
237 FreePoolMemory(errmsg);
238 FreePoolMemory(cmd);
239 FreePoolMemory(cached_path);
240 FreePoolMemory(fname);
241 FreePoolMemory(path);
242 FreePoolMemory(esc_name);
243 FreePoolMemory(esc_path);
244 FreePoolMemory(esc_obj);
245 if (db_driver_) { free(db_driver_); }
246 if (db_name_) { free(db_name_); }
247 delete this;
248 if (db_list->size() == 0) {
249 delete db_list;
250 db_list = NULL;
251 }
252 }
253 V(mutex);
254 }
255
ValidateConnection(void)256 bool BareosDbSqlite::ValidateConnection(void)
257 {
258 bool retval;
259
260 DbLock(this);
261 if (SqlQueryWithoutHandler("SELECT 1", true)) {
262 SqlFreeResult();
263 retval = true;
264 goto bail_out;
265 } else {
266 retval = false;
267 goto bail_out;
268 }
269
270 bail_out:
271 DbUnlock(this);
272 return retval;
273 }
274
ThreadCleanup(void)275 void BareosDbSqlite::ThreadCleanup(void) { sqlite3_thread_cleanup(); }
276
277 /**
278 * Start a transaction. This groups inserts and makes things
279 * much more efficient. Usually started when inserting
280 * file attributes.
281 */
StartTransaction(JobControlRecord * jcr)282 void BareosDbSqlite::StartTransaction(JobControlRecord* jcr)
283 {
284 if (!jcr->attr) { jcr->attr = GetPoolMemory(PM_FNAME); }
285
286 if (!jcr->ar) {
287 jcr->ar = (AttributesDbRecord*)malloc(sizeof(AttributesDbRecord));
288 jcr->ar->Digest = NULL;
289 }
290
291 if (!allow_transactions_) { return; }
292
293 DbLock(this);
294 /*
295 * Allow only 10,000 changes per transaction
296 */
297 if (transaction_ && changes > 10000) { EndTransaction(jcr); }
298 if (!transaction_) {
299 SqlQueryWithoutHandler("BEGIN"); /* begin transaction */
300 Dmsg0(400, "Start SQLite transaction\n");
301 transaction_ = true;
302 }
303 DbUnlock(this);
304 }
305
EndTransaction(JobControlRecord * jcr)306 void BareosDbSqlite::EndTransaction(JobControlRecord* jcr)
307 {
308 if (jcr && jcr->cached_attribute) {
309 Dmsg0(400, "Flush last cached attribute.\n");
310 if (!CreateAttributesRecord(jcr, jcr->ar)) {
311 Jmsg1(jcr, M_FATAL, 0, _("Attribute create error. %s"), strerror());
312 }
313 jcr->cached_attribute = false;
314 }
315
316 if (!allow_transactions_) { return; }
317
318 DbLock(this);
319 if (transaction_) {
320 SqlQueryWithoutHandler("COMMIT"); /* end transaction */
321 transaction_ = false;
322 Dmsg1(400, "End SQLite transaction changes=%d\n", changes);
323 }
324 changes = 0;
325 DbUnlock(this);
326 }
327
328 struct rh_data {
329 BareosDbSqlite* mdb;
330 DB_RESULT_HANDLER* ResultHandler;
331 void* ctx;
332 bool initialized;
333 };
334
335 /**
336 * Convert SQLite's callback into BAREOS DB callback
337 */
SqliteResultHandler(void * arh_data,int num_fields,char ** rows,char ** col_names)338 static int SqliteResultHandler(void* arh_data,
339 int num_fields,
340 char** rows,
341 char** col_names)
342 {
343 struct rh_data* rh_data = (struct rh_data*)arh_data;
344
345 /*
346 * The SqlQueryWithHandler doesn't have access to results_,
347 * so if we wan't to get fields information, we need to use col_names
348 */
349 if (!rh_data->initialized) {
350 rh_data->mdb->SetColumnNames(col_names, num_fields);
351 rh_data->initialized = true;
352 }
353 if (rh_data->ResultHandler) {
354 (*(rh_data->ResultHandler))(rh_data->ctx, num_fields, rows);
355 }
356
357 return 0;
358 }
359
360 /**
361 * Submit a general SQL command (cmd), and for each row returned,
362 * the ResultHandler is called with the ctx.
363 */
SqlQueryWithHandler(const char * query,DB_RESULT_HANDLER * ResultHandler,void * ctx)364 bool BareosDbSqlite::SqlQueryWithHandler(const char* query,
365 DB_RESULT_HANDLER* ResultHandler,
366 void* ctx)
367 {
368 bool retval = false;
369 int status;
370 struct rh_data rh_data;
371
372 Dmsg1(500, "SqlQueryWithHandler starts with '%s'\n", query);
373
374 DbLock(this);
375 if (lowlevel_errmsg_) {
376 sqlite3_free(lowlevel_errmsg_);
377 lowlevel_errmsg_ = NULL;
378 }
379 SqlFreeResult();
380
381 rh_data.ctx = ctx;
382 rh_data.mdb = this;
383 rh_data.initialized = false;
384 rh_data.ResultHandler = ResultHandler;
385
386 status = sqlite3_exec(db_handle_, query, SqliteResultHandler, (void*)&rh_data,
387 &lowlevel_errmsg_);
388
389 if (status != SQLITE_OK) {
390 Mmsg(errmsg, _("Query failed: %s: ERR=%s\n"), query, sql_strerror());
391 Dmsg0(500, "SqlQueryWithHandler finished\n");
392 goto bail_out;
393 }
394 Dmsg0(500, "db_sql_query finished\n");
395 SqlFreeResult();
396 retval = true;
397
398 bail_out:
399 DbUnlock(this);
400 return retval;
401 }
402
403 /**
404 * Submit a sqlite query and retrieve all the data
405 */
SqlQueryWithoutHandler(const char * query,int flags)406 bool BareosDbSqlite::SqlQueryWithoutHandler(const char* query, int flags)
407 {
408 int status;
409 bool retval = false;
410
411 Dmsg1(500, "SqlQueryWithoutHandler starts with '%s'\n", query);
412
413 SqlFreeResult();
414 if (lowlevel_errmsg_) {
415 sqlite3_free(lowlevel_errmsg_);
416 lowlevel_errmsg_ = NULL;
417 }
418
419 status = sqlite3_get_table(db_handle_, (char*)query, &result_, &num_rows_,
420 &num_fields_, &lowlevel_errmsg_);
421
422 row_number_ = 0; /* no row fetched */
423 if (status != 0) { /* something went wrong */
424 num_rows_ = num_fields_ = 0;
425 Dmsg0(500, "SqlQueryWithoutHandler finished\n");
426 } else {
427 Dmsg0(500, "SqlQueryWithoutHandler finished\n");
428 retval = true;
429 }
430 return retval;
431 }
432
SqlFreeResult(void)433 void BareosDbSqlite::SqlFreeResult(void)
434 {
435 DbLock(this);
436 if (fields_) {
437 free(fields_);
438 fields_ = NULL;
439 }
440 if (result_) {
441 sqlite3_free_table(result_);
442 result_ = NULL;
443 }
444 col_names_ = NULL;
445 num_rows_ = num_fields_ = 0;
446 DbUnlock(this);
447 }
448
449 /**
450 * Fetch one row at a time
451 */
SqlFetchRow(void)452 SQL_ROW BareosDbSqlite::SqlFetchRow(void)
453 {
454 if (!result_ || (row_number_ >= num_rows_)) { return NULL; }
455 row_number_++;
456 return &result_[num_fields_ * row_number_];
457 }
458
sql_strerror(void)459 const char* BareosDbSqlite::sql_strerror(void)
460 {
461 return lowlevel_errmsg_ ? lowlevel_errmsg_ : "unknown";
462 }
463
SqlDataSeek(int row)464 void BareosDbSqlite::SqlDataSeek(int row)
465 {
466 /*
467 * Set the row number to be returned on the next call to sql_fetch_row
468 */
469 row_number_ = row;
470 }
471
SqlAffectedRows(void)472 int BareosDbSqlite::SqlAffectedRows(void)
473 {
474 return sqlite3_changes(db_handle_);
475 }
476
SqlInsertAutokeyRecord(const char * query,const char * table_name)477 uint64_t BareosDbSqlite::SqlInsertAutokeyRecord(const char* query,
478 const char* table_name)
479 {
480 /*
481 * First execute the insert query and then retrieve the currval.
482 */
483 if (!SqlQueryWithoutHandler(query)) { return 0; }
484
485 num_rows_ = SqlAffectedRows();
486 if (num_rows_ != 1) { return 0; }
487
488 changes++;
489
490 return sqlite3_last_insert_rowid(db_handle_);
491 }
492
SqlFetchField(void)493 SQL_FIELD* BareosDbSqlite::SqlFetchField(void)
494 {
495 int i, j, len;
496
497 /* We are in the middle of a db_sql_query and we want to get fields info */
498 if (col_names_ != NULL) {
499 if (num_fields_ > field_number_) {
500 sql_field_.name = col_names_[field_number_];
501 /* We don't have the maximum field length, so we can use 80 as
502 * estimation.
503 */
504 len = MAX(cstrlen(sql_field_.name), 80 / num_fields_);
505 sql_field_.max_length = len;
506
507 field_number_++;
508 sql_field_.type = 0; /* not numeric */
509 sql_field_.flags = 1; /* not null */
510 return &sql_field_;
511 } else { /* too much fetch_field() */
512 return NULL;
513 }
514 }
515
516 /* We are after a SqlQuery() that stores the result in results_ */
517 if (!fields_ || fields_size_ < num_fields_) {
518 if (fields_) {
519 free(fields_);
520 fields_ = NULL;
521 }
522 Dmsg1(500, "allocating space for %d fields\n", num_fields_);
523 fields_ = (SQL_FIELD*)malloc(sizeof(SQL_FIELD) * num_fields_);
524 fields_size_ = num_fields_;
525
526 for (i = 0; i < num_fields_; i++) {
527 Dmsg1(500, "filling field %d\n", i);
528 fields_[i].name = result_[i];
529 fields_[i].max_length = cstrlen(fields_[i].name);
530 for (j = 1; j <= num_rows_; j++) {
531 if (result_[i + num_fields_ * j]) {
532 len = (uint32_t)cstrlen(result_[i + num_fields_ * j]);
533 } else {
534 len = 0;
535 }
536 if (len > fields_[i].max_length) { fields_[i].max_length = len; }
537 }
538 fields_[i].type = 0;
539 fields_[i].flags = 1; /* not null */
540
541 Dmsg4(500,
542 "SqlFetchField finds field '%s' has length='%d' type='%d' and "
543 "IsNull=%d\n",
544 fields_[i].name, fields_[i].max_length, fields_[i].type,
545 fields_[i].flags);
546 }
547 }
548
549 /*
550 * Increment field number for the next time around
551 */
552 return &fields_[field_number_++];
553 }
554
SqlFieldIsNotNull(int field_type)555 bool BareosDbSqlite::SqlFieldIsNotNull(int field_type)
556 {
557 switch (field_type) {
558 case 1:
559 return true;
560 default:
561 return false;
562 }
563 }
564
SqlFieldIsNumeric(int field_type)565 bool BareosDbSqlite::SqlFieldIsNumeric(int field_type)
566 {
567 switch (field_type) {
568 case 1:
569 return true;
570 default:
571 return false;
572 }
573 }
574
575 /**
576 * Returns true if OK
577 * false if failed
578 */
SqlBatchStartFileTable(JobControlRecord * jcr)579 bool BareosDbSqlite::SqlBatchStartFileTable(JobControlRecord* jcr)
580 {
581 bool retval;
582
583 DbLock(this);
584 retval = SqlQueryWithoutHandler(
585 "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 "Fhinfo TEXT,"
594 "Fhnode TEXT "
595 ")");
596 DbUnlock(this);
597
598 return retval;
599 }
600
601 /* set error to something to abort operation */
602 /**
603 * Returns true if OK
604 * false if failed
605 */
SqlBatchEndFileTable(JobControlRecord * jcr,const char * error)606 bool BareosDbSqlite::SqlBatchEndFileTable(JobControlRecord* jcr,
607 const char* error)
608 {
609 status_ = 0;
610
611 return true;
612 }
613
614 /**
615 * Returns true if OK
616 * false if failed
617 */
SqlBatchInsertFileTable(JobControlRecord * jcr,AttributesDbRecord * ar)618 bool BareosDbSqlite::SqlBatchInsertFileTable(JobControlRecord* jcr,
619 AttributesDbRecord* ar)
620 {
621 const char* digest;
622 char ed1[50], ed2[50], ed3[50];
623
624 esc_name = CheckPoolMemorySize(esc_name, fnl * 2 + 1);
625 EscapeString(jcr, esc_name, fname, fnl);
626
627 esc_path = CheckPoolMemorySize(esc_path, pnl * 2 + 1);
628 EscapeString(jcr, esc_path, path, pnl);
629
630 if (ar->Digest == NULL || ar->Digest[0] == 0) {
631 digest = "0";
632 } else {
633 digest = ar->Digest;
634 }
635
636 Mmsg(cmd,
637 "INSERT INTO batch VALUES "
638 "(%u,%s,'%s','%s','%s','%s',%u,'%s','%s')",
639 ar->FileIndex, edit_int64(ar->JobId, ed1), esc_path, esc_name, ar->attr,
640 digest, ar->DeltaSeq, edit_uint64(ar->Fhinfo, ed2),
641 edit_uint64(ar->Fhnode, ed3));
642
643 return SqlQueryWithoutHandler(cmd);
644 }
645
SqlCopyStart(const std::string &,const std::vector<std::string> &)646 bool BareosDbSqlite::SqlCopyStart(
647 const std::string& /*table_name*/,
648 const std::vector<std::string>& /*column_names*/)
649 {
650 return false;
651 }
652
SqlCopyInsert(const std::vector<DatabaseField> &)653 bool BareosDbSqlite::SqlCopyInsert(
654 const std::vector<DatabaseField>& /* data_fields */)
655 {
656 return false;
657 }
658
SqlCopyEnd()659 bool BareosDbSqlite::SqlCopyEnd() { return false; }
660
661 /**
662 * Initialize database data structure. In principal this should
663 * never have errors, or it is really fatal.
664 */
665 # ifdef HAVE_DYNAMIC_CATS_BACKENDS
backend_instantiate(JobControlRecord * 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,bool mult_db_connections,bool disable_batch_insert,bool try_reconnect,bool exit_on_fatal,bool need_private)666 extern "C" BareosDb* backend_instantiate(JobControlRecord* jcr,
667 const char* db_driver,
668 const char* db_name,
669 const char* db_user,
670 const char* db_password,
671 const char* db_address,
672 int db_port,
673 const char* db_socket,
674 bool mult_db_connections,
675 bool disable_batch_insert,
676 bool try_reconnect,
677 bool exit_on_fatal,
678 bool need_private)
679 # else
680 BareosDb* db_init_database(JobControlRecord* jcr,
681 const char* db_driver,
682 const char* db_name,
683 const char* db_user,
684 const char* db_password,
685 const char* db_address,
686 int db_port,
687 const char* db_socket,
688 bool mult_db_connections,
689 bool disable_batch_insert,
690 bool try_reconnect,
691 bool exit_on_fatal,
692 bool need_private)
693 # endif
694 {
695 BareosDb* mdb = NULL;
696
697 P(mutex); /* lock DB queue */
698
699 /*
700 * Look to see if DB already open
701 */
702 if (db_list && !mult_db_connections && !need_private) {
703 foreach_dlist (mdb, db_list) {
704 if (mdb->IsPrivate()) { continue; }
705
706 if (mdb->MatchDatabase(db_driver, db_name, db_address, db_port)) {
707 Dmsg1(300, "DB REopen %s\n", db_name);
708 mdb->IncrementRefcount();
709 goto bail_out;
710 }
711 }
712 }
713 Dmsg0(300, "db_init_database first time\n");
714 mdb = new BareosDbSqlite(jcr, db_driver, db_name, db_user, db_password,
715 db_address, db_port, db_socket, mult_db_connections,
716 disable_batch_insert, try_reconnect, exit_on_fatal,
717 need_private);
718
719 bail_out:
720 V(mutex);
721 return mdb;
722 }
723
724 # ifdef HAVE_DYNAMIC_CATS_BACKENDS
flush_backend(void)725 extern "C" void flush_backend(void)
726 # else
727 void DbFlushBackends(void)
728 # endif
729 {
730 }
731
732 #endif /* HAVE_SQLITE3 */
733