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    /* If the list is present, but we authorize everything */
271    if (list && list->size() == 1 && strcasecmp((char*)list->get(0), "*all*") == 0) {
272       return;
273    }
274 
275    /* If the list is present, but we authorize everything */
276    if (list2 && list2->size() == 1 && strcasecmp((char*)list2->get(0), "*all*") == 0) {
277       return;
278    }
279 
280    POOLMEM *tmp = get_pool_memory(PM_FNAME);
281    POOLMEM *where = get_pool_memory(PM_FNAME);
282 
283    *where = 0;
284    *tmp = 0;
285 
286    /* For clients, we can have up to 2 lists */
287    escape_acl_list(jcr, &tmp, list);
288    escape_acl_list(jcr, &tmp, list2);
289 
290    switch(type) {
291    case DB_ACL_JOB:
292       Mmsg(where, "   AND  Job.Name IN (%s) ", tmp);
293       break;
294    case DB_ACL_CLIENT:
295       Mmsg(where, "   AND  Client.Name IN (%s) ", tmp);
296       break;
297    case DB_ACL_BCLIENT:
298       Mmsg(where, "   AND  Client.Name IN (%s) ", tmp);
299       break;
300    case DB_ACL_RCLIENT:
301       Mmsg(where, "   AND  Client.Name IN (%s) ", tmp);
302       break;
303    case  DB_ACL_FILESET:
304       Mmsg(where, "   AND  (FileSetId = 0 OR FileSet.FileSet IN (%s)) ", tmp);
305       break;
306    case DB_ACL_POOL:
307       Mmsg(where, "   AND  (PoolId = 0 OR Pool.Name IN (%s)) ", tmp);
308       break;
309    default:
310       break;
311    }
312    acls[type] = where;
313    free_pool_memory(tmp);
314 }
315 
316 /* Convert a ACL list to a SQL IN() list */
escape_acl_list(JCR * jcr,POOLMEM ** escaped_list,alist * lst)317 char *BDB::escape_acl_list(JCR *jcr, POOLMEM **escaped_list, alist *lst)
318 {
319    char *elt;
320    int len;
321    POOL_MEM tmp;
322 
323    if (!lst) {
324       return *escaped_list;     /* TODO: check how we handle the empty list */
325 
326    /* List is empty, reject everything */
327    } else if (lst->size() == 0) {
328       Mmsg(escaped_list, "''");
329       return *escaped_list;
330    }
331 
332    foreach_alist(elt, lst) {
333       if (elt && *elt) {
334          len = strlen(elt);
335          /* Escape + ' ' */
336          tmp.check_size(2 * len + 2 + 2);
337 
338          pm_strcpy(tmp, "'");
339          bdb_lock();
340          bdb_escape_string(jcr, tmp.c_str() + 1 , elt, len);
341          bdb_unlock();
342          pm_strcat(tmp, "'");
343 
344          if (*escaped_list[0]) {
345             pm_strcat(escaped_list, ",");
346          }
347 
348          pm_strcat(escaped_list, tmp.c_str());
349       }
350    }
351    return *escaped_list;
352 }
353 
354 /*
355  * Check catalog max_connections setting
356  */
bdb_check_max_connections(JCR * jcr,uint32_t max_concurrent_jobs)357 bool BDB::bdb_check_max_connections(JCR *jcr, uint32_t max_concurrent_jobs)
358 {
359    struct max_connections_context context;
360 
361    /* Without Batch insert, no need to verify max_connections */
362    if (!batch_insert_available())
363       return true;
364 
365    context.db = this;
366    context.nr_connections = 0;
367 
368    /* Check max_connections setting */
369    if (!bdb_sql_query(sql_get_max_connections[bdb_get_type_index()],
370                      db_max_connections_handler, &context)) {
371       Jmsg(jcr, M_ERROR, 0, "Can't verify max_connections settings %s", errmsg);
372       return false;
373    }
374    if (context.nr_connections && max_concurrent_jobs && max_concurrent_jobs > context.nr_connections) {
375       Mmsg(errmsg,
376            _("Potential performance problem:\n"
377              "max_connections=%d set for %s database \"%s\" should be larger than Director's "
378              "MaxConcurrentJobs=%d\n"),
379            context.nr_connections, bdb_get_engine_name(), get_db_name(), max_concurrent_jobs);
380       Jmsg(jcr, M_WARNING, 0, "%s", errmsg);
381       return false;
382    }
383 
384    return true;
385 }
386 
387 /* NOTE!!! The following routines expect that the
388  *  calling subroutine sets and clears the mutex
389  */
390 
391 /* Check that the tables correspond to the version we want */
bdb_check_version(JCR * jcr)392 bool BDB::bdb_check_version(JCR *jcr)
393 {
394    uint32_t bacula_db_version = 0;
395    const char *query = "SELECT VersionId FROM Version";
396 
397    bacula_db_version = 0;
398    if (!bdb_sql_query(query, db_int_handler, (void *)&bacula_db_version)) {
399       Jmsg(jcr, M_FATAL, 0, "%s", errmsg);
400       return false;
401    }
402    if (bacula_db_version != BDB_VERSION) {
403       Mmsg(errmsg, "Version error for database \"%s\". Wanted %d, got %d\n",
404           get_db_name(), BDB_VERSION, bacula_db_version);
405       Jmsg(jcr, M_FATAL, 0, "%s", errmsg);
406       return false;
407    }
408    return true;
409 }
410 
411 /*
412  * Utility routine for queries. The database MUST be locked before calling here.
413  * Returns: 0 on failure
414  *          1 on success
415  */
QueryDB(JCR * jcr,char * cmd,const char * file,int line)416 bool BDB::QueryDB(JCR *jcr, char *cmd, const char *file, int line)
417 {
418    sql_free_result();
419    if (!sql_query(cmd, QF_STORE_RESULT)) {
420       m_msg(file, line, &errmsg, _("query %s failed:\n%s\n"), cmd, sql_strerror());
421       if (use_fatal_jmsg()) {
422          j_msg(file, line, jcr, M_FATAL, 0, "%s", errmsg);
423       }
424       if (verbose) {
425          j_msg(file, line, jcr, M_INFO, 0, "%s\n", cmd);
426       }
427       return false;
428    }
429 
430    return true;
431 }
432 
433 /*
434  * Utility routine to do inserts
435  * Returns: 0 on failure
436  *          1 on success
437  */
InsertDB(JCR * jcr,char * cmd,const char * file,int line)438 bool BDB::InsertDB(JCR *jcr, char *cmd, const char *file, int line)
439 {
440    if (!sql_query(cmd)) {
441       m_msg(file, line, &errmsg,  _("insert %s failed:\n%s\n"), cmd, sql_strerror());
442       if (use_fatal_jmsg()) {
443          j_msg(file, line, jcr, M_FATAL, 0, "%s", errmsg);
444       }
445       if (verbose) {
446          j_msg(file, line, jcr, M_INFO, 0, "%s\n", cmd);
447       }
448       return false;
449    }
450    int num_rows = sql_affected_rows();
451    if (num_rows != 1) {
452       char ed1[30];
453       m_msg(file, line, &errmsg, _("Insertion problem: affected_rows=%s\n"),
454          edit_uint64(num_rows, ed1));
455       if (verbose) {
456          j_msg(file, line, jcr, M_INFO, 0, "%s\n", cmd);
457       }
458       return false;
459    }
460    changes++;
461    return true;
462 }
463 
464 /* Utility routine for updates.
465  *  Returns: false on failure
466  *           true  on success
467  *
468  * Some UPDATE queries must update record(s), other queries might not update
469  * anything.
470  */
UpdateDB(JCR * jcr,char * cmd,bool can_be_empty,const char * file,int line)471 bool BDB::UpdateDB(JCR *jcr, char *cmd, bool can_be_empty,
472                    const char *file, int line)
473 {
474    if (!sql_query(cmd)) {
475       m_msg(file, line, &errmsg, _("update %s failed:\n%s\n"), cmd, sql_strerror());
476       j_msg(file, line, jcr, M_ERROR, 0, "%s", errmsg);
477       if (verbose) {
478          j_msg(file, line, jcr, M_INFO, 0, "%s\n", cmd);
479       }
480       return false;
481    }
482    int num_rows = sql_affected_rows();
483    if ((num_rows == 0 && !can_be_empty) || num_rows < 0) {
484       char ed1[30];
485       m_msg(file, line, &errmsg, _("Update failed: affected_rows=%s for %s\n"),
486          edit_uint64(num_rows, ed1), cmd);
487       if (verbose) {
488 //       j_msg(file, line, jcr, M_INFO, 0, "%s\n", cmd);
489       }
490       return false;
491    }
492    changes++;
493    return true;
494 }
495 
496 /* Utility routine for deletes
497  *
498  * Returns: -1 on error
499  *           n number of rows affected
500  */
DeleteDB(JCR * jcr,char * cmd,const char * file,int line)501 int BDB::DeleteDB(JCR *jcr, char *cmd, const char *file, int line)
502 {
503 
504    if (!sql_query(cmd)) {
505       m_msg(file, line, &errmsg, _("delete %s failed:\n%s\n"), cmd, sql_strerror());
506       j_msg(file, line, jcr, M_ERROR, 0, "%s", errmsg);
507       if (verbose) {
508          j_msg(file, line, jcr, M_INFO, 0, "%s\n", cmd);
509       }
510       return -1;
511    }
512    changes++;
513    return sql_affected_rows();
514 }
515 
516 
517 /*
518  * Get record max. Query is already in mdb->cmd
519  *  No locking done
520  *
521  * Returns: -1 on failure
522  *          count on success
523  */
get_sql_record_max(JCR * jcr,BDB * mdb)524 int get_sql_record_max(JCR *jcr, BDB *mdb)
525 {
526    SQL_ROW row;
527    int stat = 0;
528 
529    if (mdb->QueryDB(jcr, mdb->cmd)) {
530       if ((row = mdb->sql_fetch_row()) == NULL) {
531          Mmsg1(&mdb->errmsg, _("error fetching row: %s\n"), mdb->sql_strerror());
532          stat = -1;
533       } else {
534          stat = str_to_int64(row[0]);
535       }
536       mdb->sql_free_result();
537    } else {
538       Mmsg1(&mdb->errmsg, _("error fetching row: %s\n"), mdb->sql_strerror());
539       stat = -1;
540    }
541    return stat;
542 }
543 
544 /*
545  * Given a full filename, split it into its path
546  *  and filename parts. They are returned in pool memory
547  *  in the mdb structure.
548  */
split_path_and_file(JCR * jcr,BDB * mdb,const char * afname)549 void split_path_and_file(JCR *jcr, BDB *mdb, const char *afname)
550 {
551    const char *p, *f;
552 
553    /* Find path without the filename.
554     * I.e. everything after the last / is a "filename".
555     * OK, maybe it is a directory name, but we treat it like
556     * a filename. If we don't find a / then the whole name
557     * must be a path name (e.g. c:).
558     */
559    for (p=f=afname; *p; p++) {
560       if (IsPathSeparator(*p)) {
561          f = p;                       /* set pos of last slash */
562       }
563    }
564    if (IsPathSeparator(*f)) {                   /* did we find a slash? */
565       f++;                            /* yes, point to filename */
566    } else {                           /* no, whole thing must be path name */
567       f = p;
568    }
569 
570    /* If filename doesn't exist (i.e. root directory), we
571     * simply create a blank name consisting of a single
572     * space. This makes handling zero length filenames
573     * easier.
574     */
575    mdb->fnl = p - f;
576    if (mdb->fnl > 0) {
577       mdb->fname = check_pool_memory_size(mdb->fname, mdb->fnl+1);
578       memcpy(mdb->fname, f, mdb->fnl);    /* copy filename */
579       mdb->fname[mdb->fnl] = 0;
580    } else {
581       mdb->fname[0] = 0;
582       mdb->fnl = 0;
583    }
584 
585    mdb->pnl = f - afname;
586    if (mdb->pnl > 0) {
587       mdb->path = check_pool_memory_size(mdb->path, mdb->pnl+1);
588       memcpy(mdb->path, afname, mdb->pnl);
589       mdb->path[mdb->pnl] = 0;
590    } else {
591       Mmsg1(&mdb->errmsg, _("Path length is zero. File=%s\n"), afname);
592       Jmsg(jcr, M_FATAL, 0, "%s", mdb->errmsg);
593       mdb->path[0] = 0;
594       mdb->pnl = 0;
595    }
596 
597    Dmsg3(500, "split fname=%s: path=%s file=%s\n", afname, mdb->path, mdb->fname);
598 }
599 
600 /*
601  * Set maximum field length to something reasonable
602  */
max_length(int max_length)603 static int max_length(int max_length)
604 {
605    int max_len = max_length;
606    /* Sanity check */
607    if (max_len < 0) {
608       max_len = 2;
609    } else if (max_len > 100) {
610       max_len = 100;
611    }
612    return max_len;
613 }
614 
615 /*
616  * List dashes as part of header for listing SQL results in a table
617  */
618 void
list_dashes(BDB * mdb,DB_LIST_HANDLER * send,void * ctx)619 list_dashes(BDB *mdb, DB_LIST_HANDLER *send, void *ctx)
620 {
621    SQL_FIELD  *field;
622    int i, j;
623    int len;
624 
625    mdb->sql_field_seek(0);
626    send(ctx, "+");
627    for (i = 0; i < mdb->sql_num_fields(); i++) {
628       field = mdb->sql_fetch_field();
629       if (!field) {
630          break;
631       }
632       len = max_length(field->max_length + 2);
633       for (j = 0; j < len; j++) {
634          send(ctx, "-");
635       }
636       send(ctx, "+");
637    }
638    send(ctx, "\n");
639 }
640 
641 /* Small handler to print the last line of a list xxx command */
last_line_handler(void * vctx,const char * str)642 static void last_line_handler(void *vctx, const char *str)
643 {
644    LIST_CTX *ctx = (LIST_CTX *)vctx;
645    bstrncat(ctx->line, str, sizeof(ctx->line));
646 }
647 
list_result(void * vctx,int nb_col,char ** row)648 int list_result(void *vctx, int nb_col, char **row)
649 {
650    SQL_FIELD *field;
651    int i, col_len, max_len = 0;
652    char buf[2000], ewc[30];
653 
654    LIST_CTX *pctx = (LIST_CTX *)vctx;
655    DB_LIST_HANDLER *send = pctx->send;
656    e_list_type type = pctx->type;
657    BDB *mdb = pctx->mdb;
658    void *ctx = pctx->ctx;
659    JCR *jcr = pctx->jcr;
660 
661    if (!pctx->once) {
662       pctx->once = true;
663 
664       Dmsg1(800, "list_result starts looking at %d fields\n", mdb->sql_num_fields());
665       /* determine column display widths */
666       mdb->sql_field_seek(0);
667       for (i = 0; i < mdb->sql_num_fields(); i++) {
668          Dmsg1(800, "list_result processing field %d\n", i);
669          field = mdb->sql_fetch_field();
670          if (!field) {
671             break;
672          }
673          col_len = cstrlen(field->name);
674          if (type == VERT_LIST) {
675             if (col_len > max_len) {
676                max_len = col_len;
677             }
678          } else {
679             if (mdb->sql_field_is_numeric(field->type) && (int)field->max_length > 0) { /* fixup for commas */
680                field->max_length += (field->max_length - 1) / 3;
681             }
682             if (col_len < (int)field->max_length) {
683                col_len = field->max_length;
684             }
685             if (col_len < 4 && !mdb->sql_field_is_not_null(field->flags)) {
686                col_len = 4;                 /* 4 = length of the word "NULL" */
687             }
688             field->max_length = col_len;    /* reset column info */
689          }
690       }
691 
692       pctx->num_rows++;
693 
694       Dmsg0(800, "list_result finished first loop\n");
695       if (type == VERT_LIST) {
696          goto vertical_list;
697       }
698       if (type == ARG_LIST) {
699          goto arg_list;
700       }
701 
702       Dmsg1(800, "list_result starts second loop looking at %d fields\n",
703             mdb->sql_num_fields());
704 
705       /* Keep the result to display the same line at the end of the table */
706       list_dashes(mdb, last_line_handler, pctx);
707       send(ctx, pctx->line);
708 
709       send(ctx, "|");
710       mdb->sql_field_seek(0);
711       for (i = 0; i < mdb->sql_num_fields(); i++) {
712          Dmsg1(800, "list_result looking at field %d\n", i);
713          field = mdb->sql_fetch_field();
714          if (!field) {
715             break;
716          }
717          max_len = max_length(field->max_length);
718          bsnprintf(buf, sizeof(buf), " %-*s |", max_len, field->name);
719          send(ctx, buf);
720       }
721       send(ctx, "\n");
722       list_dashes(mdb, send, ctx);
723    }
724    Dmsg1(800, "list_result starts third loop looking at %d fields\n",
725          mdb->sql_num_fields());
726    mdb->sql_field_seek(0);
727    send(ctx, "|");
728    for (i = 0; i < mdb->sql_num_fields(); i++) {
729       field = mdb->sql_fetch_field();
730       if (!field) {
731          break;
732       }
733       max_len = max_length(field->max_length);
734       if (row[i] == NULL) {
735          bsnprintf(buf, sizeof(buf), " %-*s |", max_len, "NULL");
736       } else if (mdb->sql_field_is_numeric(field->type) && !jcr->gui && is_an_integer(row[i])) {
737          bsnprintf(buf, sizeof(buf), " %*s |", max_len,
738                    add_commas(row[i], ewc));
739       } else {
740          bsnprintf(buf, sizeof(buf), " %-*s |", max_len, row[i]);
741       }
742       send(ctx, buf);
743    }
744    send(ctx, "\n");
745    return 0;
746 
747 vertical_list:
748 
749    Dmsg1(800, "list_result starts vertical list at %d fields\n", mdb->sql_num_fields());
750    mdb->sql_field_seek(0);
751    for (i = 0; i < mdb->sql_num_fields(); i++) {
752       field = mdb->sql_fetch_field();
753       if (!field) {
754          break;
755       }
756       if (row[i] == NULL) {
757          bsnprintf(buf, sizeof(buf), " %*s: %s\n", max_len, field->name, "NULL");
758       } else if (mdb->sql_field_is_numeric(field->type) && !jcr->gui && is_an_integer(row[i])) {
759          bsnprintf(buf, sizeof(buf), " %*s: %s\n", max_len, field->name,
760                    add_commas(row[i], ewc));
761       } else {
762          bsnprintf(buf, sizeof(buf), " %*s: %s\n", max_len, field->name, row[i]);
763       }
764       send(ctx, buf);
765    }
766    send(ctx, "\n");
767    return 0;
768 
769 arg_list:
770    Dmsg1(800, "list_result starts simple list at %d fields\n", mdb->sql_num_fields());
771    mdb->sql_field_seek(0);
772    for (i = 0; i < mdb->sql_num_fields(); i++) {
773       field = mdb->sql_fetch_field();
774       if (!field) {
775          break;
776       }
777       if (row[i] == NULL) {
778          bsnprintf(buf, sizeof(buf), "%s%s=", (i>0?" ":""), field->name);
779       } else {
780          bash_spaces(row[i]);
781          bsnprintf(buf, sizeof(buf), "%s%s=%s ", (i>0?" ":""), field->name, row[i]);
782       }
783       send(ctx, buf);
784    }
785    send(ctx, "\n");
786    return 0;
787 
788 }
789 
790 /*
791  * If full_list is set, we list vertically, otherwise, we
792  *  list on one line horizontally.
793  * Return number of rows
794  */
795 int
list_result(JCR * jcr,BDB * mdb,DB_LIST_HANDLER * send,void * ctx,e_list_type type)796 list_result(JCR *jcr, BDB *mdb, DB_LIST_HANDLER *send, void *ctx, e_list_type type)
797 {
798    SQL_FIELD *field;
799    SQL_ROW row;
800    int i, col_len, max_len = 0;
801    char buf[2000], ewc[30];
802 
803    Dmsg0(800, "list_result starts\n");
804    if (mdb->sql_num_rows() == 0) {
805       send(ctx, _("No results to list.\n"));
806       return mdb->sql_num_rows();
807    }
808 
809    Dmsg1(800, "list_result starts looking at %d fields\n", mdb->sql_num_fields());
810    /* determine column display widths */
811    mdb->sql_field_seek(0);
812    for (i = 0; i < mdb->sql_num_fields(); i++) {
813       Dmsg1(800, "list_result processing field %d\n", i);
814       field = mdb->sql_fetch_field();
815       if (!field) {
816          break;
817       }
818       col_len = cstrlen(field->name);
819       if (type == VERT_LIST) {
820          if (col_len > max_len) {
821             max_len = col_len;
822          }
823       } else {
824          if (mdb->sql_field_is_numeric(field->type) && (int)field->max_length > 0) { /* fixup for commas */
825             field->max_length += (field->max_length - 1) / 3;
826          }
827          if (col_len < (int)field->max_length) {
828             col_len = field->max_length;
829          }
830          if (col_len < 4 && !mdb->sql_field_is_not_null(field->flags)) {
831             col_len = 4;                 /* 4 = length of the word "NULL" */
832          }
833          field->max_length = col_len;    /* reset column info */
834       }
835    }
836 
837    Dmsg0(800, "list_result finished first loop\n");
838    if (type == VERT_LIST) {
839       goto vertical_list;
840    }
841    if (type == ARG_LIST) {
842       goto arg_list;
843    }
844 
845    Dmsg1(800, "list_result starts second loop looking at %d fields\n", mdb->sql_num_fields());
846    list_dashes(mdb, send, ctx);
847    send(ctx, "|");
848    mdb->sql_field_seek(0);
849    for (i = 0; i < mdb->sql_num_fields(); i++) {
850       Dmsg1(800, "list_result looking at field %d\n", i);
851       field = mdb->sql_fetch_field();
852       if (!field) {
853          break;
854       }
855       max_len = max_length(field->max_length);
856       bsnprintf(buf, sizeof(buf), " %-*s |", max_len, field->name);
857       send(ctx, buf);
858    }
859    send(ctx, "\n");
860    list_dashes(mdb, send, ctx);
861 
862    Dmsg1(800, "list_result starts third loop looking at %d fields\n", mdb->sql_num_fields());
863    while ((row = mdb->sql_fetch_row()) != NULL) {
864       mdb->sql_field_seek(0);
865       send(ctx, "|");
866       for (i = 0; i < mdb->sql_num_fields(); i++) {
867          field = mdb->sql_fetch_field();
868          if (!field) {
869             break;
870          }
871          max_len = max_length(field->max_length);
872          if (row[i] == NULL) {
873             bsnprintf(buf, sizeof(buf), " %-*s |", max_len, "NULL");
874          } else if (mdb->sql_field_is_numeric(field->type) && !jcr->gui && is_an_integer(row[i])) {
875             bsnprintf(buf, sizeof(buf), " %*s |", max_len,
876                       add_commas(row[i], ewc));
877          } else {
878             strip_trailing_junk(row[i]);
879             bsnprintf(buf, sizeof(buf), " %-*s |", max_len, row[i]);
880          }
881          send(ctx, buf);
882       }
883       send(ctx, "\n");
884    }
885    list_dashes(mdb, send, ctx);
886    return mdb->sql_num_rows();
887 
888 vertical_list:
889 
890    Dmsg1(800, "list_result starts vertical list at %d fields\n", mdb->sql_num_fields());
891    while ((row = mdb->sql_fetch_row()) != NULL) {
892       mdb->sql_field_seek(0);
893       for (i = 0; i < mdb->sql_num_fields(); i++) {
894          field = mdb->sql_fetch_field();
895          if (!field) {
896             break;
897          }
898          if (row[i] == NULL) {
899             bsnprintf(buf, sizeof(buf), " %*s: %s\n", max_len, field->name, "NULL");
900          } else if (mdb->sql_field_is_numeric(field->type) && !jcr->gui && is_an_integer(row[i])) {
901             bsnprintf(buf, sizeof(buf), " %*s: %s\n", max_len, field->name,
902                 add_commas(row[i], ewc));
903          } else {
904             strip_trailing_junk(row[i]);
905             bsnprintf(buf, sizeof(buf), " %*s: %s\n", max_len, field->name, row[i]);
906          }
907          send(ctx, buf);
908       }
909       send(ctx, "\n");
910    }
911 
912 arg_list:
913 
914    Dmsg1(800, "list_result starts arg list at %d fields\n", mdb->sql_num_fields());
915    while ((row = mdb->sql_fetch_row()) != NULL) {
916       mdb->sql_field_seek(0);
917       for (i = 0; i < mdb->sql_num_fields(); i++) {
918          field = mdb->sql_fetch_field();
919          if (!field) {
920             break;
921          }
922          if (row[i] == NULL) {
923             bsnprintf(buf, sizeof(buf), "%s%s=", (i>0?" ":""), field->name);
924          } else {
925             bash_spaces(row[i]);
926             bsnprintf(buf, sizeof(buf), "%s%s=%s", (i>0?" ":""), field->name, row[i]);
927          }
928          send(ctx, buf);
929       }
930       send(ctx, "\n");
931    }
932    return mdb->sql_num_rows();
933 }
934 
935 /*
936  * Open a new connexion to mdb catalog. This function is used
937  * by batch and accurate mode.
938  */
bdb_open_batch_connexion(JCR * jcr)939 bool BDB::bdb_open_batch_connexion(JCR *jcr)
940 {
941    bool multi_db;
942 
943    multi_db = batch_insert_available();
944 
945    if (!jcr->db_batch) {
946       jcr->db_batch = bdb_clone_database_connection(jcr, multi_db);
947       if (!jcr->db_batch) {
948          Mmsg0(&errmsg, _("Could not init database batch connection\n"));
949          Jmsg(jcr, M_FATAL, 0, "%s", errmsg);
950          return false;
951       }
952 
953       if (!jcr->db_batch->bdb_open_database(jcr)) {
954          Mmsg2(&errmsg,  _("Could not open database \"%s\": ERR=%s\n"),
955               jcr->db_batch->get_db_name(), jcr->db_batch->bdb_strerror());
956          Jmsg(jcr, M_FATAL, 0, "%s", errmsg);
957          return false;
958       }
959    }
960    return true;
961 }
962 
963 /*
964  * !!! WARNING !!! Use this function only when bacula is stopped.
965  * ie, after a fatal signal and before exiting the program
966  * Print information about a BDB object.
967  */
bdb_debug_print(JCR * jcr,FILE * fp)968 void bdb_debug_print(JCR *jcr, FILE *fp)
969 {
970    BDB *mdb = jcr->db;
971 
972    if (!mdb) {
973       return;
974    }
975 
976    fprintf(fp, "BDB=%p db_name=%s db_user=%s connected=%s\n",
977            mdb, NPRTB(mdb->get_db_name()), NPRTB(mdb->get_db_user()), mdb->is_connected() ? "true" : "false");
978    fprintf(fp, "\tcmd=\"%s\" changes=%i\n", NPRTB(mdb->cmd), mdb->changes);
979    mdb->print_lock_info(fp);
980 }
981 
bdb_check_settings(JCR * jcr,int64_t * starttime,int val,int64_t val2)982 bool BDB::bdb_check_settings(JCR *jcr, int64_t *starttime, int val, int64_t val2)
983 {
984    return true;
985 }
986 
987 #endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL */
988