1 /*
2 Bacula(R) - The Network Backup Solution
3
4 Copyright (C) 2000-2020 Kern Sibbald
5
6 The original author of Bacula is Kern Sibbald, with contributions
7 from many others, a complete list can be found in the file AUTHORS.
8
9 You may use this file and others of this release according to the
10 license defined in the LICENSE file, which includes the Affero General
11 Public License, v3.0 ("AGPLv3") and some additional permissions and
12 terms pursuant to its AGPLv3 Section 7.
13
14 This notice must be preserved when any source code is
15 conveyed and/or propagated.
16
17 Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19 /*
20 * Bacula Catalog Database interface routines
21 *
22 * Almost generic set of SQL database interface routines
23 * (with a little more work)
24 * SQL engine specific routines are in mysql.c, postgresql.c,
25 * sqlite.c, ...
26 *
27 * Written by Kern Sibbald, March 2000
28 *
29 * Note: at one point, this file was changed to class based by a certain
30 * programmer, and other than "wrapping" in a class, which is a trivial
31 * change for a C++ programmer, nothing substantial was done, yet all the
32 * code was recommitted under this programmer's name. Consequently, we
33 * undo those changes here.
34 */
35
36 #include "bacula.h"
37
38 #if HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL
39
40 #include "cats.h"
41
42 /* Forward referenced subroutines */
43 void print_dashes(BDB *mdb);
44 void print_result(BDB *mdb);
45
dbid_list()46 dbid_list::dbid_list()
47 {
48 memset(this, 0, sizeof(dbid_list));
49 max_ids = 1000;
50 DBId = (DBId_t *)malloc(max_ids * sizeof(DBId_t));
51 num_ids = num_seen = tot_ids = 0;
52 PurgedFiles = NULL;
53 }
54
~dbid_list()55 dbid_list::~dbid_list()
56 {
57 free(DBId);
58 }
59
60 /*
61 * Called here to retrieve an string list from the database
62 */
db_string_list_handler(void * ctx,int num_fields,char ** row)63 int db_string_list_handler(void *ctx, int num_fields, char **row)
64 {
65 alist **val = (alist **)ctx;
66
67 if (row[0]) {
68 (*val)->append(bstrdup(row[0]));
69 }
70
71 return 0;
72 }
73
74 /*
75 * Called here to retrieve an integer from the database
76 */
db_int_handler(void * ctx,int num_fields,char ** row)77 int db_int_handler(void *ctx, int num_fields, char **row)
78 {
79 uint32_t *val = (uint32_t *)ctx;
80
81 Dmsg1(800, "int_handler starts with row pointing at %x\n", row);
82
83 if (row[0]) {
84 Dmsg1(800, "int_handler finds '%s'\n", row[0]);
85 *val = str_to_int64(row[0]);
86 } else {
87 Dmsg0(800, "int_handler finds zero\n");
88 *val = 0;
89 }
90 Dmsg0(800, "int_handler finishes\n");
91 return 0;
92 }
93
94 /*
95 * Called here to retrieve a 32/64 bit integer from the database.
96 * The returned integer will be extended to 64 bit.
97 */
db_int64_handler(void * ctx,int num_fields,char ** row)98 int db_int64_handler(void *ctx, int num_fields, char **row)
99 {
100 db_int64_ctx *lctx = (db_int64_ctx *)ctx;
101
102 if (row[0]) {
103 lctx->value = str_to_int64(row[0]);
104 lctx->count++;
105 }
106 return 0;
107 }
108
109 /*
110 * Called here to retrieve a btime from the database.
111 * The returned integer will be extended to 64 bit.
112 */
db_strtime_handler(void * ctx,int num_fields,char ** row)113 int db_strtime_handler(void *ctx, int num_fields, char **row)
114 {
115 db_int64_ctx *lctx = (db_int64_ctx *)ctx;
116
117 if (row[0]) {
118 lctx->value = str_to_utime(row[0]);
119 lctx->count++;
120 }
121 return 0;
122 }
123
124 /*
125 * Use to build a comma separated list of values from a query. "10,20,30"
126 */
db_list_handler(void * ctx,int num_fields,char ** row)127 int db_list_handler(void *ctx, int num_fields, char **row)
128 {
129 db_list_ctx *lctx = (db_list_ctx *)ctx;
130 if (num_fields == 1 && row[0]) {
131 lctx->add(row[0]);
132 }
133 return 0;
134 }
135
136 /*
137 * specific context passed from bdb_check_max_connections to
138 * db_max_connections_handler.
139 */
140 struct max_connections_context {
141 BDB *db;
142 uint32_t nr_connections;
143 };
144
145 /*
146 * Called here to retrieve max_connections from db
147 */
db_max_connections_handler(void * ctx,int num_fields,char ** row)148 static int db_max_connections_handler(void *ctx, int num_fields, char **row)
149 {
150 struct max_connections_context *context;
151 uint32_t index;
152
153 context = (struct max_connections_context *)ctx;
154 switch (context->db->bdb_get_type_index()) {
155 case SQL_TYPE_MYSQL:
156 index = 1;
157 default:
158 index = 0;
159 }
160
161 if (row[index]) {
162 context->nr_connections = str_to_int64(row[index]);
163 } else {
164 Dmsg0(800, "int_handler finds zero\n");
165 context->nr_connections = 0;
166 }
167 return 0;
168 }
169
BDB()170 BDB::BDB()
171 {
172 init_acl();
173 acl_join = get_pool_memory(PM_MESSAGE);
174 acl_where = get_pool_memory(PM_MESSAGE);
175 }
176
~BDB()177 BDB::~BDB()
178 {
179 free_acl();
180 free_pool_memory(acl_join);
181 free_pool_memory(acl_where);
182 }
183
184 /* Get the WHERE section of a query that permits to respect
185 * the console ACLs.
186 *
187 * get_acls(DB_ACL_BIT(DB_ACL_JOB) | DB_ACL_BIT(DB_ACL_CLIENT), true)
188 * -> WHERE Job.Name IN ('a', 'b', 'c') AND Client.Name IN ('d', 'e')
189 *
190 * get_acls(DB_ACL_BIT(DB_ACL_JOB) | DB_ACL_BIT(DB_ACL_CLIENT), false)
191 * -> AND Job.Name IN ('a', 'b', 'c') AND Client.Name IN ('d', 'e')
192 */
get_acls(int tables,bool where)193 char *BDB::get_acls(int tables, bool where /* use WHERE or AND */)
194 {
195 POOL_MEM tmp;
196 pm_strcpy(acl_where, "");
197
198 for (int i=0 ; i < DB_ACL_LAST; i++) {
199 if (tables & DB_ACL_BIT(i)) {
200 pm_strcat(acl_where, get_acl((DB_ACL_t)i, where));
201 where = acl_where[0] == 0 && where;
202 }
203 }
204 return acl_where;
205 }
206
207 /* Create the JOIN string that will help to filter queries results */
get_acl_join_filter(int tables)208 char *BDB::get_acl_join_filter(int tables)
209 {
210 POOL_MEM tmp;
211 pm_strcpy(acl_join, "");
212
213 if (tables & DB_ACL_BIT(DB_ACL_JOB)) {
214 Mmsg(tmp, " JOIN Job USING (JobId) ");
215 pm_strcat(acl_join, tmp);
216 }
217 if (tables & (DB_ACL_BIT(DB_ACL_CLIENT) | DB_ACL_BIT(DB_ACL_RCLIENT) | DB_ACL_BIT(DB_ACL_BCLIENT))) {
218 Mmsg(tmp, " JOIN Client USING (ClientId) ");
219 pm_strcat(acl_join, tmp);
220 }
221 if (tables & DB_ACL_BIT(DB_ACL_POOL)) {
222 Mmsg(tmp, " JOIN Pool USING (PoolId) ");
223 pm_strcat(acl_join, tmp);
224 }
225 if (tables & DB_ACL_BIT(DB_ACL_PATH)) {
226 Mmsg(tmp, " JOIN Path USING (PathId) ");
227 pm_strcat(acl_join, tmp);
228 }
229 if (tables & DB_ACL_BIT(DB_ACL_LOG)) {
230 Mmsg(tmp, " JOIN Log USING (JobId) ");
231 pm_strcat(acl_join, tmp);
232 }
233 if (tables & DB_ACL_BIT(DB_ACL_FILESET)) {
234 Mmsg(tmp, " LEFT JOIN FileSet USING (FileSetId) ");
235 pm_strcat(acl_join, tmp);
236 }
237 return acl_join;
238 }
239
240 /* Intialize the ACL list */
init_acl()241 void BDB::init_acl()
242 {
243 for(int i=0; i < DB_ACL_LAST; i++) {
244 acls[i] = NULL;
245 }
246 }
247
248 /* Free ACL list */
free_acl()249 void BDB::free_acl()
250 {
251 for(int i=0; i < DB_ACL_LAST; i++) {
252 free_and_null_pool_memory(acls[i]);
253 }
254 }
255
256 /* Get ACL for a given type */
get_acl(DB_ACL_t type,bool where)257 const char *BDB::get_acl(DB_ACL_t type, bool where /* display WHERE or AND */)
258 {
259 if (!acls[type]) {
260 return "";
261 }
262 strcpy(acls[type], where?" WHERE ":" AND ");
263 acls[type][7] = ' ' ; /* replace \0 by ' ' */
264 return acls[type];
265 }
266
267 /* Keep UAContext ACLs in our structure for further SQL queries */
set_acl(JCR * jcr,DB_ACL_t type,alist * list,alist * list2)268 void BDB::set_acl(JCR *jcr, DB_ACL_t type, alist *list, alist *list2)
269 {
270 const char *key=NULL;
271 const char *keyid=NULL;
272
273 /* If the list is present, but we authorize everything */
274 if (list && list->size() == 1 && strcasecmp((char*)list->get(0), "*all*") == 0) {
275 return;
276 }
277
278 /* If the list is present, but we authorize everything */
279 if (list2 && list2->size() == 1 && strcasecmp((char*)list2->get(0), "*all*") == 0) {
280 return;
281 }
282
283 POOLMEM *tmp = get_pool_memory(PM_FNAME);
284 POOLMEM *where = get_pool_memory(PM_FNAME);
285
286 *where = 0;
287 *tmp = 0;
288
289 switch(type) {
290 case DB_ACL_JOB:
291 key = "Job.Name";
292 break;
293
294 case DB_ACL_BCLIENT:
295 case DB_ACL_CLIENT:
296 case DB_ACL_RCLIENT:
297 key = "Client.Name";
298 break;
299
300 case DB_ACL_FILESET:
301 key = "FileSet.FileSet";
302 keyid = "FileSet.FileSetId";
303 break;
304
305 case DB_ACL_POOL:
306 key = "Pool.Name";
307 keyid = "Pool.PoolId";
308 break;
309
310 default:
311 break;
312 }
313
314 /* For clients, we can have up to 2 lists */
315 char *elt;
316 alist *merged_list = New(alist(5, not_owned_by_alist));
317 if (list) {
318 foreach_alist(elt, list) {
319 merged_list->append(elt);
320 }
321 }
322 if (list2) {
323 foreach_alist(elt, list2) {
324 merged_list->append(elt);
325 }
326 }
327 escape_acl_list(jcr, key, &tmp, merged_list);
328 delete merged_list;
329
330 if (keyid) {
331 Mmsg(where, " AND (%s IS NULL OR %s) ", keyid, tmp);
332 } else {
333 Mmsg(where, " AND %s ", tmp);
334 }
335
336 acls[type] = where;
337 Dmsg1(50|DT_SQL, "%s\n", where);
338 free_pool_memory(tmp);
339 }
340
341 /* Convert a ACL list to a SQL IN() / regexp list
342 * key=Client.Name + (test1, test2)
343 * => (Client.Name IN ('test1', 'test2'))
344 * key=Client.Name + (test1, test2*)
345 * => (Client.Name IN ('test1') OR (Client.Name ~ 'test2.*'))
346 */
escape_acl_list(JCR * jcr,const char * key,POOLMEM ** escaped_list,alist * lst)347 char *BDB::escape_acl_list(JCR *jcr, const char *key, POOLMEM **escaped_list, alist *lst)
348 {
349 char *elt, *p, *dst;
350 int len;
351 bool have_in=false, have_reg=false;
352 POOL_MEM tmp, tmp2, reg, in;
353
354 if (lst==NULL || lst->size() == 0) {
355 Mmsg(tmp, "(%s IN (''))", key);
356 pm_strcat(escaped_list, tmp.c_str());
357 return *escaped_list;
358 }
359
360 foreach_alist(elt, lst) {
361 if (elt && *elt) {
362 len = strlen(elt);
363 /* Escape + ' ' */
364 tmp.check_size(4 * len + 2 + 2);
365 tmp2.check_size(4 * len + 2 + 2);
366
367 if (strchr(elt, '*') != NULL || strchr(elt, '[') != NULL) {
368 /* Replace all * by .* */
369 dst = tmp2.c_str();
370 for (p = elt; *p ; p++) {
371 if (*p == '*') {
372 *dst++ = '.';
373 *dst = '*';
374 /* Escape regular */
375 } else if (*p == '.' || *p == '$' || *p == '^' || *p == '+' || *p == '(' || *p == ')' || *p == '|') {
376 *dst++ = '\\';
377 *dst = *p;
378
379 } else {
380 *dst = *p;
381 }
382 dst++;
383 }
384 *dst = '\0';
385
386 /* Escape the expression, the result is now in "tmp" */
387 bdb_lock();
388 bdb_escape_string(jcr, tmp.c_str(), tmp2.c_str(), strlen(tmp2.c_str()));
389 bdb_unlock();
390
391 /* Build the SQL part */
392 Mmsg(tmp2, "(%s %s '%s')", key, regexp_value[bdb_get_type_index()], tmp.c_str());
393
394 /* Append the expression to the existing one if any */
395 if (have_reg) {
396 pm_strcat(reg, " OR ");
397 }
398 pm_strcat(reg, tmp2.c_str());
399 have_reg = true;
400
401 } else {
402
403 /* Escape the string between '' */
404 pm_strcpy(tmp, "'");
405
406 bdb_lock();
407 bdb_escape_string(jcr, tmp.c_str() + 1 , elt, len);
408 bdb_unlock();
409
410 pm_strcat(tmp, "'");
411 if (have_in) {
412 pm_strcat(in, ",");
413 }
414 pm_strcat(in, tmp.c_str());
415 have_in = true;
416 }
417 }
418 }
419 /* Check if we have */
420 pm_strcat(escaped_list, "(");
421 if (have_in) {
422 Mmsg(tmp, "%s IN (%s)", key, in.c_str());
423 pm_strcat(escaped_list, tmp.c_str());
424 }
425 if (have_reg) {
426 if (have_in) {
427 pm_strcat(escaped_list, " OR ");
428 }
429 pm_strcat(escaped_list, reg.c_str());
430 }
431 pm_strcat(escaped_list, ")");
432 return *escaped_list;
433 }
434
435 /*
436 * Check catalog max_connections setting
437 */
bdb_check_max_connections(JCR * jcr,uint32_t max_concurrent_jobs)438 bool BDB::bdb_check_max_connections(JCR *jcr, uint32_t max_concurrent_jobs)
439 {
440 struct max_connections_context context;
441
442 /* Without Batch insert, no need to verify max_connections */
443 if (!batch_insert_available())
444 return true;
445
446 context.db = this;
447 context.nr_connections = 0;
448
449 /* Check max_connections setting */
450 if (!bdb_sql_query(sql_get_max_connections[bdb_get_type_index()],
451 db_max_connections_handler, &context)) {
452 Jmsg(jcr, M_ERROR, 0, "Can't verify max_connections settings %s", errmsg);
453 return false;
454 }
455 if (context.nr_connections && max_concurrent_jobs && max_concurrent_jobs > context.nr_connections) {
456 Mmsg(errmsg,
457 _("Potential performance problem:\n"
458 "max_connections=%d set for %s database \"%s\" should be larger than Director's "
459 "MaxConcurrentJobs=%d\n"),
460 context.nr_connections, bdb_get_engine_name(), get_db_name(), max_concurrent_jobs);
461 Jmsg(jcr, M_WARNING, 0, "%s", errmsg);
462 return false;
463 }
464
465 return true;
466 }
467
468 /* NOTE!!! The following routines expect that the
469 * calling subroutine sets and clears the mutex
470 */
471
472 /* Check that the tables correspond to the version we want */
bdb_check_version(JCR * jcr)473 bool BDB::bdb_check_version(JCR *jcr)
474 {
475 uint32_t bacula_db_version = 0;
476 const char *query = "SELECT VersionId FROM Version";
477
478 bacula_db_version = 0;
479 if (!bdb_sql_query(query, db_int_handler, (void *)&bacula_db_version)) {
480 Jmsg(jcr, M_FATAL, 0, "%s", errmsg);
481 return false;
482 }
483 if (bacula_db_version != BDB_VERSION) {
484 Mmsg(errmsg, "Version error for database \"%s\". Wanted %d, got %d\n",
485 get_db_name(), BDB_VERSION, bacula_db_version);
486 Jmsg(jcr, M_FATAL, 0, "%s", errmsg);
487 return false;
488 }
489 return true;
490 }
491
492 /*
493 * Utility routine for queries. The database MUST be locked before calling here.
494 * Returns: 0 on failure
495 * 1 on success
496 */
QueryDB(JCR * jcr,char * cmd,const char * file,int line)497 bool BDB::QueryDB(JCR *jcr, char *cmd, const char *file, int line)
498 {
499 sql_free_result();
500 if (!sql_query(cmd, QF_STORE_RESULT)) {
501 m_msg(file, line, &errmsg, _("query %s failed:\n%s\n"), cmd, sql_strerror());
502 if (use_fatal_jmsg()) {
503 j_msg(file, line, jcr, M_FATAL, 0, "%s", errmsg);
504 }
505 if (verbose) {
506 j_msg(file, line, jcr, M_INFO, 0, "%s\n", cmd);
507 }
508 return false;
509 }
510
511 return true;
512 }
513
514 /*
515 * Utility routine to do inserts
516 * Returns: 0 on failure
517 * 1 on success
518 */
InsertDB(JCR * jcr,char * cmd,const char * file,int line)519 bool BDB::InsertDB(JCR *jcr, char *cmd, const char *file, int line)
520 {
521 if (!sql_query(cmd)) {
522 m_msg(file, line, &errmsg, _("insert %s failed:\n%s\n"), cmd, sql_strerror());
523 if (use_fatal_jmsg()) {
524 j_msg(file, line, jcr, M_FATAL, 0, "%s", errmsg);
525 }
526 if (verbose) {
527 j_msg(file, line, jcr, M_INFO, 0, "%s\n", cmd);
528 }
529 return false;
530 }
531 int num_rows = sql_affected_rows();
532 if (num_rows != 1) {
533 char ed1[30];
534 m_msg(file, line, &errmsg, _("Insertion problem: affected_rows=%s\n"),
535 edit_uint64(num_rows, ed1));
536 if (verbose) {
537 j_msg(file, line, jcr, M_INFO, 0, "%s\n", cmd);
538 }
539 return false;
540 }
541 changes++;
542 return true;
543 }
544
545 /* Utility routine for updates.
546 * Returns: false on failure
547 * true on success
548 *
549 * Some UPDATE queries must update record(s), other queries might not update
550 * anything.
551 */
UpdateDB(JCR * jcr,char * cmd,bool can_be_empty,const char * file,int line)552 bool BDB::UpdateDB(JCR *jcr, char *cmd, bool can_be_empty,
553 const char *file, int line)
554 {
555 if (!sql_query(cmd)) {
556 m_msg(file, line, &errmsg, _("update %s failed:\n%s\n"), cmd, sql_strerror());
557 j_msg(file, line, jcr, M_ERROR, 0, "%s", errmsg);
558 if (verbose) {
559 j_msg(file, line, jcr, M_INFO, 0, "%s\n", cmd);
560 }
561 return false;
562 }
563 int num_rows = sql_affected_rows();
564 if ((num_rows == 0 && !can_be_empty) || num_rows < 0) {
565 char ed1[30];
566 m_msg(file, line, &errmsg, _("Update failed: affected_rows=%s for %s\n"),
567 edit_uint64(num_rows, ed1), cmd);
568 if (verbose) {
569 // j_msg(file, line, jcr, M_INFO, 0, "%s\n", cmd);
570 }
571 return false;
572 }
573 changes++;
574 return true;
575 }
576
577 /* Utility routine for deletes
578 *
579 * Returns: -1 on error
580 * n number of rows affected
581 */
DeleteDB(JCR * jcr,char * cmd,const char * file,int line)582 int BDB::DeleteDB(JCR *jcr, char *cmd, const char *file, int line)
583 {
584
585 if (!sql_query(cmd)) {
586 m_msg(file, line, &errmsg, _("delete %s failed:\n%s\n"), cmd, sql_strerror());
587 j_msg(file, line, jcr, M_ERROR, 0, "%s", errmsg);
588 if (verbose) {
589 j_msg(file, line, jcr, M_INFO, 0, "%s\n", cmd);
590 }
591 return -1;
592 }
593 changes++;
594 return sql_affected_rows();
595 }
596
597
598 /*
599 * Get record max. Query is already in mdb->cmd
600 * No locking done
601 *
602 * Returns: -1 on failure
603 * count on success
604 */
get_sql_record_max(JCR * jcr,BDB * mdb)605 int get_sql_record_max(JCR *jcr, BDB *mdb)
606 {
607 SQL_ROW row;
608 int stat = 0;
609
610 if (mdb->QueryDB(jcr, mdb->cmd)) {
611 if ((row = mdb->sql_fetch_row()) == NULL) {
612 Mmsg1(&mdb->errmsg, _("error fetching row: %s\n"), mdb->sql_strerror());
613 stat = -1;
614 } else {
615 stat = str_to_int64(row[0]);
616 }
617 mdb->sql_free_result();
618 } else {
619 Mmsg1(&mdb->errmsg, _("error fetching row: %s\n"), mdb->sql_strerror());
620 stat = -1;
621 }
622 return stat;
623 }
624
625 /*
626 * Given a full filename, split it into its path
627 * and filename parts. They are returned in pool memory
628 * in the mdb structure.
629 */
split_path_and_file(JCR * jcr,BDB * mdb,const char * afname)630 void split_path_and_file(JCR *jcr, BDB *mdb, const char *afname)
631 {
632 const char *p, *f;
633
634 /* Find path without the filename.
635 * I.e. everything after the last / is a "filename".
636 * OK, maybe it is a directory name, but we treat it like
637 * a filename. If we don't find a / then the whole name
638 * must be a path name (e.g. c:).
639 */
640 for (p=f=afname; *p; p++) {
641 if (IsPathSeparator(*p)) {
642 f = p; /* set pos of last slash */
643 }
644 }
645 if (IsPathSeparator(*f)) { /* did we find a slash? */
646 f++; /* yes, point to filename */
647 } else { /* no, whole thing must be path name */
648 f = p;
649 }
650
651 /* If filename doesn't exist (i.e. root directory), we
652 * simply create a blank name consisting of a single
653 * space. This makes handling zero length filenames
654 * easier.
655 */
656 mdb->fnl = p - f;
657 if (mdb->fnl > 0) {
658 mdb->fname = check_pool_memory_size(mdb->fname, mdb->fnl+1);
659 memcpy(mdb->fname, f, mdb->fnl); /* copy filename */
660 mdb->fname[mdb->fnl] = 0;
661 } else {
662 mdb->fname[0] = 0;
663 mdb->fnl = 0;
664 }
665
666 mdb->pnl = f - afname;
667 if (mdb->pnl > 0) {
668 mdb->path = check_pool_memory_size(mdb->path, mdb->pnl+1);
669 memcpy(mdb->path, afname, mdb->pnl);
670 mdb->path[mdb->pnl] = 0;
671 } else {
672 Mmsg1(&mdb->errmsg, _("Path length is zero. File=%s\n"), afname);
673 Jmsg(jcr, M_FATAL, 0, "%s", mdb->errmsg);
674 mdb->path[0] = 0;
675 mdb->pnl = 0;
676 }
677
678 Dmsg3(500, "split fname=%s: path=%s file=%s\n", afname, mdb->path, mdb->fname);
679 }
680
681 /*
682 * Set maximum field length to something reasonable
683 */
max_length(int max_length)684 static int max_length(int max_length)
685 {
686 int max_len = max_length;
687 /* Sanity check */
688 if (max_len < 0) {
689 max_len = 2;
690 } else if (max_len > 100) {
691 max_len = 100;
692 }
693 return max_len;
694 }
695
696 /*
697 * List dashes as part of header for listing SQL results in a table
698 */
699 void
list_dashes(BDB * mdb,DB_LIST_HANDLER * send,void * ctx)700 list_dashes(BDB *mdb, DB_LIST_HANDLER *send, void *ctx)
701 {
702 SQL_FIELD *field;
703 int i, j;
704 int len;
705
706 mdb->sql_field_seek(0);
707 send(ctx, "+");
708 for (i = 0; i < mdb->sql_num_fields(); i++) {
709 field = mdb->sql_fetch_field();
710 if (!field) {
711 break;
712 }
713 len = max_length(field->max_length + 2);
714 for (j = 0; j < len; j++) {
715 send(ctx, "-");
716 }
717 send(ctx, "+");
718 }
719 send(ctx, "\n");
720 }
721
722 /* Small handler to print the last line of a list xxx command */
last_line_handler(void * vctx,const char * str)723 static void last_line_handler(void *vctx, const char *str)
724 {
725 LIST_CTX *ctx = (LIST_CTX *)vctx;
726 bstrncat(ctx->line, str, sizeof(ctx->line));
727 }
728
list_result(void * vctx,int nb_col,char ** row)729 int list_result(void *vctx, int nb_col, char **row)
730 {
731 SQL_FIELD *field;
732 int i, col_len, max_len = 0;
733 char buf[2000], ewc[30];
734
735 LIST_CTX *pctx = (LIST_CTX *)vctx;
736 DB_LIST_HANDLER *send = pctx->send;
737 e_list_type type = pctx->type;
738 BDB *mdb = pctx->mdb;
739 void *ctx = pctx->ctx;
740 JCR *jcr = pctx->jcr;
741
742 if (!pctx->once) {
743 pctx->once = true;
744
745 Dmsg1(800, "list_result starts looking at %d fields\n", mdb->sql_num_fields());
746 /* determine column display widths */
747 mdb->sql_field_seek(0);
748 for (i = 0; i < mdb->sql_num_fields(); i++) {
749 Dmsg1(800, "list_result processing field %d\n", i);
750 field = mdb->sql_fetch_field();
751 if (!field) {
752 break;
753 }
754 col_len = cstrlen(field->name);
755 if (type == VERT_LIST) {
756 if (col_len > max_len) {
757 max_len = col_len;
758 }
759 } else {
760 if (mdb->sql_field_is_numeric(field->type) && (int)field->max_length > 0) { /* fixup for commas */
761 field->max_length += (field->max_length - 1) / 3;
762 }
763 if (col_len < (int)field->max_length) {
764 col_len = field->max_length;
765 }
766 if (col_len < 4 && !mdb->sql_field_is_not_null(field->flags)) {
767 col_len = 4; /* 4 = length of the word "NULL" */
768 }
769 field->max_length = col_len; /* reset column info */
770 }
771 }
772
773 pctx->num_rows++;
774
775 Dmsg0(800, "list_result finished first loop\n");
776 if (type == VERT_LIST) {
777 goto vertical_list;
778 }
779 if (type == ARG_LIST) {
780 goto arg_list;
781 }
782
783 Dmsg1(800, "list_result starts second loop looking at %d fields\n",
784 mdb->sql_num_fields());
785
786 /* Keep the result to display the same line at the end of the table */
787 list_dashes(mdb, last_line_handler, pctx);
788 send(ctx, pctx->line);
789
790 send(ctx, "|");
791 mdb->sql_field_seek(0);
792 for (i = 0; i < mdb->sql_num_fields(); i++) {
793 Dmsg1(800, "list_result looking at field %d\n", i);
794 field = mdb->sql_fetch_field();
795 if (!field) {
796 break;
797 }
798 max_len = max_length(field->max_length);
799 bsnprintf(buf, sizeof(buf), " %-*s |", max_len, field->name);
800 send(ctx, buf);
801 }
802 send(ctx, "\n");
803 list_dashes(mdb, send, ctx);
804 }
805 Dmsg1(800, "list_result starts third loop looking at %d fields\n",
806 mdb->sql_num_fields());
807 mdb->sql_field_seek(0);
808 send(ctx, "|");
809 for (i = 0; i < mdb->sql_num_fields(); i++) {
810 field = mdb->sql_fetch_field();
811 if (!field) {
812 break;
813 }
814 max_len = max_length(field->max_length);
815 if (row[i] == NULL) {
816 bsnprintf(buf, sizeof(buf), " %-*s |", max_len, "NULL");
817 } else if (mdb->sql_field_is_numeric(field->type) && !jcr->gui && is_an_integer(row[i])) {
818 bsnprintf(buf, sizeof(buf), " %*s |", max_len,
819 add_commas(row[i], ewc));
820 } else {
821 bsnprintf(buf, sizeof(buf), " %-*s |", max_len, row[i]);
822 }
823 send(ctx, buf);
824 }
825 send(ctx, "\n");
826 return 0;
827
828 vertical_list:
829
830 Dmsg1(800, "list_result starts vertical list at %d fields\n", mdb->sql_num_fields());
831 mdb->sql_field_seek(0);
832 for (i = 0; i < mdb->sql_num_fields(); i++) {
833 field = mdb->sql_fetch_field();
834 if (!field) {
835 break;
836 }
837 if (row[i] == NULL) {
838 bsnprintf(buf, sizeof(buf), " %*s: %s\n", max_len, field->name, "NULL");
839 } else if (mdb->sql_field_is_numeric(field->type) && !jcr->gui && is_an_integer(row[i])) {
840 bsnprintf(buf, sizeof(buf), " %*s: %s\n", max_len, field->name,
841 add_commas(row[i], ewc));
842 } else {
843 bsnprintf(buf, sizeof(buf), " %*s: %s\n", max_len, field->name, row[i]);
844 }
845 send(ctx, buf);
846 }
847 send(ctx, "\n");
848 return 0;
849
850 arg_list:
851 Dmsg1(800, "list_result starts simple list at %d fields\n", mdb->sql_num_fields());
852 mdb->sql_field_seek(0);
853 for (i = 0; i < mdb->sql_num_fields(); i++) {
854 field = mdb->sql_fetch_field();
855 if (!field) {
856 break;
857 }
858 if (row[i] == NULL) {
859 bsnprintf(buf, sizeof(buf), "%s%s=", (i>0?" ":""), field->name);
860 } else {
861 bash_spaces(row[i]);
862 bsnprintf(buf, sizeof(buf), "%s%s=%s ", (i>0?" ":""), field->name, row[i]);
863 }
864 send(ctx, buf);
865 }
866 send(ctx, "\n");
867 return 0;
868
869 }
870
871 /*
872 * If full_list is set, we list vertically, otherwise, we
873 * list on one line horizontally.
874 * Return number of rows
875 */
876 int
list_result(JCR * jcr,BDB * mdb,DB_LIST_HANDLER * send,void * ctx,e_list_type type)877 list_result(JCR *jcr, BDB *mdb, DB_LIST_HANDLER *send, void *ctx, e_list_type type)
878 {
879 SQL_FIELD *field;
880 SQL_ROW row;
881 int i, col_len, max_len = 0;
882 char buf[2000], ewc[30];
883
884 Dmsg0(800, "list_result starts\n");
885 if (mdb->sql_num_rows() == 0) {
886 send(ctx, _("No results to list.\n"));
887 return mdb->sql_num_rows();
888 }
889
890 Dmsg1(800, "list_result starts looking at %d fields\n", mdb->sql_num_fields());
891 /* determine column display widths */
892 mdb->sql_field_seek(0);
893 for (i = 0; i < mdb->sql_num_fields(); i++) {
894 Dmsg1(800, "list_result processing field %d\n", i);
895 field = mdb->sql_fetch_field();
896 if (!field) {
897 break;
898 }
899 col_len = cstrlen(field->name);
900 if (type == VERT_LIST) {
901 if (col_len > max_len) {
902 max_len = col_len;
903 }
904 } else {
905 if (mdb->sql_field_is_numeric(field->type) && (int)field->max_length > 0) { /* fixup for commas */
906 field->max_length += (field->max_length - 1) / 3;
907 }
908 if (col_len < (int)field->max_length) {
909 col_len = field->max_length;
910 }
911 if (col_len < 4 && !mdb->sql_field_is_not_null(field->flags)) {
912 col_len = 4; /* 4 = length of the word "NULL" */
913 }
914 field->max_length = col_len; /* reset column info */
915 }
916 }
917
918 Dmsg0(800, "list_result finished first loop\n");
919 if (type == VERT_LIST) {
920 goto vertical_list;
921 }
922 if (type == ARG_LIST) {
923 goto arg_list;
924 }
925
926 Dmsg1(800, "list_result starts second loop looking at %d fields\n", mdb->sql_num_fields());
927 list_dashes(mdb, send, ctx);
928 send(ctx, "|");
929 mdb->sql_field_seek(0);
930 for (i = 0; i < mdb->sql_num_fields(); i++) {
931 Dmsg1(800, "list_result looking at field %d\n", i);
932 field = mdb->sql_fetch_field();
933 if (!field) {
934 break;
935 }
936 max_len = max_length(field->max_length);
937 bsnprintf(buf, sizeof(buf), " %-*s |", max_len, field->name);
938 send(ctx, buf);
939 }
940 send(ctx, "\n");
941 list_dashes(mdb, send, ctx);
942
943 Dmsg1(800, "list_result starts third loop looking at %d fields\n", mdb->sql_num_fields());
944 while ((row = mdb->sql_fetch_row()) != NULL) {
945 mdb->sql_field_seek(0);
946 send(ctx, "|");
947 for (i = 0; i < mdb->sql_num_fields(); i++) {
948 field = mdb->sql_fetch_field();
949 if (!field) {
950 break;
951 }
952 max_len = max_length(field->max_length);
953 if (row[i] == NULL) {
954 bsnprintf(buf, sizeof(buf), " %-*s |", max_len, "NULL");
955 } else if (mdb->sql_field_is_numeric(field->type) && !jcr->gui && is_an_integer(row[i])) {
956 bsnprintf(buf, sizeof(buf), " %*s |", max_len,
957 add_commas(row[i], ewc));
958 } else {
959 strip_trailing_junk(row[i]);
960 bsnprintf(buf, sizeof(buf), " %-*s |", max_len, row[i]);
961 }
962 send(ctx, buf);
963 }
964 send(ctx, "\n");
965 }
966 list_dashes(mdb, send, ctx);
967 return mdb->sql_num_rows();
968
969 vertical_list:
970
971 Dmsg1(800, "list_result starts vertical list at %d fields\n", mdb->sql_num_fields());
972 while ((row = mdb->sql_fetch_row()) != NULL) {
973 mdb->sql_field_seek(0);
974 for (i = 0; i < mdb->sql_num_fields(); i++) {
975 field = mdb->sql_fetch_field();
976 if (!field) {
977 break;
978 }
979 if (row[i] == NULL) {
980 bsnprintf(buf, sizeof(buf), " %*s: %s\n", max_len, field->name, "NULL");
981 } else if (mdb->sql_field_is_numeric(field->type) && !jcr->gui && is_an_integer(row[i])) {
982 bsnprintf(buf, sizeof(buf), " %*s: %s\n", max_len, field->name,
983 add_commas(row[i], ewc));
984 } else {
985 strip_trailing_junk(row[i]);
986 bsnprintf(buf, sizeof(buf), " %*s: %s\n", max_len, field->name, row[i]);
987 }
988 send(ctx, buf);
989 }
990 send(ctx, "\n");
991 }
992
993 arg_list:
994
995 Dmsg1(800, "list_result starts arg list at %d fields\n", mdb->sql_num_fields());
996 while ((row = mdb->sql_fetch_row()) != NULL) {
997 mdb->sql_field_seek(0);
998 for (i = 0; i < mdb->sql_num_fields(); i++) {
999 field = mdb->sql_fetch_field();
1000 if (!field) {
1001 break;
1002 }
1003 if (row[i] == NULL) {
1004 bsnprintf(buf, sizeof(buf), "%s%s=", (i>0?" ":""), field->name);
1005 } else {
1006 bash_spaces(row[i]);
1007 bsnprintf(buf, sizeof(buf), "%s%s=%s", (i>0?" ":""), field->name, row[i]);
1008 }
1009 send(ctx, buf);
1010 }
1011 send(ctx, "\n");
1012 }
1013 return mdb->sql_num_rows();
1014 }
1015
1016 /*
1017 * Open a new connexion to mdb catalog. This function is used
1018 * by batch and accurate mode.
1019 */
bdb_open_batch_connexion(JCR * jcr)1020 bool BDB::bdb_open_batch_connexion(JCR *jcr)
1021 {
1022 bool multi_db;
1023
1024 multi_db = batch_insert_available();
1025
1026 if (!jcr->db_batch) {
1027 jcr->db_batch = bdb_clone_database_connection(jcr, multi_db);
1028 if (!jcr->db_batch) {
1029 Mmsg0(&errmsg, _("Could not init database batch connection\n"));
1030 Jmsg(jcr, M_FATAL, 0, "%s", errmsg);
1031 return false;
1032 }
1033
1034 if (!jcr->db_batch->bdb_open_database(jcr)) {
1035 Mmsg2(&errmsg, _("Could not open database \"%s\": ERR=%s\n"),
1036 jcr->db_batch->get_db_name(), jcr->db_batch->bdb_strerror());
1037 Jmsg(jcr, M_FATAL, 0, "%s", errmsg);
1038 return false;
1039 }
1040 }
1041 return true;
1042 }
1043
1044 /*
1045 * !!! WARNING !!! Use this function only when bacula is stopped.
1046 * ie, after a fatal signal and before exiting the program
1047 * Print information about a BDB object.
1048 */
bdb_debug_print(JCR * jcr,FILE * fp)1049 void bdb_debug_print(JCR *jcr, FILE *fp)
1050 {
1051 BDB *mdb = jcr->db;
1052
1053 if (!mdb) {
1054 return;
1055 }
1056
1057 fprintf(fp, "BDB=%p db_name=%s db_user=%s connected=%s\n",
1058 mdb, NPRTB(mdb->get_db_name()), NPRTB(mdb->get_db_user()), mdb->is_connected() ? "true" : "false");
1059 fprintf(fp, "\tcmd=\"%s\" changes=%i\n", NPRTB(mdb->cmd), mdb->changes);
1060 mdb->print_lock_info(fp);
1061 }
1062
1063 #ifdef COMMUNITY
bdb_check_settings(JCR * jcr,int64_t * starttime,int val,int64_t val2)1064 bool BDB::bdb_check_settings(JCR *jcr, int64_t *starttime, int val, int64_t val2)
1065 {
1066 /* Implement checks for tuning hints */
1067 return true;
1068 }
1069 #endif
1070
1071 #endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL */
1072