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