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