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