]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/tools/dbcheck.c
Last major change for 1.27 code -- see kes13Nov02
[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, 2001, 2002 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 int verbose = FALSE;
56 static B_DB *db;
57 static ID_LIST id_list;
58 static NAME_LIST name_list;
59 static char buf[2000];
60
61 #define MAX_ID_LIST_LEN 1000000
62
63 /* Forward referenced functions */
64 static int make_id_list(char *query, ID_LIST *id_list);
65 static int delete_id_list(char *query, ID_LIST *id_list);
66 static int make_name_list(char *query, NAME_LIST *name_list);
67 static void print_name_list(NAME_LIST *name_list);
68 static void free_name_list(NAME_LIST *name_list);
69 static char *get_cmd(char *prompt);
70 static void eliminate_duplicate_filenames();
71 static void eliminate_duplicate_paths();
72 static void eliminate_orphaned_jobmedia_records();
73 static void eliminate_orphaned_file_records();
74 static void eliminate_orphaned_path_records();
75 static void eliminate_orphaned_filename_records();
76 static void eliminate_orphaned_fileset_records();
77 static void do_interactive_mode();
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);
163    if (!db_open_database(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(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 Please select the fuction you want to perform.\n");
192
193    while (!quit) {
194       printf(_("\n\
195      1) Toggle modify database flag\n\
196      2) Toggle verbose flag\n\
197      3) Eliminate duplicate Filename records\n\
198      4) Eliminate duplicate Path records\n\
199      5) Eliminate orphaned Jobmedia records\n\
200      6) Eliminate orphaned File records\n\
201      7) Eliminate orphaned Path records\n\
202      8) Eliminate orphaned Filename records\n\
203      9) Eliminate orphaned FileSet records\n\
204     10) All (3-9)\n\
205     11) Quit\n"));
206
207       cmd = get_cmd(_("Select function number: "));
208       if (cmd) {
209          int item = atoi(cmd);
210          switch (item) {
211          case 1:
212             fix = !fix;
213             printf(_("Database will %sbe modified.\n"), fix?"":_("NOT "));
214             break;
215          case 2:
216             verbose = verbose?0:1;
217             printf(_("Verbose is %s\n"), verbose?_("On"):_("Off"));
218             break;
219          case 3:
220             eliminate_duplicate_filenames();
221             break;
222          case 4:
223             eliminate_duplicate_paths();
224             break;
225          case 5:
226             eliminate_orphaned_jobmedia_records();
227             break;
228          case 6:
229             eliminate_orphaned_file_records();
230             break;
231          case 7:
232             eliminate_orphaned_path_records();
233             break;
234          case 8:
235             eliminate_orphaned_filename_records();
236             break;
237          case 9:
238             eliminate_orphaned_fileset_records();
239             break;
240          case 10:
241             eliminate_duplicate_filenames();
242             eliminate_duplicate_paths();
243             eliminate_orphaned_jobmedia_records();
244             eliminate_orphaned_file_records();
245             eliminate_orphaned_path_records();
246             eliminate_orphaned_filename_records();
247             eliminate_orphaned_fileset_records();
248             break;
249          case 11:
250             quit = 1;
251             break;
252          }
253       }
254    }
255 }
256
257 static int print_name_handler(void *ctx, int num_fields, char **row)
258 {
259    if (row[0]) {
260       printf("%s\n", row[0]);
261    }
262    return 0;
263 }
264
265 static int print_jobmedia_handler(void *ctx, int num_fields, char **row)
266 {
267    printf(_("Orphaned JobMediaId=%s JobId=%s Volume=\"%s\"\n"), 
268               NPRT(row[0]), NPRT(row[1]), NPRT(row[2]));
269    return 0;
270 }
271
272 static int print_file_handler(void *ctx, int num_fields, char **row)
273 {
274    printf(_("Orphaned FileId=%s JobId=%s Volume=\"%s\"\n"), 
275               NPRT(row[0]), NPRT(row[1]), NPRT(row[2]));
276    return 0;
277 }
278
279 static int print_fileset_handler(void *ctx, int num_fields, char **row)
280 {
281    printf(_("Orphaned FileSetId=%s FileSet=\"%s\" MD5=%s\n"), 
282               NPRT(row[0]), NPRT(row[1]), NPRT(row[2]));
283    return 0;
284 }
285
286
287
288
289   
290 /*
291  * Called here with each id to be added to the list
292  */
293 static int id_list_handler(void *ctx, int num_fields, char **row)
294 {
295    ID_LIST *lst = (ID_LIST *)ctx;
296
297    if (lst->num_ids == MAX_ID_LIST_LEN) {  
298       return 1;
299    }
300    if (lst->num_ids == lst->max_ids) {
301       if (lst->max_ids == 0) {
302          lst->max_ids = 1000;
303          lst->Id = (uint32_t *)bmalloc(sizeof(uint32_t) * lst->max_ids);
304       } else {
305          lst->max_ids = (lst->max_ids * 3) / 2;
306          lst->Id = (uint32_t *)brealloc(lst->Id, sizeof(uint32_t) * lst->max_ids);
307       }
308    }
309    lst->Id[lst->num_ids++] = (uint32_t)strtod(row[0], NULL);
310    return 0;
311 }
312
313 /*
314  * Construct record id list
315  */
316 static int make_id_list(char *query, ID_LIST *id_list)
317 {
318    id_list->num_ids = 0;
319    id_list->num_del = 0;
320    id_list->tot_ids = 0;
321
322    if (!db_sql_query(db, query, id_list_handler, (void *)id_list)) {
323       printf("%s", db_strerror(db));
324       return 0;
325    }
326    return 1;
327 }
328
329 /*
330  * Delete all entries in the list 
331  */
332 static int delete_id_list(char *query, ID_LIST *id_list)
333
334    int i;
335
336    for (i=0; i < id_list->num_ids; i++) {
337       sprintf(buf, query, id_list->Id[i]);
338       if (verbose) {
339          printf("Deleting: %s\n", buf);
340       }
341       db_sql_query(db, buf, NULL, NULL);
342    }
343    return 1;
344 }
345
346 /*
347  * Called here with each name to be added to the list
348  */
349 static int name_list_handler(void *ctx, int num_fields, char **row)
350 {
351    NAME_LIST *name = (NAME_LIST *)ctx;
352
353    if (name->num_ids == MAX_ID_LIST_LEN) {  
354       return 1;
355    }
356    if (name->num_ids == name->max_ids) {
357       if (name->max_ids == 0) {
358          name->max_ids = 1000;
359          name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
360       } else {
361          name->max_ids = (name->max_ids * 3) / 2;
362          name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
363       }
364    }
365    name->name[name->num_ids++] = bstrdup(row[0]);
366    return 0;
367 }
368
369
370 /*
371  * Construct name list
372  */
373 static int make_name_list(char *query, NAME_LIST *name_list)
374 {
375    name_list->num_ids = 0;
376    name_list->num_del = 0;
377    name_list->tot_ids = 0;
378
379    if (!db_sql_query(db, query, name_list_handler, (void *)name_list)) {
380       printf("%s", db_strerror(db));
381       return 0;
382    }
383    return 1;
384 }
385
386 /*
387  * Print names in the list
388  */
389 static void print_name_list(NAME_LIST *name_list)
390
391    int i;
392
393    for (i=0; i < name_list->num_ids; i++) {
394       printf("%s\n", name_list->name[i]);
395    }
396 }
397
398
399 /*
400  * Free names in the list
401  */
402 static void free_name_list(NAME_LIST *name_list)
403
404    int i;
405
406    for (i=0; i < name_list->num_ids; i++) {
407       free(name_list->name[i]);
408    }
409    name_list->num_ids = 0;
410 }
411
412 static void eliminate_duplicate_filenames()
413 {
414    char *query;
415
416    printf("Checking for duplicate Filename entries.\n");
417    
418    /* Make list of duplicated names */
419 #ifdef really_needed_for_HAVE_SQLITE
420    query = "SELECT Name FROM (SELECT COUNT(Name) as Count,Name from Filename "
421            "GROUP BY Name) WHERE Count > 1";
422 #endif
423
424    query = "SELECT Name,count(Name) as Count FROM Filename GROUP BY Name "
425            "HAVING Count > 1";
426
427    if (!make_name_list(query, &name_list)) {
428       exit(1);
429    }
430    printf("Found %d duplicate Filename records.\n", name_list.num_ids);
431    if (verbose) {
432       print_name_list(&name_list);
433    }
434    if (fix) {
435       /* Loop through list of duplicate names */
436       for (int i=0; i<name_list.num_ids; i++) {
437          /* Get all the Ids of each name */
438          sprintf(buf, "SELECT FilenameId FROM Filename WHERE Name='%s'",
439             name_list.name[i]);
440          if (!make_id_list(buf, &id_list)) {
441             exit(1);
442          }
443          /* Force all records to use the first id then delete the other ids */
444          for (int j=1; j<id_list.num_ids; j++) {
445             sprintf(buf, "UPDATE File SET FilenameId=%u WHERE FilenameId=%u", 
446                id_list.Id[0], id_list.Id[j]);
447             db_sql_query(db, buf, NULL, NULL);
448             sprintf(buf, "DELETE FROM Filename WHERE FilenameId=%u", 
449                id_list.Id[j]);
450             db_sql_query(db, buf, NULL, NULL);
451          }
452       }
453    }
454    free_name_list(&name_list);
455 }
456
457 static void eliminate_duplicate_paths()
458 {
459    char *query;
460
461    printf("Checking for duplicate Path entries.\n");
462    
463    /* Make list of duplicated names */
464 #ifdef really_needed_for_HAVE_SQLITE
465    query = "SELECT Path FROM (SELECT COUNT(Path) AS Count,Path FROM Path "
466            "GROUP BY Path) WHERE Count > 1";
467 #endif
468
469    query = "SELECT Path,count(Path) as Count FROM Path "
470            "GROUP BY Path HAVING Count > 1";
471
472    if (!make_name_list(query, &name_list)) {
473       exit(1);
474    }
475    printf("Found %d duplicate Path records.\n", name_list.num_ids);
476    if (verbose) {
477       print_name_list(&name_list);
478    }
479    if (fix) {
480       /* Loop through list of duplicate names */
481       for (int i=0; i<name_list.num_ids; i++) {
482          /* Get all the Ids of each name */
483          sprintf(buf, "SELECT PathId FROM Path WHERE Path='%s'",
484             name_list.name[i]);
485          if (!make_id_list(buf, &id_list)) {
486             exit(1);
487          }
488          /* Force all records to use the first id then delete the other ids */
489          for (int j=1; j<id_list.num_ids; j++) {
490             sprintf(buf, "UPDATE File SET PathId=%u WHERE PathId=%u", 
491                id_list.Id[0], id_list.Id[j]);
492             db_sql_query(db, buf, NULL, NULL);
493             sprintf(buf, "DELETE FROM Path WHERE PathId=%u", 
494                id_list.Id[j]);
495             db_sql_query(db, buf, NULL, NULL);
496          }
497       }
498    }
499    free_name_list(&name_list);
500 }
501
502 static void eliminate_orphaned_jobmedia_records()
503 {
504    char *query;
505
506    printf("Checking for orphaned JobMedia entries.\n");
507 #ifdef really_needed_for_HAVE_SQLITE
508    query = "SELECT X FROM (SELECT JobMedia.JobMediaId as X,Job.JobId as Y "
509            "FROM JobMedia LEFT OUTER JOIN Job ON (JobMedia.JobId=Job.JobId)) "
510            "WHERE Y IS NULL";
511 #endif
512
513    query = "SELECT JobMedia.JobMediaId,Job.JobId FROM JobMedia "
514            "LEFT OUTER JOIN Job ON (JobMedia.JobId=Job.JobId) "
515            "WHERE Job.JobId IS NULL";
516    if (!make_id_list(query, &id_list)) {
517       exit(1);
518    }
519    printf("Found %d orphaned JobMedia records.\n", id_list.num_ids);
520    if (verbose) {
521       int i;
522       for (i=0; i < id_list.num_ids; i++) {
523          sprintf(buf, 
524 "SELECT JobMedia.JobMediaId,JobMedia.JobId,Media.VolumeName FROM JobMedia,Media "
525 "WHERE JobMedia.JobMediaId=%u AND Media.MediaId=JobMedia.MediaId", id_list.Id[i]);
526          if (!db_sql_query(db, buf, print_jobmedia_handler, NULL)) {
527             printf("%s\n", db_strerror(db));
528          }
529       }
530    }
531    
532    if (fix && id_list.num_ids > 0) {
533       printf("Deleting %d orphaned JobMedia records.\n", id_list.num_ids);
534       delete_id_list("DELETE FROM JobMedia WHERE JobMediaId=%u", &id_list);
535    }
536 }
537
538 static void eliminate_orphaned_file_records()
539 {
540    char *query;
541
542    printf("Checking for orphaned File entries. This may take some time!\n");
543 #ifdef really_needed_for_HAVE_SQLITE
544    query = "SELECT X FROM (SELECT File.FileId as X,Job.JobId as Y "
545            "FROM File LEFT OUTER JOIN Job ON (File.JobId=Job.JobId)) "
546            "WHERE Y IS NULL";
547 #endif
548
549    query = "SELECT File.FileId,Job.JobId FROM File "
550            "LEFT OUTER JOIN Job ON (File.JobId=Job.JobId) "
551            "WHERE Job.JobId IS NULL";
552    if (!make_id_list(query, &id_list)) {
553       exit(1);
554    }
555    printf("Found %d orphaned File records.\n", id_list.num_ids);
556    if (verbose) {
557       int i;
558       for (i=0; i < id_list.num_ids; i++) {
559          sprintf(buf, 
560 "SELECT File.FileId,File.JobId,Filename.Name FROM File,Filename "
561 "WHERE File.FileId=%u AND File.FilenameId=Filename.FilenameId", id_list.Id[i]);
562          if (!db_sql_query(db, buf, print_file_handler, NULL)) {
563             printf("%s\n", db_strerror(db));
564          }
565       }
566    }
567       
568    if (fix && id_list.num_ids > 0) {
569       printf("Deleting %d orphaned File records.\n", id_list.num_ids);
570       delete_id_list("DELETE FROM File WHERE FileId=%u", &id_list);
571    }
572 }
573
574 static void eliminate_orphaned_path_records()
575 {
576    char *query;
577
578    printf("Checking for orphaned Path entries. This may take some time!\n");
579 #ifdef really_needed_for_HAVE_SQLITE
580    query = "SELECT X FROM (SELECT Path.PathId as X,File.JobId as Y "
581            "FROM Path LEFT OUTER JOIN File ON (Path.PathId=File.PathId)) "
582            "WHERE Y IS NULL";
583 #endif
584
585    query = "SELECT Path.PathId,File.PathId FROM Path "
586            "LEFT OUTER JOIN File ON (Path.PathId=File.PathId) "
587            "HAVING File.PathId IS NULL";
588    if (!make_id_list(query, &id_list)) {
589       exit(1);
590    }
591    printf("Found %d orphaned Path records.\n", id_list.num_ids);
592    if (verbose) {
593       int i;
594       for (i=0; i < id_list.num_ids; i++) {
595          sprintf(buf, "SELECT Path FROM Path WHERE PathId=%u", id_list.Id[i]);
596          db_sql_query(db, buf, print_name_handler, NULL);
597       }
598    }
599    
600    if (fix && id_list.num_ids > 0) {
601       printf("Deleting %d orphaned Path records.\n", id_list.num_ids);
602       delete_id_list("DELETE FROM Path WHERE PathId=%u", &id_list);
603    }
604 }
605
606 static void eliminate_orphaned_filename_records()
607 {
608    char *query;
609
610    printf("Checking for orphaned Filename entries. This may take some time!\n");
611 #ifdef really_needed_for_HAVE_SQLITE
612    query = "SELECT X FROM (SELECT Filename.FilenameId as X,File.JobId as Y "
613            "FROM Filename LEFT OUTER JOIN File ON "
614            "(Filename.FilenameId=File.FilenameId)) "
615            "WHERE Y IS NULL";
616 #endif
617
618    query = "SELECT Filename.FilenameId,File.FilenameId FROM Filename "
619            "LEFT OUTER JOIN File ON (Filename.FilenameId=File.FilenameId) "
620            "WHERE File.FilenameId IS NULL";
621
622    if (!make_id_list(query, &id_list)) {
623       exit(1);
624    }
625    printf("Found %d orphaned Filename records.\n", id_list.num_ids);
626    if (verbose) {
627       int i;
628       for (i=0; i < id_list.num_ids; i++) {
629          sprintf(buf, "SELECT Name FROM Filename WHERE FilenameId=%u", id_list.Id[i]);
630          db_sql_query(db, buf, print_name_handler, NULL);
631       }
632    }
633    
634    if (fix && id_list.num_ids > 0) {
635       printf("Deleting %d orphaned Filename records.\n", id_list.num_ids);
636       delete_id_list("DELETE FROM Filename WHERE FilenameId=%u", &id_list);
637    }
638 }
639
640 static void eliminate_orphaned_fileset_records()
641 {
642    char *query;
643
644    printf("Checking for orphaned FileSet entries. This takes some time!\n");
645 #ifdef really_needed_for_HAVE_SQLITE
646    query = "SELECT X FROM (SELECT FileSet.FileSetId as X,Job.JobId as Y "
647            "FROM FileSet LEFT OUTER JOIN Job ON (FileSet.FileSetId=Job.FileSetId)) "
648            "WHERE Y IS NULL";
649 #endif
650
651    query = "SELECT FileSet.FileSetId,Job.FileSetId FROM FileSet "
652            "LEFT OUTER JOIN Job ON (FileSet.FileSetId=Job.FileSetId) "
653            "WHERE Job.FileSetId IS NULL";
654    if (!make_id_list(query, &id_list)) {
655       exit(1);
656    }
657    printf("Found %d orphaned FileSet records.\n", id_list.num_ids);
658    if (verbose) {
659       int i;
660       for (i=0; i < id_list.num_ids; i++) {
661          sprintf(buf, "SELECT FileSetId,FileSet,MD5 FROM FileSet "
662                       "WHERE FileSetId=%u", id_list.Id[i]);
663          if (!db_sql_query(db, buf, print_fileset_handler, NULL)) {
664             printf("%s\n", db_strerror(db));
665          }
666       }
667    }
668    
669    if (fix && id_list.num_ids > 0) {
670       printf("Deleting %d orphaned FileSet records.\n", id_list.num_ids);
671       delete_id_list("DELETE FROM FileSet WHERE FileSetId=%u", &id_list);
672    }
673 }
674
675
676 /*
677  * Gen next input command from the terminal
678  */
679 static char *get_cmd(char *prompt)
680 {
681    static char cmd[1000];
682
683    printf("%s", prompt);
684    if (fgets(cmd, sizeof(cmd), stdin) == NULL)
685       return NULL;
686    printf("\n");
687    strip_trailing_junk(cmd);
688    return cmd;
689 }