1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2002-2011 Free Software Foundation Europe e.V.
5    Copyright (C) 2011-2016 Planets Communications B.V.
6    Copyright (C) 2013-2020 Bareos GmbH & Co. KG
7 
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version three of the GNU Affero General Public
10    License as published by the Free Software Foundation and included
11    in the file LICENSE.
12 
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    Affero General Public License for more details.
17 
18    You should have received a copy of the GNU Affero General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22 */
23 /*
24  * Kern E. Sibbald, August 2002
25  */
26 /**
27  * Program to check a BAREOS database for consistency and to make repairs
28  */
29 
30 #include "include/bareos.h"
31 #include "cats/cats.h"
32 #include "cats/cats_backends.h"
33 #include "lib/runscript.h"
34 #include "dird/dird_conf.h"
35 #include "dird/dird_globals.h"
36 #include "lib/edit.h"
37 #include "lib/parse_conf.h"
38 #include "lib/util.h"
39 
40 using namespace directordaemon;
41 
42 extern bool ParseDirConfig(const char* configfile, int exit_code);
43 
44 typedef struct s_id_ctx {
45   int64_t* Id; /* ids to be modified */
46   int num_ids; /* ids stored */
47   int max_ids; /* size of array */
48   int num_del; /* number deleted */
49   int tot_ids; /* total to process */
50 } ID_LIST;
51 
52 typedef struct s_name_ctx {
53   char** name; /* list of names */
54   int num_ids; /* ids stored */
55   int max_ids; /* size of array */
56   int num_del; /* number deleted */
57   int tot_ids; /* total to process */
58 } NameList;
59 
60 /*
61  * Global variables
62  */
63 static bool fix = false;
64 static bool batch = false;
65 static BareosDb* db;
66 static ID_LIST id_list;
67 static NameList name_list;
68 static char buf[20000];
69 static bool quit = false;
70 static const char* idx_tmp_name;
71 #if defined(HAVE_DYNAMIC_CATS_BACKENDS)
72 static const char* backend_directory = _PATH_BAREOS_BACKENDDIR;
73 #endif
74 
75 #define MAX_ID_LIST_LEN 10000000
76 
77 /*
78  * Forward referenced functions
79  */
80 static void set_quit();
81 static void toggle_modify();
82 static void toggle_verbose();
83 static void eliminate_duplicate_paths();
84 static void eliminate_orphaned_jobmedia_records();
85 static void eliminate_orphaned_file_records();
86 static void eliminate_orphaned_path_records();
87 static void eliminate_orphaned_fileset_records();
88 static void eliminate_orphaned_client_records();
89 static void eliminate_orphaned_job_records();
90 static void eliminate_admin_records();
91 static void eliminate_restore_records();
92 static void repair_bad_paths();
93 static void repair_bad_filenames();
94 static void run_all_commands();
95 
96 struct dbcheck_cmdstruct {
97   void (*func)();           /**< Handler */
98   const char* description;  /**< Main purpose */
99   const bool baserepaircmd; /**< command that modifies the database */
100 };
101 
102 static struct dbcheck_cmdstruct commands[] = {
103     {set_quit, "Quit", false},
104     {toggle_modify, "Toggle modify database flag", false},
105     {toggle_verbose, "Toggle verbose flag", false},
106     {repair_bad_filenames, "Check for bad Filename records", true},
107     {repair_bad_paths, "Check for bad Path records", true},
108     {eliminate_duplicate_paths, "Check for duplicate Path records", true},
109     {eliminate_orphaned_jobmedia_records, "Check for orphaned Jobmedia records",
110      true},
111     {eliminate_orphaned_file_records, "Check for orphaned File records", true},
112     {eliminate_orphaned_path_records, "Check for orphaned Path records", true},
113     {eliminate_orphaned_fileset_records, "Check for orphaned FileSet records",
114      true},
115     {eliminate_orphaned_client_records, "Check for orphaned Client records",
116      true},
117     {eliminate_orphaned_job_records, "Check for orphaned Job records", true},
118     {eliminate_admin_records, "Check for all Admin records", true},
119     {eliminate_restore_records, "Check for all Restore records", true},
120     {run_all_commands, "Run ALL checks", false},
121 };
122 
123 const int number_commands =
124     (sizeof(commands) / sizeof(struct dbcheck_cmdstruct));
125 
126 
usage()127 static void usage()
128 {
129   kBareosVersionStrings.PrintCopyrightWithFsfAndPlanets(stderr, 2002);
130   fprintf(stderr,
131           "Usage: bareos-dbcheck [ options ] <working-directory> "
132           "<bareos-database> <user> <password> [<dbhost>] [<dbport>]\n"
133           "       -b                batch mode\n"
134           "       -B                print catalog configuration and exit\n"
135           "       -c  <config>      Director configuration filename or "
136           "configuration directory (e.g. /etc/bareos)\n"
137           "       -C  <catalog>     catalog name in the director configuration "
138           "file\n"
139           "       -d  <nnn>         set debug level to <nnn>\n"
140           "       -dt               print a timestamp in debug output\n"
141           "       -D  <driver name> specify the database driver name (default "
142           "NULL) <postgresql|mysql|sqlite3>\n"
143           "       -f                fix inconsistencies\n"
144           "       -v                verbose\n"
145           "       -?                print this message\n\n");
146   exit(1);
147 }
148 
149 /**
150  * helper functions
151  */
152 
153 /**
154  * Gen next input command from the terminal
155  */
GetCmd(const char * prompt)156 static char* GetCmd(const char* prompt)
157 {
158   static char cmd[1000];
159 
160   printf("%s", prompt);
161   fflush(stdout);
162   if (fgets(cmd, sizeof(cmd), stdin) == NULL) {
163     printf("\n");
164     quit = true;
165     return NULL;
166   }
167   StripTrailingJunk(cmd);
168   return cmd;
169 }
170 
yes_no(const char * prompt,bool batchvalue=true)171 static bool yes_no(const char* prompt, bool batchvalue = true)
172 {
173   char* cmd;
174   /*
175    * return the batchvalue if batch operation is set
176    */
177   if (batch) { return batchvalue; }
178   cmd = GetCmd(prompt);
179   if (!cmd) {
180     quit = true;
181     return false;
182   }
183   return (Bstrcasecmp(cmd, "yes")) || (Bstrcasecmp(cmd, _("yes")));
184 }
185 
set_quit()186 static void set_quit() { quit = true; }
187 
toggle_modify()188 static void toggle_modify()
189 {
190   fix = !fix;
191   if (fix)
192     printf(_("Database will be modified.\n"));
193   else
194     printf(_("Database will NOT be modified.\n"));
195 }
196 
toggle_verbose()197 static void toggle_verbose()
198 {
199   verbose = verbose ? 0 : 1;
200   if (verbose)
201     printf(_(" Verbose is on.\n"));
202   else
203     printf(_(" Verbose is off.\n"));
204 }
205 
206 
PrintCatalogDetails(CatalogResource * catalog,const char * working_dir)207 static void PrintCatalogDetails(CatalogResource* catalog,
208                                 const char* working_dir)
209 {
210   POOLMEM* catalog_details = GetPoolMemory(PM_MESSAGE);
211 
212   /*
213    * Instantiate a BareosDb class and see what db_type gets assigned to it.
214    */
215   db = db_init_database(NULL, catalog->db_driver, catalog->db_name,
216                         catalog->db_user, catalog->db_password.value,
217                         catalog->db_address, catalog->db_port,
218                         catalog->db_socket, catalog->mult_db_connections,
219                         catalog->disable_batch_insert, catalog->try_reconnect,
220                         catalog->exit_on_fatal);
221   if (db) {
222     printf("%sdb_type=%s\nworking_dir=%s\n", catalog->display(catalog_details),
223            db->GetType(), working_directory);
224     db->CloseDatabase(NULL);
225   }
226   FreePoolMemory(catalog_details);
227 }
228 
PrintNameHandler(void * ctx,int num_fields,char ** row)229 static int PrintNameHandler(void* ctx, int num_fields, char** row)
230 {
231   if (row[0]) { printf("%s\n", row[0]); }
232   return 0;
233 }
234 
GetNameHandler(void * ctx,int num_fields,char ** row)235 static int GetNameHandler(void* ctx, int num_fields, char** row)
236 {
237   POOLMEM* name = (POOLMEM*)ctx;
238 
239   if (row[0]) { PmStrcpy(name, row[0]); }
240   return 0;
241 }
242 
PrintJobHandler(void * ctx,int num_fields,char ** row)243 static int PrintJobHandler(void* ctx, int num_fields, char** row)
244 {
245   printf(_("JobId=%s Name=\"%s\" StartTime=%s\n"), NPRT(row[0]), NPRT(row[1]),
246          NPRT(row[2]));
247   return 0;
248 }
249 
PrintJobmediaHandler(void * ctx,int num_fields,char ** row)250 static int PrintJobmediaHandler(void* ctx, int num_fields, char** row)
251 {
252   printf(_("Orphaned JobMediaId=%s JobId=%s Volume=\"%s\"\n"), NPRT(row[0]),
253          NPRT(row[1]), NPRT(row[2]));
254   return 0;
255 }
256 
PrintFileHandler(void * ctx,int num_fields,char ** row)257 static int PrintFileHandler(void* ctx, int num_fields, char** row)
258 {
259   printf(_("Orphaned FileId=%s JobId=%s Volume=\"%s\"\n"), NPRT(row[0]),
260          NPRT(row[1]), NPRT(row[2]));
261   return 0;
262 }
263 
PrintFilesetHandler(void * ctx,int num_fields,char ** row)264 static int PrintFilesetHandler(void* ctx, int num_fields, char** row)
265 {
266   printf(_("Orphaned FileSetId=%s FileSet=\"%s\" MD5=%s\n"), NPRT(row[0]),
267          NPRT(row[1]), NPRT(row[2]));
268   return 0;
269 }
270 
PrintClientHandler(void * ctx,int num_fields,char ** row)271 static int PrintClientHandler(void* ctx, int num_fields, char** row)
272 {
273   printf(_("Orphaned ClientId=%s Name=\"%s\"\n"), NPRT(row[0]), NPRT(row[1]));
274   return 0;
275 }
276 
277 /**
278  * database index handling functions
279  *
280  * The code below to add indexes is needed only for MySQL, and
281  *  that to improve the performance.
282  */
283 
284 #define MAXIDX 100
285 typedef struct s_idx_list {
286   char* key_name;
287   int count_key; /* how many times the index meets *key_name */
288   int CountCol;  /* how many times meets the desired column name */
289 } IDX_LIST;
290 
291 /**
292  * Drop temporary index
293  */
DropTmpIdx(const char * idx_name,const char * table_name)294 static bool DropTmpIdx(const char* idx_name, const char* table_name)
295 {
296   if (idx_tmp_name != NULL) {
297     printf(_("Drop temporary index.\n"));
298     fflush(stdout);
299     Bsnprintf(buf, sizeof(buf), "DROP INDEX %s ON %s", idx_name, table_name);
300     if (verbose) { printf("%s\n", buf); }
301     if (!db->SqlQuery(buf, NULL, NULL)) {
302       printf("%s\n", db->strerror());
303       return false;
304     } else {
305       if (verbose) { printf(_("Temporary index %s deleted.\n"), idx_tmp_name); }
306     }
307     fflush(stdout);
308   }
309   idx_tmp_name = NULL;
310   return true;
311 }
312 
313 
314 /*
315  * Called here with each id to be added to the list
316  */
IdListHandler(void * ctx,int num_fields,char ** row)317 static int IdListHandler(void* ctx, int num_fields, char** row)
318 {
319   ID_LIST* lst = (ID_LIST*)ctx;
320 
321   if (lst->num_ids == MAX_ID_LIST_LEN) { return 1; }
322   if (lst->num_ids == lst->max_ids) {
323     if (lst->max_ids == 0) {
324       lst->max_ids = 10000;
325       lst->Id = (int64_t*)malloc(sizeof(int64_t) * lst->max_ids);
326     } else {
327       lst->max_ids = (lst->max_ids * 3) / 2;
328       lst->Id = (int64_t*)realloc(lst->Id, sizeof(int64_t) * lst->max_ids);
329     }
330   }
331   lst->Id[lst->num_ids++] = str_to_int64(row[0]);
332   return 0;
333 }
334 
335 /*
336  * Construct record id list
337  */
MakeIdList(const char * query,ID_LIST * id_list)338 static int MakeIdList(const char* query, ID_LIST* id_list)
339 {
340   id_list->num_ids = 0;
341   id_list->num_del = 0;
342   id_list->tot_ids = 0;
343 
344   if (!db->SqlQuery(query, IdListHandler, (void*)id_list)) {
345     printf("%s", db->strerror());
346     return 0;
347   }
348   return 1;
349 }
350 
351 /*
352  * Delete all entries in the list
353  */
DeleteIdList(const char * query,ID_LIST * id_list)354 static int DeleteIdList(const char* query, ID_LIST* id_list)
355 {
356   char ed1[50];
357 
358   for (int i = 0; i < id_list->num_ids; i++) {
359     Bsnprintf(buf, sizeof(buf), query, edit_int64(id_list->Id[i], ed1));
360     if (verbose) { printf(_("Deleting: %s\n"), buf); }
361     db->SqlQuery(buf, NULL, NULL);
362   }
363   return 1;
364 }
365 
366 /*
367  * Called here with each name to be added to the list
368  */
NameListHandler(void * ctx,int num_fields,char ** row)369 static int NameListHandler(void* ctx, int num_fields, char** row)
370 {
371   NameList* name = (NameList*)ctx;
372 
373   if (name->num_ids == MAX_ID_LIST_LEN) { return 1; }
374   if (name->num_ids == name->max_ids) {
375     if (name->max_ids == 0) {
376       name->max_ids = 10000;
377       name->name = (char**)malloc(sizeof(char*) * name->max_ids);
378     } else {
379       name->max_ids = (name->max_ids * 3) / 2;
380       name->name = (char**)realloc(name->name, sizeof(char*) * name->max_ids);
381     }
382   }
383   name->name[name->num_ids++] = strdup(row[0]);
384   return 0;
385 }
386 
387 /*
388  * Construct name list
389  */
MakeNameList(const char * query,NameList * name_list)390 static int MakeNameList(const char* query, NameList* name_list)
391 {
392   name_list->num_ids = 0;
393   name_list->num_del = 0;
394   name_list->tot_ids = 0;
395 
396   if (!db->SqlQuery(query, NameListHandler, (void*)name_list)) {
397     printf("%s", db->strerror());
398     return 0;
399   }
400   return 1;
401 }
402 
403 /*
404  * Print names in the list
405  */
PrintNameList(NameList * name_list)406 static void PrintNameList(NameList* name_list)
407 {
408   for (int i = 0; i < name_list->num_ids; i++) {
409     printf("%s\n", name_list->name[i]);
410   }
411 }
412 
413 /*
414  * Free names in the list
415  */
FreeNameList(NameList * name_list)416 static void FreeNameList(NameList* name_list)
417 {
418   for (int i = 0; i < name_list->num_ids; i++) { free(name_list->name[i]); }
419   name_list->num_ids = 0;
420 }
421 
eliminate_duplicate_paths()422 static void eliminate_duplicate_paths()
423 {
424   const char* query;
425   char esc_name[5000];
426 
427   printf(_("Checking for duplicate Path entries.\n"));
428   fflush(stdout);
429 
430   /*
431    * Make list of duplicated names
432    */
433   query =
434       "SELECT Path, count(Path) as Count FROM Path "
435       "GROUP BY Path HAVING count(Path) > 1";
436 
437   if (!MakeNameList(query, &name_list)) { exit(1); }
438   printf(_("Found %d duplicate Path records.\n"), name_list.num_ids);
439   fflush(stdout);
440   if (name_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
441     PrintNameList(&name_list);
442   }
443   if (quit) { return; }
444   if (fix) {
445     /*
446      * Loop through list of duplicate names
447      */
448     for (int i = 0; i < name_list.num_ids; i++) {
449       /*
450        * Get all the Ids of each name
451        */
452       db->EscapeString(NULL, esc_name, name_list.name[i],
453                        strlen(name_list.name[i]));
454       Bsnprintf(buf, sizeof(buf), "SELECT PathId FROM Path WHERE Path='%s'",
455                 esc_name);
456       if (verbose > 1) { printf("%s\n", buf); }
457       if (!MakeIdList(buf, &id_list)) { exit(1); }
458       if (verbose) {
459         printf(_("Found %d for: %s\n"), id_list.num_ids, name_list.name[i]);
460       }
461       /*
462        * Force all records to use the first id then delete the other ids
463        */
464       for (int j = 1; j < id_list.num_ids; j++) {
465         char ed1[50], ed2[50];
466         Bsnprintf(buf, sizeof(buf), "UPDATE File SET PathId=%s WHERE PathId=%s",
467                   edit_int64(id_list.Id[0], ed1),
468                   edit_int64(id_list.Id[j], ed2));
469         if (verbose > 1) { printf("%s\n", buf); }
470         db->SqlQuery(buf, NULL, NULL);
471         Bsnprintf(buf, sizeof(buf), "DELETE FROM Path WHERE PathId=%s", ed2);
472         if (verbose > 2) { printf("%s\n", buf); }
473         db->SqlQuery(buf, NULL, NULL);
474       }
475     }
476     fflush(stdout);
477   }
478   FreeNameList(&name_list);
479 }
480 
481 /*
482  * repair functions
483  */
484 
eliminate_orphaned_jobmedia_records()485 static void eliminate_orphaned_jobmedia_records()
486 {
487   const char* query =
488       "SELECT JobMedia.JobMediaId,Job.JobId FROM JobMedia "
489       "LEFT OUTER JOIN Job USING(JobId) "
490       "WHERE Job.JobId IS NULL LIMIT 300000";
491 
492   printf(_("Checking for orphaned JobMedia entries.\n"));
493   fflush(stdout);
494   if (!MakeIdList(query, &id_list)) { exit(1); }
495   /*
496    * Loop doing 300000 at a time
497    */
498   while (id_list.num_ids != 0) {
499     printf(_("Found %d orphaned JobMedia records.\n"), id_list.num_ids);
500     if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
501       for (int i = 0; i < id_list.num_ids; i++) {
502         char ed1[50];
503         Bsnprintf(
504             buf, sizeof(buf),
505             "SELECT JobMedia.JobMediaId,JobMedia.JobId,Media.VolumeName FROM "
506             "JobMedia,Media "
507             "WHERE JobMedia.JobMediaId=%s AND Media.MediaId=JobMedia.MediaId",
508             edit_int64(id_list.Id[i], ed1));
509         if (!db->SqlQuery(buf, PrintJobmediaHandler, NULL)) {
510           printf("%s\n", db->strerror());
511         }
512       }
513     }
514     if (quit) { return; }
515 
516     if (fix && id_list.num_ids > 0) {
517       printf(_("Deleting %d orphaned JobMedia records.\n"), id_list.num_ids);
518       DeleteIdList("DELETE FROM JobMedia WHERE JobMediaId=%s", &id_list);
519     } else {
520       break; /* get out if not updating db */
521     }
522     if (!MakeIdList(query, &id_list)) { exit(1); }
523   }
524   fflush(stdout);
525 }
526 
eliminate_orphaned_file_records()527 static void eliminate_orphaned_file_records()
528 {
529   const char* query =
530       "SELECT File.FileId,Job.JobId FROM File "
531       "LEFT OUTER JOIN Job USING (JobId) "
532       "WHERE Job.JobId IS NULL LIMIT 300000";
533 
534   printf(_("Checking for orphaned File entries. This may take some time!\n"));
535   if (verbose > 1) { printf("%s\n", query); }
536   fflush(stdout);
537   if (!MakeIdList(query, &id_list)) { exit(1); }
538   /*
539    * Loop doing 300000 at a time
540    */
541   while (id_list.num_ids != 0) {
542     printf(_("Found %d orphaned File records.\n"), id_list.num_ids);
543     if (name_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
544       for (int i = 0; i < id_list.num_ids; i++) {
545         char ed1[50];
546         Bsnprintf(buf, sizeof(buf),
547                   "SELECT File.FileId,File.JobId,File.Name FROM File "
548                   "WHERE File.FileId=%s",
549                   edit_int64(id_list.Id[i], ed1));
550         if (!db->SqlQuery(buf, PrintFileHandler, NULL)) {
551           printf("%s\n", db->strerror());
552         }
553       }
554     }
555     if (quit) { return; }
556     if (fix && id_list.num_ids > 0) {
557       printf(_("Deleting %d orphaned File records.\n"), id_list.num_ids);
558       DeleteIdList("DELETE FROM File WHERE FileId=%s", &id_list);
559     } else {
560       break; /* get out if not updating db */
561     }
562     if (!MakeIdList(query, &id_list)) { exit(1); }
563   }
564   fflush(stdout);
565 }
566 
eliminate_orphaned_path_records()567 static void eliminate_orphaned_path_records()
568 {
569   db_int64_ctx lctx;
570   PoolMem query(PM_MESSAGE);
571 
572   lctx.count = 0;
573   idx_tmp_name = NULL;
574 
575   db->FillQuery(query, BareosDb::SQL_QUERY::get_orphaned_paths_0);
576 
577   printf(_("Checking for orphaned Path entries. This may take some time!\n"));
578   if (verbose > 1) { printf("%s\n", query.c_str()); }
579   fflush(stdout);
580   if (!MakeIdList(query.c_str(), &id_list)) { exit(1); }
581   /*
582    * Loop doing 300000 at a time
583    */
584   while (id_list.num_ids != 0) {
585     printf(_("Found %d orphaned Path records.\n"), id_list.num_ids);
586     fflush(stdout);
587     if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
588       for (int i = 0; i < id_list.num_ids; i++) {
589         char ed1[50];
590         Bsnprintf(buf, sizeof(buf), "SELECT Path FROM Path WHERE PathId=%s",
591                   edit_int64(id_list.Id[i], ed1));
592         db->SqlQuery(buf, PrintNameHandler, NULL);
593       }
594       fflush(stdout);
595     }
596     if (quit) { return; }
597     if (fix && id_list.num_ids > 0) {
598       printf(_("Deleting %d orphaned Path records.\n"), id_list.num_ids);
599       fflush(stdout);
600       DeleteIdList("DELETE FROM Path WHERE PathId=%s", &id_list);
601     } else {
602       break; /* get out if not updating db */
603     }
604     if (!MakeIdList(query.c_str(), &id_list)) { exit(1); }
605   }
606 }
607 
eliminate_orphaned_fileset_records()608 static void eliminate_orphaned_fileset_records()
609 {
610   const char* query;
611 
612   printf(_("Checking for orphaned FileSet entries. This takes some time!\n"));
613   query =
614       "SELECT FileSet.FileSetId,Job.FileSetId FROM FileSet "
615       "LEFT OUTER JOIN Job USING(FileSetId) "
616       "WHERE Job.FileSetId IS NULL";
617   if (verbose > 1) { printf("%s\n", query); }
618   fflush(stdout);
619   if (!MakeIdList(query, &id_list)) { exit(1); }
620   printf(_("Found %d orphaned FileSet records.\n"), id_list.num_ids);
621   fflush(stdout);
622   if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
623     for (int i = 0; i < id_list.num_ids; i++) {
624       char ed1[50];
625       Bsnprintf(buf, sizeof(buf),
626                 "SELECT FileSetId,FileSet,MD5 FROM FileSet "
627                 "WHERE FileSetId=%s",
628                 edit_int64(id_list.Id[i], ed1));
629       if (!db->SqlQuery(buf, PrintFilesetHandler, NULL)) {
630         printf("%s\n", db->strerror());
631       }
632     }
633     fflush(stdout);
634   }
635   if (quit) { return; }
636   if (fix && id_list.num_ids > 0) {
637     printf(_("Deleting %d orphaned FileSet records.\n"), id_list.num_ids);
638     fflush(stdout);
639     DeleteIdList("DELETE FROM FileSet WHERE FileSetId=%s", &id_list);
640   }
641 }
642 
eliminate_orphaned_client_records()643 static void eliminate_orphaned_client_records()
644 {
645   const char* query;
646 
647   printf(_("Checking for orphaned Client entries.\n"));
648   /*
649    * In English:
650    *   Wiffle through Client for every Client
651    *   joining with the Job table including every Client even if
652    *   there is not a match in Job (left outer join), then
653    *   filter out only those where no Job points to a Client
654    *   i.e. Job.Client is NULL
655    */
656   query =
657       "SELECT Client.ClientId,Client.Name FROM Client "
658       "LEFT OUTER JOIN Job USING(ClientId) "
659       "WHERE Job.ClientId IS NULL";
660   if (verbose > 1) { printf("%s\n", query); }
661   fflush(stdout);
662   if (!MakeIdList(query, &id_list)) { exit(1); }
663   printf(_("Found %d orphaned Client records.\n"), id_list.num_ids);
664   if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
665     for (int i = 0; i < id_list.num_ids; i++) {
666       char ed1[50];
667       Bsnprintf(buf, sizeof(buf),
668                 "SELECT ClientId,Name FROM Client "
669                 "WHERE ClientId=%s",
670                 edit_int64(id_list.Id[i], ed1));
671       if (!db->SqlQuery(buf, PrintClientHandler, NULL)) {
672         printf("%s\n", db->strerror());
673       }
674     }
675     fflush(stdout);
676   }
677   if (quit) { return; }
678   if (fix && id_list.num_ids > 0) {
679     printf(_("Deleting %d orphaned Client records.\n"), id_list.num_ids);
680     fflush(stdout);
681     DeleteIdList("DELETE FROM Client WHERE ClientId=%s", &id_list);
682   }
683 }
684 
eliminate_orphaned_job_records()685 static void eliminate_orphaned_job_records()
686 {
687   const char* query;
688 
689   printf(_("Checking for orphaned Job entries.\n"));
690   /*
691    * In English:
692    *   Wiffle through Job for every Job
693    *   joining with the Client table including every Job even if
694    *   there is not a match in Client (left outer join), then
695    *   filter out only those where no Client exists
696    *   i.e. Client.Name is NULL
697    */
698   query =
699       "SELECT Job.JobId,Job.Name FROM Job "
700       "LEFT OUTER JOIN Client USING(ClientId) "
701       "WHERE Client.Name IS NULL";
702   if (verbose > 1) { printf("%s\n", query); }
703   fflush(stdout);
704   if (!MakeIdList(query, &id_list)) { exit(1); }
705   printf(_("Found %d orphaned Job records.\n"), id_list.num_ids);
706   fflush(stdout);
707   if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
708     for (int i = 0; i < id_list.num_ids; i++) {
709       char ed1[50];
710       Bsnprintf(buf, sizeof(buf),
711                 "SELECT JobId,Name,StartTime FROM Job "
712                 "WHERE JobId=%s",
713                 edit_int64(id_list.Id[i], ed1));
714       if (!db->SqlQuery(buf, PrintJobHandler, NULL)) {
715         printf("%s\n", db->strerror());
716       }
717     }
718     fflush(stdout);
719   }
720   if (quit) { return; }
721   if (fix && id_list.num_ids > 0) {
722     printf(_("Deleting %d orphaned Job records.\n"), id_list.num_ids);
723     fflush(stdout);
724     DeleteIdList("DELETE FROM Job WHERE JobId=%s", &id_list);
725     printf(_("Deleting JobMedia records of orphaned Job records.\n"));
726     fflush(stdout);
727     DeleteIdList("DELETE FROM JobMedia WHERE JobId=%s", &id_list);
728     printf(_("Deleting Log records of orphaned Job records.\n"));
729     fflush(stdout);
730     DeleteIdList("DELETE FROM Log WHERE JobId=%s", &id_list);
731   }
732 }
733 
eliminate_admin_records()734 static void eliminate_admin_records()
735 {
736   const char* query;
737 
738   printf(_("Checking for Admin Job entries.\n"));
739   query =
740       "SELECT Job.JobId FROM Job "
741       "WHERE Job.Type='D'";
742   if (verbose > 1) { printf("%s\n", query); }
743   fflush(stdout);
744   if (!MakeIdList(query, &id_list)) { exit(1); }
745   printf(_("Found %d Admin Job records.\n"), id_list.num_ids);
746   if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
747     for (int i = 0; i < id_list.num_ids; i++) {
748       char ed1[50];
749       Bsnprintf(buf, sizeof(buf),
750                 "SELECT JobId,Name,StartTime FROM Job "
751                 "WHERE JobId=%s",
752                 edit_int64(id_list.Id[i], ed1));
753       if (!db->SqlQuery(buf, PrintJobHandler, NULL)) {
754         printf("%s\n", db->strerror());
755       }
756     }
757     fflush(stdout);
758   }
759   if (quit) { return; }
760   if (fix && id_list.num_ids > 0) {
761     printf(_("Deleting %d Admin Job records.\n"), id_list.num_ids);
762     fflush(stdout);
763     DeleteIdList("DELETE FROM Job WHERE JobId=%s", &id_list);
764   }
765 }
766 
eliminate_restore_records()767 static void eliminate_restore_records()
768 {
769   const char* query;
770 
771   printf(_("Checking for Restore Job entries.\n"));
772   query =
773       "SELECT Job.JobId FROM Job "
774       "WHERE Job.Type='R'";
775   if (verbose > 1) { printf("%s\n", query); }
776   fflush(stdout);
777   if (!MakeIdList(query, &id_list)) { exit(1); }
778   printf(_("Found %d Restore Job records.\n"), id_list.num_ids);
779   if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
780     for (int i = 0; i < id_list.num_ids; i++) {
781       char ed1[50];
782       Bsnprintf(buf, sizeof(buf),
783                 "SELECT JobId,Name,StartTime FROM Job "
784                 "WHERE JobId=%s",
785                 edit_int64(id_list.Id[i], ed1));
786       if (!db->SqlQuery(buf, PrintJobHandler, NULL)) {
787         printf("%s\n", db->strerror());
788       }
789     }
790     fflush(stdout);
791   }
792   if (quit) { return; }
793   if (fix && id_list.num_ids > 0) {
794     printf(_("Deleting %d Restore Job records.\n"), id_list.num_ids);
795     fflush(stdout);
796     DeleteIdList("DELETE FROM Job WHERE JobId=%s", &id_list);
797   }
798 }
799 
repair_bad_filenames()800 static void repair_bad_filenames()
801 {
802   const char* query;
803   int i;
804 
805   printf(_("Checking for Filenames with a trailing slash\n"));
806   query =
807       "SELECT FileId,Name from File "
808       "WHERE Name LIKE '%/'";
809   if (verbose > 1) { printf("%s\n", query); }
810   fflush(stdout);
811   if (!MakeIdList(query, &id_list)) { exit(1); }
812   printf(_("Found %d bad Filename records.\n"), id_list.num_ids);
813   if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
814     for (i = 0; i < id_list.num_ids; i++) {
815       char ed1[50];
816       Bsnprintf(buf, sizeof(buf), "SELECT Name FROM File WHERE FileId=%s",
817                 edit_int64(id_list.Id[i], ed1));
818       if (!db->SqlQuery(buf, PrintNameHandler, NULL)) {
819         printf("%s\n", db->strerror());
820       }
821     }
822     fflush(stdout);
823   }
824   if (quit) { return; }
825   if (fix && id_list.num_ids > 0) {
826     POOLMEM* name = GetPoolMemory(PM_FNAME);
827     char esc_name[5000];
828     printf(_("Reparing %d bad Filename records.\n"), id_list.num_ids);
829     fflush(stdout);
830     for (i = 0; i < id_list.num_ids; i++) {
831       int len;
832       char ed1[50];
833       Bsnprintf(buf, sizeof(buf), "SELECT Name FROM File WHERE FileId=%s",
834                 edit_int64(id_list.Id[i], ed1));
835       if (!db->SqlQuery(buf, GetNameHandler, name)) {
836         printf("%s\n", db->strerror());
837       }
838       /*
839        * Strip trailing slash(es)
840        */
841       for (len = strlen(name); len > 0 && IsPathSeparator(name[len - 1]);
842            len--) {}
843       if (len == 0) {
844         len = 1;
845         esc_name[0] = ' ';
846         esc_name[1] = 0;
847       } else {
848         name[len - 1] = 0;
849         db->EscapeString(NULL, esc_name, name, len);
850       }
851       Bsnprintf(buf, sizeof(buf), "UPDATE File SET Name='%s' WHERE FileId=%s",
852                 esc_name, edit_int64(id_list.Id[i], ed1));
853       if (verbose > 1) { printf("%s\n", buf); }
854       db->SqlQuery(buf, NULL, NULL);
855     }
856     FreePoolMemory(name);
857   }
858   fflush(stdout);
859 }
860 
repair_bad_paths()861 static void repair_bad_paths()
862 {
863   PoolMem query(PM_MESSAGE);
864   int i;
865 
866   printf(_("Checking for Paths without a trailing slash\n"));
867   db->FillQuery(query, BareosDb::SQL_QUERY::get_bad_paths_0);
868   if (verbose > 1) { printf("%s\n", query.c_str()); }
869   fflush(stdout);
870   if (!MakeIdList(query.c_str(), &id_list)) { exit(1); }
871   printf(_("Found %d bad Path records.\n"), id_list.num_ids);
872   fflush(stdout);
873   if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
874     for (i = 0; i < id_list.num_ids; i++) {
875       char ed1[50];
876       Bsnprintf(buf, sizeof(buf), "SELECT Path FROM Path WHERE PathId=%s",
877                 edit_int64(id_list.Id[i], ed1));
878       if (!db->SqlQuery(buf, PrintNameHandler, NULL)) {
879         printf("%s\n", db->strerror());
880       }
881     }
882     fflush(stdout);
883   }
884   if (quit) { return; }
885   if (fix && id_list.num_ids > 0) {
886     POOLMEM* name = GetPoolMemory(PM_FNAME);
887     char esc_name[5000];
888     printf(_("Reparing %d bad Filename records.\n"), id_list.num_ids);
889     fflush(stdout);
890     for (i = 0; i < id_list.num_ids; i++) {
891       int len;
892       char ed1[50];
893       Bsnprintf(buf, sizeof(buf), "SELECT Path FROM Path WHERE PathId=%s",
894                 edit_int64(id_list.Id[i], ed1));
895       if (!db->SqlQuery(buf, GetNameHandler, name)) {
896         printf("%s\n", db->strerror());
897       }
898       /*
899        * Strip trailing blanks
900        */
901       for (len = strlen(name); len > 0 && name[len - 1] == ' '; len--) {
902         name[len - 1] = 0;
903       }
904       /*
905        * Add trailing slash
906        */
907       len = PmStrcat(name, "/");
908       db->EscapeString(NULL, esc_name, name, len);
909       Bsnprintf(buf, sizeof(buf), "UPDATE Path SET Path='%s' WHERE PathId=%s",
910                 esc_name, edit_int64(id_list.Id[i], ed1));
911       if (verbose > 1) { printf("%s\n", buf); }
912       db->SqlQuery(buf, NULL, NULL);
913     }
914     fflush(stdout);
915     FreePoolMemory(name);
916   }
917 }
918 
run_all_commands()919 static void run_all_commands()
920 {
921   for (int i = 0; i < number_commands; i++) {
922     if (commands[i].baserepaircmd) {
923       printf("===========================================================\n");
924       printf("=\n");
925       printf("= %s, modify=%d\n", commands[i].description, fix);
926       printf("=\n");
927 
928       /* execute the real function */
929       (commands[i].func)();
930 
931       printf("=\n");
932       printf("=\n");
933       printf(
934           "==========================================================="
935           "\n\n\n\n");
936     }
937   }
938 }
939 
print_commands()940 static void print_commands()
941 {
942   for (int i = 0; i < number_commands; i++) {
943     printf("    %2d) %s\n", i, commands[i].description);
944   }
945 }
946 
do_interactive_mode()947 static void do_interactive_mode()
948 {
949   const char* cmd;
950 
951   printf(_("Hello, this is the Bareos database check/correct program.\n"));
952 
953   while (!quit) {
954     if (fix)
955       printf(_("Modify database is on."));
956     else
957       printf(_("Modify database is off."));
958     if (verbose)
959       printf(_(" Verbose is on.\n"));
960     else
961       printf(_(" Verbose is off.\n"));
962 
963     printf(_("Please select the function you want to perform.\n"));
964 
965     print_commands();
966     cmd = GetCmd(_("Select function number: "));
967     if (cmd) {
968       int item = atoi(cmd);
969       if ((item >= 0) && (item < number_commands)) {
970         /* run specified function */
971         (commands[item].func)();
972       }
973     }
974   }
975 }
976 
977 /**
978  * main
979  */
main(int argc,char * argv[])980 int main(int argc, char* argv[])
981 {
982   int ch;
983   const char* db_driver = NULL;
984   const char *user, *password, *db_name, *dbhost;
985   int dbport = 0;
986   bool print_catalog = false;
987   char* configfile = NULL;
988   char* catalogname = NULL;
989   char* endptr;
990 #if defined(HAVE_DYNAMIC_CATS_BACKENDS)
991   std::vector<std::string> backend_directories;
992 #endif
993 
994   setlocale(LC_ALL, "");
995   tzset();
996   bindtextdomain("bareos", LOCALEDIR);
997   textdomain("bareos");
998 
999   MyNameIs(argc, argv, "dbcheck");
1000   InitMsg(NULL, NULL); /* setup message handler */
1001 
1002   memset(&id_list, 0, sizeof(id_list));
1003   memset(&name_list, 0, sizeof(name_list));
1004 
1005   while ((ch = getopt(argc, argv, "bc:C:D:d:fvBt?")) != -1) {
1006     switch (ch) {
1007       case 'B':
1008         print_catalog = true; /* get catalog information from config */
1009         break;
1010       case 'b': /* batch */
1011         batch = true;
1012         break;
1013       case 'C': /* CatalogName */
1014         catalogname = optarg;
1015         break;
1016       case 'c': /* configfile */
1017         configfile = optarg;
1018         break;
1019 
1020       case 'D': /* db_driver */
1021         db_driver = optarg;
1022         break;
1023       case 'd': /* debug level */
1024         if (*optarg == 't') {
1025           dbg_timestamp = true;
1026         } else {
1027           debug_level = atoi(optarg);
1028           if (debug_level <= 0) { debug_level = 1; }
1029         }
1030         break;
1031       case 'f': /* fix inconsistencies */
1032         fix = true;
1033         break;
1034       case 'v':
1035         verbose++;
1036         break;
1037       case '?':
1038       default:
1039         usage();
1040     }
1041   }
1042   argc -= optind;
1043   argv += optind;
1044 
1045   OSDependentInit();
1046 
1047   if (configfile || (argc == 0)) {
1048     CatalogResource* catalog = NULL;
1049     int found = 0;
1050     if (argc > 0) {
1051       Pmsg0(0, _("Warning skipping the additional parameters for working "
1052                  "directory/dbname/user/password/host.\n"));
1053     }
1054     my_config = InitDirConfig(configfile, M_ERROR_TERM);
1055     my_config->ParseConfig();
1056     LockRes(my_config);
1057     foreach_res (catalog, R_CATALOG) {
1058       if (catalogname && bstrcmp(catalog->resource_name_, catalogname)) {
1059         ++found;
1060         break;
1061       } else if (!catalogname) {  // stop on first if no catalogname is given
1062         ++found;
1063         break;
1064       }
1065     }
1066     UnlockRes(my_config);
1067     if (!found) {
1068       if (catalogname) {
1069         Pmsg2(0,
1070               _("Error can not find the Catalog name[%s] in the given config "
1071                 "file [%s]\n"),
1072               catalogname, configfile);
1073       } else {
1074         Pmsg1(0,
1075               _("Error there is no Catalog section in the given config file "
1076                 "[%s]\n"),
1077               configfile);
1078       }
1079       exit(1);
1080     } else {
1081       LockRes(my_config);
1082       me = (DirectorResource*)my_config->GetNextRes(R_DIRECTOR, NULL);
1083       my_config->own_resource_ = me;
1084       UnlockRes(my_config);
1085       if (!me) {
1086         Pmsg0(0, _("Error no Director resource defined.\n"));
1087         exit(1);
1088       }
1089 
1090       SetWorkingDirectory(me->working_directory);
1091 #if defined(HAVE_DYNAMIC_CATS_BACKENDS)
1092       DbSetBackendDirs(me->backend_directories);
1093 #endif
1094 
1095       /*
1096        * Print catalog information and exit (-B)
1097        */
1098       if (print_catalog) {
1099         PrintCatalogDetails(catalog, me->working_directory);
1100         exit(0);
1101       }
1102 
1103       db_name = catalog->db_name;
1104       user = catalog->db_user;
1105       password = catalog->db_password.value;
1106       dbhost = catalog->db_address;
1107       db_driver = catalog->db_driver;
1108       if (dbhost && dbhost[0] == 0) { dbhost = NULL; }
1109       dbport = catalog->db_port;
1110     }
1111   } else {
1112     if (argc > 6) {
1113       Pmsg0(0, _("Wrong number of arguments.\n"));
1114       usage();
1115     }
1116 
1117     /*
1118      * This is needed by SQLite to find the db
1119      */
1120     working_directory = argv[0];
1121     db_name = "bareos";
1122     user = db_name;
1123     password = "";
1124     dbhost = NULL;
1125 
1126     if (argc == 2) {
1127       db_name = argv[1];
1128       user = db_name;
1129     } else if (argc == 3) {
1130       db_name = argv[1];
1131       user = argv[2];
1132     } else if (argc == 4) {
1133       db_name = argv[1];
1134       user = argv[2];
1135       password = argv[3];
1136     } else if (argc == 5) {
1137       db_name = argv[1];
1138       user = argv[2];
1139       password = argv[3];
1140       dbhost = argv[4];
1141     } else if (argc == 6) {
1142       db_name = argv[1];
1143       user = argv[2];
1144       password = argv[3];
1145       dbhost = argv[4];
1146       errno = 0;
1147       dbport = strtol(argv[5], &endptr, 10);
1148       if (*endptr != '\0') {
1149         Pmsg0(0, _("Database port must be a numeric value.\n"));
1150         exit(1);
1151       } else if (errno == ERANGE) {
1152         Pmsg0(0, _("Database port must be a int value.\n"));
1153         exit(1);
1154       }
1155     }
1156 
1157 #if defined(HAVE_DYNAMIC_CATS_BACKENDS)
1158     backend_directories.emplace_back(backend_directory);
1159     DbSetBackendDirs(backend_directories);
1160 #endif
1161   }
1162 
1163   /*
1164    * Open database
1165    */
1166   db = db_init_database(NULL, db_driver, db_name, user, password, dbhost,
1167                         dbport, NULL, false, false, false, false);
1168   if (!db->OpenDatabase(NULL)) {
1169     Emsg1(M_FATAL, 0, "%s", db->strerror());
1170     return 1;
1171   }
1172 
1173   /*
1174    * Drop temporary index idx_tmp_name if it already exists
1175    */
1176   DropTmpIdx("idxPIchk", "File");
1177 
1178   if (batch) {
1179     run_all_commands();
1180   } else {
1181     do_interactive_mode();
1182   }
1183 
1184   /*
1185    * Drop temporary index idx_tmp_name
1186    */
1187   DropTmpIdx("idxPIchk", "File");
1188 
1189   db->CloseDatabase(NULL);
1190   DbFlushBackends();
1191   CloseMsg(NULL);
1192   TermMsg();
1193 
1194   return 0;
1195 }
1196