]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/tools/dbcheck.c
Fix NumVols in Pools, implement full listing of DB tables
[bacula/bacula] / bacula / src / tools / dbcheck.c
1 /*
2  *
3  *  Program to check a Bacula database for consistency and to
4  *   make repairs 
5  *
6  *   Kern E. Sibbald, August 2002
7  *
8  *   Version $Id$
9  *
10  */
11 /*
12    Copyright (C) 2000-2003 Kern Sibbald and John Walker
13
14    This program is free software; you can redistribute it and/or
15    modify it under the terms of the GNU General Public License as
16    published by the Free Software Foundation; either version 2 of
17    the License, or (at your option) any later version.
18
19    This program is distributed in the hope that it will be useful,
20    but WITHOUT ANY WARRANTY; without even the implied warranty of
21    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22    General Public License for more details.
23
24    You should have received a copy of the GNU General Public
25    License along with this program; if not, write to the Free
26    Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
27    MA 02111-1307, USA.
28
29  */
30
31 #include "bacula.h"
32 #include "cats/cats.h"
33
34 typedef struct s_id_ctx {
35    uint32_t *Id;                      /* ids to be modified */
36    int num_ids;                       /* ids stored */
37    int max_ids;                       /* size of array */
38    int num_del;                       /* number deleted */
39    int tot_ids;                       /* total to process */
40 } ID_LIST;
41
42 typedef struct s_name_ctx {
43    char **name;                       /* list of names */
44    int num_ids;                       /* ids stored */
45    int max_ids;                       /* size of array */
46    int num_del;                       /* number deleted */
47    int tot_ids;                       /* total to process */
48 } NAME_LIST;
49
50
51
52 /* Global variables */
53 static int fix = FALSE;
54 static int batch = FALSE;
55 static B_DB *db;
56 static ID_LIST id_list;
57 static NAME_LIST name_list;
58 static char buf[2000];
59
60 #define MAX_ID_LIST_LEN 1000000
61
62 /* Forward referenced functions */
63 static int make_id_list(char *query, ID_LIST *id_list);
64 static int delete_id_list(char *query, ID_LIST *id_list);
65 static int make_name_list(char *query, NAME_LIST *name_list);
66 static void print_name_list(NAME_LIST *name_list);
67 static void free_name_list(NAME_LIST *name_list);
68 static char *get_cmd(char *prompt);
69 static void eliminate_duplicate_filenames();
70 static void eliminate_duplicate_paths();
71 static void eliminate_orphaned_jobmedia_records();
72 static void eliminate_orphaned_file_records();
73 static void eliminate_orphaned_path_records();
74 static void eliminate_orphaned_filename_records();
75 static void eliminate_orphaned_fileset_records();
76 static void do_interactive_mode();
77 static int yes_no(char *prompt);
78
79
80 static void usage()
81 {
82    fprintf(stderr,
83 "Usage: dbcheck [-d debug_level] <working-directory> <bacula-databse> <user> <password>\n"
84 "       -b              batch mode\n"
85 "       -dnn            set debug level to nn\n"
86 "       -f              fix inconsistencies\n"
87 "       -v              verbose\n"
88 "       -?              print this message\n\n");
89    exit(1);
90 }
91
92 int main (int argc, char *argv[])
93 {
94    int ch;
95    char *user, *password, *db_name;
96
97    my_name_is(argc, argv, "dbcheck");
98    init_msg(NULL, NULL);              /* setup message handler */
99
100    memset(&id_list, 0, sizeof(id_list));
101    memset(&name_list, 0, sizeof(name_list));
102
103
104    while ((ch = getopt(argc, argv, "bd:fv?")) != -1) {
105       switch (ch) {
106          case 'b':                    /* batch */
107             batch = TRUE;
108             break;
109
110          case 'd':                    /* debug level */
111             debug_level = atoi(optarg);
112             if (debug_level <= 0)
113                debug_level = 1; 
114             break;
115
116          case 'f':                    /* fix inconsistencies */
117             fix = TRUE;
118             break;
119
120          case 'v':
121             verbose++;
122             break;
123
124          case '?':
125          default:
126             usage();
127
128       }  
129    }
130    argc -= optind;
131    argv += optind;
132
133    if (argc > 4) {
134       Pmsg0(0, _("Wrong number of arguments.\n"));
135       usage();
136    }
137
138    if (argc < 1) {
139       Pmsg0(0, _("Working directory not supplied.\n"));
140       usage();
141    }
142
143    /* This is needed by SQLite to find the db */
144    working_directory = argv[0];
145    db_name = "bacula";
146    user = db_name;
147    password = "";
148
149    if (argc == 2) {
150       db_name = argv[1];
151       user = db_name;
152    } else if (argc == 3) {
153       db_name = argv[1];
154       user = argv[2];
155    } else if (argc == 4) {
156       db_name = argv[1];
157       user = argv[2];
158       password = argv[3];
159    }
160
161    /* Open database */
162    db = db_init_database(NULL, db_name, user, password, NULL, 0, NULL);
163    if (!db_open_database(NULL, db)) {
164       Emsg1(M_FATAL, 0, "%s", db_strerror(db));
165    }
166
167    if (batch) {
168       eliminate_duplicate_filenames();
169       eliminate_duplicate_paths();
170       eliminate_orphaned_jobmedia_records();
171       eliminate_orphaned_file_records();
172       eliminate_orphaned_path_records();
173       eliminate_orphaned_filename_records();
174       eliminate_orphaned_fileset_records();
175    } else {
176       do_interactive_mode();
177    }
178
179    db_close_database(NULL, db);
180    close_msg(NULL);
181    term_msg();
182    return 0;
183 }
184
185 static void do_interactive_mode()
186 {
187    int quit = FALSE;
188    char *cmd;
189
190    printf("Hello, this is the database check/correct program.\n\
191 Modify database is %s. Verbose is %s.\n\
192 Please select the fuction you want to perform.\n",
193           fix?"On":"Off", verbose?"On":"Off");
194
195    while (!quit) {
196       if (fix) {
197          printf(_("\n\
198      1) Toggle modify database flag\n\
199      2) Toggle verbose flag\n\
200      3) Eliminate duplicate Filename records\n\
201      4) Eliminate duplicate Path records\n\
202      5) Eliminate orphaned Jobmedia records\n\
203      6) Eliminate orphaned File records\n\
204      7) Eliminate orphaned Path records\n\
205      8) Eliminate orphaned Filename records\n\
206      9) Eliminate orphaned FileSet records\n\
207     10) All (3-9)\n\
208     11) Quit\n"));
209        } else {
210          printf(_("\n\
211      1) Toggle modify database flag\n\
212      2) Toggle verbose flag\n\
213      3) Check for duplicate Filename records\n\
214      4) Check for duplicate Path records\n\
215      5) Check for orphaned Jobmedia records\n\
216      6) Check for orphaned File records\n\
217      7) Check for orphaned Path records\n\
218      8) Check for orphaned Filename records\n\
219      9) Check for orphaned FileSet records\n\
220     10) All (3-9)\n\
221     11) Quit\n"));
222        }
223
224       cmd = get_cmd(_("Select function number: "));
225       if (cmd) {
226          int item = atoi(cmd);
227          switch (item) {
228          case 1:
229             fix = !fix;
230             printf(_("Database will %sbe modified.\n"), fix?"":_("NOT "));
231             break;
232          case 2:
233             verbose = verbose?0:1;
234             printf(_("Verbose is %s\n"), verbose?_("On"):_("Off"));
235             break;
236          case 3:
237             eliminate_duplicate_filenames();
238             break;
239          case 4:
240             eliminate_duplicate_paths();
241             break;
242          case 5:
243             eliminate_orphaned_jobmedia_records();
244             break;
245          case 6:
246             eliminate_orphaned_file_records();
247             break;
248          case 7:
249             eliminate_orphaned_path_records();
250             break;
251          case 8:
252             eliminate_orphaned_filename_records();
253             break;
254          case 9:
255             eliminate_orphaned_fileset_records();
256             break;
257          case 10:
258             eliminate_duplicate_filenames();
259             eliminate_duplicate_paths();
260             eliminate_orphaned_jobmedia_records();
261             eliminate_orphaned_file_records();
262             eliminate_orphaned_path_records();
263             eliminate_orphaned_filename_records();
264             eliminate_orphaned_fileset_records();
265             break;
266          case 11:
267             quit = 1;
268             break;
269          }
270       }
271    }
272 }
273
274 static int print_name_handler(void *ctx, int num_fields, char **row)
275 {
276    if (row[0]) {
277       printf("%s\n", row[0]);
278    }
279    return 0;
280 }
281
282 static int print_jobmedia_handler(void *ctx, int num_fields, char **row)
283 {
284    printf(_("Orphaned JobMediaId=%s JobId=%s Volume=\"%s\"\n"), 
285               NPRT(row[0]), NPRT(row[1]), NPRT(row[2]));
286    return 0;
287 }
288
289 static int print_file_handler(void *ctx, int num_fields, char **row)
290 {
291    printf(_("Orphaned FileId=%s JobId=%s Volume=\"%s\"\n"), 
292               NPRT(row[0]), NPRT(row[1]), NPRT(row[2]));
293    return 0;
294 }
295
296 static int print_fileset_handler(void *ctx, int num_fields, char **row)
297 {
298    printf(_("Orphaned FileSetId=%s FileSet=\"%s\" MD5=%s\n"), 
299               NPRT(row[0]), NPRT(row[1]), NPRT(row[2]));
300    return 0;
301 }
302
303
304
305
306   
307 /*
308  * Called here with each id to be added to the list
309  */
310 static int id_list_handler(void *ctx, int num_fields, char **row)
311 {
312    ID_LIST *lst = (ID_LIST *)ctx;
313
314    if (lst->num_ids == MAX_ID_LIST_LEN) {  
315       return 1;
316    }
317    if (lst->num_ids == lst->max_ids) {
318       if (lst->max_ids == 0) {
319          lst->max_ids = 1000;
320          lst->Id = (uint32_t *)bmalloc(sizeof(uint32_t) * lst->max_ids);
321       } else {
322          lst->max_ids = (lst->max_ids * 3) / 2;
323          lst->Id = (uint32_t *)brealloc(lst->Id, sizeof(uint32_t) * lst->max_ids);
324       }
325    }
326    lst->Id[lst->num_ids++] = (uint32_t)strtod(row[0], NULL);
327    return 0;
328 }
329
330 /*
331  * Construct record id list
332  */
333 static int make_id_list(char *query, ID_LIST *id_list)
334 {
335    id_list->num_ids = 0;
336    id_list->num_del = 0;
337    id_list->tot_ids = 0;
338
339    if (!db_sql_query(db, query, id_list_handler, (void *)id_list)) {
340       printf("%s", db_strerror(db));
341       return 0;
342    }
343    return 1;
344 }
345
346 /*
347  * Delete all entries in the list 
348  */
349 static int delete_id_list(char *query, ID_LIST *id_list)
350
351    int i;
352
353    for (i=0; i < id_list->num_ids; i++) {
354       sprintf(buf, query, id_list->Id[i]);
355       if (verbose) {
356          printf("Deleting: %s\n", buf);
357       }
358       db_sql_query(db, buf, NULL, NULL);
359    }
360    return 1;
361 }
362
363 /*
364  * Called here with each name to be added to the list
365  */
366 static int name_list_handler(void *ctx, int num_fields, char **row)
367 {
368    NAME_LIST *name = (NAME_LIST *)ctx;
369
370    if (name->num_ids == MAX_ID_LIST_LEN) {  
371       return 1;
372    }
373    if (name->num_ids == name->max_ids) {
374       if (name->max_ids == 0) {
375          name->max_ids = 1000;
376          name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
377       } else {
378          name->max_ids = (name->max_ids * 3) / 2;
379          name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
380       }
381    }
382    name->name[name->num_ids++] = bstrdup(row[0]);
383    return 0;
384 }
385
386
387 /*
388  * Construct name list
389  */
390 static int make_name_list(char *query, NAME_LIST *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_sql_query(db, query, name_list_handler, (void *)name_list)) {
397       printf("%s", db_strerror(db));
398       return 0;
399    }
400    return 1;
401 }
402
403 /*
404  * Print names in the list
405  */
406 static void print_name_list(NAME_LIST *name_list)
407
408    int i;
409
410    for (i=0; i < name_list->num_ids; i++) {
411       printf("%s\n", name_list->name[i]);
412    }
413 }
414
415
416 /*
417  * Free names in the list
418  */
419 static void free_name_list(NAME_LIST *name_list)
420
421    int i;
422
423    for (i=0; i < name_list->num_ids; i++) {
424       free(name_list->name[i]);
425    }
426    name_list->num_ids = 0;
427 }
428
429 static void eliminate_duplicate_filenames()
430 {
431    char *query;
432    char esc_name[5000];
433
434    printf("Checking for duplicate Filename entries.\n");
435    
436    /* Make list of duplicated names */
437    query = "SELECT Name,count(Name) as Count FROM Filename GROUP BY Name "
438            "HAVING Count > 1";
439
440    if (!make_name_list(query, &name_list)) {
441       exit(1);
442    }
443    printf("Found %d duplicate Filename records.\n", name_list.num_ids);
444    if (name_list.num_ids && verbose && yes_no("Print the list? (yes/no): ")) {
445       print_name_list(&name_list);
446    }
447    if (fix) {
448       /* Loop through list of duplicate names */
449       for (int i=0; i<name_list.num_ids; i++) {
450          /* Get all the Ids of each name */
451          db_escape_string(esc_name, name_list.name[i], strlen(name_list.name[i]));
452          sprintf(buf, "SELECT FilenameId FROM Filename WHERE Name='%s'", esc_name);
453          if (!make_id_list(buf, &id_list)) {
454             exit(1);
455          }
456          if (verbose) {
457             printf("Found %d for: %s\n", id_list.num_ids, name_list.name[i]);
458          }
459          /* Force all records to use the first id then delete the other ids */
460          for (int j=1; j<id_list.num_ids; j++) {
461             sprintf(buf, "UPDATE File SET FilenameId=%u WHERE FilenameId=%u", 
462                id_list.Id[0], id_list.Id[j]);
463             db_sql_query(db, buf, NULL, NULL);
464             sprintf(buf, "DELETE FROM Filename WHERE FilenameId=%u", 
465                id_list.Id[j]);
466             db_sql_query(db, buf, NULL, NULL);
467          }
468       }
469    }
470    free_name_list(&name_list);
471 }
472
473 static void eliminate_duplicate_paths()
474 {
475    char *query;
476    char esc_name[5000];
477
478    printf(_("Checking for duplicate Path entries.\n"));
479    
480    /* Make list of duplicated names */
481
482    query = "SELECT Path,count(Path) as Count FROM Path "
483            "GROUP BY Path HAVING Count > 1";
484
485    if (!make_name_list(query, &name_list)) {
486       exit(1);
487    }
488    printf("Found %d duplicate Path records.\n", name_list.num_ids);
489    if (name_list.num_ids && verbose && yes_no("Print them? (yes/no): ")) {
490       print_name_list(&name_list);
491    }
492    if (fix) {
493       /* Loop through list of duplicate names */
494       for (int i=0; i<name_list.num_ids; i++) {
495          /* Get all the Ids of each name */
496          db_escape_string(esc_name, name_list.name[i], strlen(name_list.name[i]));
497          sprintf(buf, "SELECT PathId FROM Path WHERE Path='%s'", esc_name);
498          id_list.num_ids = 0;
499          if (!make_id_list(buf, &id_list)) {
500             exit(1);
501          }
502          if (verbose) {
503             printf("Found %d for: %s\n", id_list.num_ids, name_list.name[i]);
504          }
505          /* Force all records to use the first id then delete the other ids */
506          for (int j=1; j<id_list.num_ids; j++) {
507             sprintf(buf, "UPDATE File SET PathId=%u WHERE PathId=%u", 
508                id_list.Id[0], id_list.Id[j]);
509             db_sql_query(db, buf, NULL, NULL);
510             sprintf(buf, "DELETE FROM Path WHERE PathId=%u", 
511                id_list.Id[j]);
512             db_sql_query(db, buf, NULL, NULL);
513          }
514       }
515    }
516    free_name_list(&name_list);
517 }
518
519 static void eliminate_orphaned_jobmedia_records()
520 {
521    char *query;
522
523    printf("Checking for orphaned JobMedia entries.\n");
524    query = "SELECT JobMedia.JobMediaId,Job.JobId FROM JobMedia "
525            "LEFT OUTER JOIN Job ON (JobMedia.JobId=Job.JobId) "
526            "WHERE Job.JobId IS NULL";
527    if (!make_id_list(query, &id_list)) {
528       exit(1);
529    }
530    printf("Found %d orphaned JobMedia records.\n", id_list.num_ids);
531    if (id_list.num_ids && verbose && yes_no("Print them? (yes/no): ")) {
532       int i;
533       for (i=0; i < id_list.num_ids; i++) {
534          sprintf(buf, 
535 "SELECT JobMedia.JobMediaId,JobMedia.JobId,Media.VolumeName FROM JobMedia,Media "
536 "WHERE JobMedia.JobMediaId=%u AND Media.MediaId=JobMedia.MediaId", id_list.Id[i]);
537          if (!db_sql_query(db, buf, print_jobmedia_handler, NULL)) {
538             printf("%s\n", db_strerror(db));
539          }
540       }
541    }
542    
543    if (fix && id_list.num_ids > 0) {
544       printf("Deleting %d orphaned JobMedia records.\n", id_list.num_ids);
545       delete_id_list("DELETE FROM JobMedia WHERE JobMediaId=%u", &id_list);
546    }
547 }
548
549 static void eliminate_orphaned_file_records()
550 {
551    char *query;
552
553    printf("Checking for orphaned File entries. This may take some time!\n");
554    query = "SELECT File.FileId,Job.JobId FROM File "
555            "LEFT OUTER JOIN Job ON (File.JobId=Job.JobId) "
556            "WHERE Job.JobId IS NULL";
557    if (!make_id_list(query, &id_list)) {
558       exit(1);
559    }
560    printf("Found %d orphaned File records.\n", id_list.num_ids);
561    if (name_list.num_ids && verbose && yes_no("Print them? (yes/no): ")) {
562       int i;
563       for (i=0; i < id_list.num_ids; i++) {
564          sprintf(buf, 
565 "SELECT File.FileId,File.JobId,Filename.Name FROM File,Filename "
566 "WHERE File.FileId=%u AND File.FilenameId=Filename.FilenameId", id_list.Id[i]);
567          if (!db_sql_query(db, buf, print_file_handler, NULL)) {
568             printf("%s\n", db_strerror(db));
569          }
570       }
571    }
572       
573    if (fix && id_list.num_ids > 0) {
574       printf("Deleting %d orphaned File records.\n", id_list.num_ids);
575       delete_id_list("DELETE FROM File WHERE FileId=%u", &id_list);
576    }
577 }
578
579 static void eliminate_orphaned_path_records()
580 {
581    char *query;
582
583    printf("Checking for orphaned Path entries. This may take some time!\n");
584    query = "SELECT Path.PathId,File.PathId FROM Path "
585            "LEFT OUTER JOIN File ON (Path.PathId=File.PathId) "
586            "HAVING File.PathId IS NULL";
587    if (!make_id_list(query, &id_list)) {
588       exit(1);
589    }
590    printf("Found %d orphaned Path records.\n", id_list.num_ids);
591    if (id_list.num_ids && verbose && yes_no("Print them? (yes/no): ")) {
592       int i;
593       for (i=0; i < id_list.num_ids; i++) {
594          sprintf(buf, "SELECT Path FROM Path WHERE PathId=%u", id_list.Id[i]);
595          db_sql_query(db, buf, print_name_handler, NULL);
596       }
597    }
598    
599    if (fix && id_list.num_ids > 0) {
600       printf("Deleting %d orphaned Path records.\n", id_list.num_ids);
601       delete_id_list("DELETE FROM Path WHERE PathId=%u", &id_list);
602    }
603 }
604
605 static void eliminate_orphaned_filename_records()
606 {
607    char *query;
608
609    printf("Checking for orphaned Filename entries. This may take some time!\n");
610    query = "SELECT Filename.FilenameId,File.FilenameId FROM Filename "
611            "LEFT OUTER JOIN File ON (Filename.FilenameId=File.FilenameId) "
612            "WHERE File.FilenameId IS NULL";
613
614    if (!make_id_list(query, &id_list)) {
615       exit(1);
616    }
617    printf("Found %d orphaned Filename records.\n", id_list.num_ids);
618    if (id_list.num_ids && verbose && yes_no("Print them? (yes/no): ")) {
619       int i;
620       for (i=0; i < id_list.num_ids; i++) {
621          sprintf(buf, "SELECT Name FROM Filename WHERE FilenameId=%u", id_list.Id[i]);
622          db_sql_query(db, buf, print_name_handler, NULL);
623       }
624    }
625    
626    if (fix && id_list.num_ids > 0) {
627       printf("Deleting %d orphaned Filename records.\n", id_list.num_ids);
628       delete_id_list("DELETE FROM Filename WHERE FilenameId=%u", &id_list);
629    }
630 }
631
632 static void eliminate_orphaned_fileset_records()
633 {
634    char *query;
635
636    printf("Checking for orphaned FileSet entries. This takes some time!\n");
637    query = "SELECT FileSet.FileSetId,Job.FileSetId FROM FileSet "
638            "LEFT OUTER JOIN Job ON (FileSet.FileSetId=Job.FileSetId) "
639            "WHERE Job.FileSetId IS NULL";
640    if (!make_id_list(query, &id_list)) {
641       exit(1);
642    }
643    printf("Found %d orphaned FileSet records.\n", id_list.num_ids);
644    if (id_list.num_ids && verbose && yes_no("Print them? (yes/no): ")) {
645       int i;
646       for (i=0; i < id_list.num_ids; i++) {
647          sprintf(buf, "SELECT FileSetId,FileSet,MD5 FROM FileSet "
648                       "WHERE FileSetId=%u", id_list.Id[i]);
649          if (!db_sql_query(db, buf, print_fileset_handler, NULL)) {
650             printf("%s\n", db_strerror(db));
651          }
652       }
653    }
654    
655    if (fix && id_list.num_ids > 0) {
656       printf("Deleting %d orphaned FileSet records.\n", id_list.num_ids);
657       delete_id_list("DELETE FROM FileSet WHERE FileSetId=%u", &id_list);
658    }
659 }
660
661
662 /*
663  * Gen next input command from the terminal
664  */
665 static char *get_cmd(char *prompt)
666 {
667    static char cmd[1000];
668
669    printf("%s", prompt);
670    if (fgets(cmd, sizeof(cmd), stdin) == NULL)
671       return NULL;
672    printf("\n");
673    strip_trailing_junk(cmd);
674    return cmd;
675 }
676
677 static int yes_no(char *prompt)
678 {
679    char *cmd;  
680    cmd = get_cmd(prompt);
681    return strcasecmp(cmd, "yes") == 0;
682 }