]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/tools/dbcheck.c
b9e83fd7ad49e519d06e7555eabc1e751469f271
[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) 2002-2006 Kern Sibbald
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
16    version 2 as amended with additional clauses defined in the
17    file LICENSE in the main source directory.
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 
22    the file LICENSE for additional details.
23
24  */
25
26 #include "bacula.h"
27 #include "cats/cats.h"
28 #include "lib/runscript.h"
29 #include "dird/dird_conf.h"
30
31 /* Dummy functions */
32 int generate_daemon_event(JCR *jcr, const char *event) 
33    { return 1; }
34
35 typedef struct s_id_ctx {
36    uint32_t *Id;                      /* ids to be modified */
37    int num_ids;                       /* ids stored */
38    int max_ids;                       /* size of array */
39    int num_del;                       /* number deleted */
40    int tot_ids;                       /* total to process */
41 } ID_LIST;
42
43 typedef struct s_name_ctx {
44    char **name;                       /* list of names */
45    int num_ids;                       /* ids stored */
46    int max_ids;                       /* size of array */
47    int num_del;                       /* number deleted */
48    int tot_ids;                       /* total to process */
49 } NAME_LIST;
50
51
52
53 /* Global variables */
54 static bool fix = false;
55 static bool batch = false;
56 static B_DB *db;
57 static ID_LIST id_list;
58 static NAME_LIST name_list;
59 static char buf[20000];
60 static bool quit = false;
61
62 #define MAX_ID_LIST_LEN 10000000
63
64 /* Forward referenced functions */
65 static int make_id_list(const char *query, ID_LIST *id_list);
66 static int delete_id_list(const char *query, ID_LIST *id_list);
67 static int make_name_list(const char *query, NAME_LIST *name_list);
68 static void print_name_list(NAME_LIST *name_list);
69 static void free_name_list(NAME_LIST *name_list);
70 static char *get_cmd(const char *prompt);
71 static void eliminate_duplicate_filenames();
72 static void eliminate_duplicate_paths();
73 static void eliminate_orphaned_jobmedia_records();
74 static void eliminate_orphaned_file_records();
75 static void eliminate_orphaned_path_records();
76 static void eliminate_orphaned_filename_records();
77 static void eliminate_orphaned_fileset_records();
78 static void eliminate_orphaned_client_records();
79 static void eliminate_orphaned_job_records();
80 static void eliminate_admin_records();
81 static void eliminate_restore_records();
82 static void repair_bad_paths();
83 static void repair_bad_filenames();
84 static void do_interactive_mode();
85 static bool yes_no(const char *prompt);
86
87
88 static void usage()
89 {
90    fprintf(stderr,
91 "Usage: dbcheck [-c config] [-C catalog name] [-d debug_level] <working-directory> <bacula-database> <user> <password> [<dbhost>]\n"
92 "       -b              batch mode\n"
93 "       -C              catalog name in the director conf file\n"
94 "       -c              director conf filename\n"
95 "       -dnn            set debug level to nn\n"
96 "       -f              fix inconsistencies\n"
97 "       -v              verbose\n"
98 "       -?              print this message\n\n");
99    exit(1);
100 }
101
102 int main (int argc, char *argv[])
103 {
104    int ch;
105    const char *user, *password, *db_name, *dbhost;
106    char *configfile = NULL;
107    char *catalogname = NULL;
108
109    setlocale(LC_ALL, "");
110    bindtextdomain("bacula", LOCALEDIR);
111    textdomain("bacula");
112
113    my_name_is(argc, argv, "dbcheck");
114    init_msg(NULL, NULL);              /* setup message handler */
115
116    memset(&id_list, 0, sizeof(id_list));
117    memset(&name_list, 0, sizeof(name_list));
118
119
120    while ((ch = getopt(argc, argv, "bc:C:d:fv?")) != -1) {
121       switch (ch) {
122       case 'b':                    /* batch */
123          batch = true;
124          break;
125
126       case 'C':                    /* CatalogName */
127           catalogname = optarg;
128          break;
129
130       case 'c':                    /* configfile */
131           configfile = optarg;
132          break;
133
134       case 'd':                    /* debug level */
135          debug_level = atoi(optarg);
136          if (debug_level <= 0)
137             debug_level = 1;
138          break;
139
140       case 'f':                    /* fix inconsistencies */
141          fix = true;
142          break;
143
144       case 'v':
145          verbose++;
146          break;
147
148       case '?':
149       default:
150          usage();
151       }
152    }
153    argc -= optind;
154    argv += optind;
155
156    OSDependentInit();
157
158    if (configfile) {
159       CAT *catalog = NULL;
160       int found = 0;
161       if (argc > 0) {
162          Pmsg0(0, _("Warning skipping the additional parameters for working directory/dbname/user/password/host.\n"));
163       }
164       parse_config(configfile);
165       LockRes();
166       foreach_res(catalog, R_CATALOG) {
167          if (catalogname && !strcmp(catalog->hdr.name, catalogname)) {
168             ++found;
169             break;
170          } else if (!catalogname) { // stop on first if no catalogname is given
171            ++found;
172            break;
173          }
174       }
175       UnlockRes();
176       if (!found) {
177          if (catalogname) {
178             Pmsg2(0, _("Error can not find the Catalog name[%s] in the given config file [%s]\n"), catalogname, configfile);
179          } else {
180             Pmsg1(0, _("Error there is no Catalog section in the given config file [%s]\n"), configfile);
181          }
182          exit(1);
183       } else {
184          DIRRES *director;
185          LockRes();
186          director = (DIRRES *)GetNextRes(R_DIRECTOR, NULL);
187          UnlockRes();
188          if (!director) {
189             Pmsg0(0, _("Error no Director resource defined.\n"));
190             exit(1);
191          }
192          set_working_directory(director->working_directory);
193          db_name = catalog->db_name;
194          user = catalog->db_user;
195          password = catalog->db_password;
196          dbhost = catalog->db_address;
197          if (dbhost && dbhost[0] == 0) {
198             dbhost = NULL;
199          }
200       }
201    } else {
202       if (argc > 5) {
203          Pmsg0(0, _("Wrong number of arguments.\n"));
204          usage();
205       }
206
207       if (argc < 1) {
208          Pmsg0(0, _("Working directory not supplied.\n"));
209          usage();
210       }
211
212       /* This is needed by SQLite to find the db */
213       working_directory = argv[0];
214       db_name = "bacula";
215       user = db_name;
216       password = "";
217       dbhost = NULL;
218
219       if (argc == 2) {
220          db_name = argv[1];
221          user = db_name;
222       } else if (argc == 3) {
223          db_name = argv[1];
224          user = argv[2];
225       } else if (argc == 4) {
226          db_name = argv[1];
227          user = argv[2];
228          password = argv[3];
229       } else if (argc == 5) {
230          db_name = argv[1];
231          user = argv[2];
232          password = argv[3];
233          dbhost = argv[4];
234       }
235    }
236
237    /* Open database */
238    db = db_init_database(NULL, db_name, user, password, dbhost, 0, NULL, 0);
239    if (!db_open_database(NULL, db)) {
240       Emsg1(M_FATAL, 0, "%s", db_strerror(db));
241           return 1;
242    }
243
244    if (batch) {
245       repair_bad_paths();
246       repair_bad_filenames();
247       eliminate_duplicate_filenames();
248       eliminate_duplicate_paths();
249       eliminate_orphaned_jobmedia_records();
250       eliminate_orphaned_file_records();
251       eliminate_orphaned_path_records();
252       eliminate_orphaned_filename_records();
253       eliminate_orphaned_fileset_records();
254       eliminate_orphaned_client_records();
255       eliminate_orphaned_job_records();
256       eliminate_admin_records();
257       eliminate_restore_records();
258    } else {
259       do_interactive_mode();
260    }
261
262    db_close_database(NULL, db);
263    close_msg(NULL);
264    term_msg();
265    return 0;
266 }
267
268 static void do_interactive_mode()
269 {
270    const char *cmd;
271
272    printf(_("Hello, this is the database check/correct program.\n"));
273    if (fix)
274       printf(_("Modify database is on."));
275    else
276       printf(_("Modify database is off."));
277    if (verbose)
278       printf(_(" Verbose is on.\n"));
279    else
280       printf(_(" Verbose is off.\n"));
281
282    printf(_("Please select the fuction you want to perform.\n"));
283
284    while (!quit) {
285       if (fix) {
286          printf(_("\n"
287 "     1) Toggle modify database flag\n"
288 "     2) Toggle verbose flag\n"
289 "     3) Repair bad Filename records\n"
290 "     4) Repair bad Path records\n"
291 "     5) Eliminate duplicate Filename records\n"
292 "     6) Eliminate duplicate Path records\n"
293 "     7) Eliminate orphaned Jobmedia records\n"
294 "     8) Eliminate orphaned File records\n"
295 "     9) Eliminate orphaned Path records\n"
296 "    10) Eliminate orphaned Filename records\n"
297 "    11) Eliminate orphaned FileSet records\n"
298 "    12) Eliminate orphaned Client records\n"
299 "    13) Eliminate orphaned Job records\n"
300 "    14) Eliminate all Admin records\n"
301 "    15) Eliminate all Restore records\n"
302 "    16) All (3-15)\n"
303 "    17) Quit\n"));
304        } else {
305          printf(_("\n"
306 "     1) Toggle modify database flag\n"
307 "     2) Toggle verbose flag\n"
308 "     3) Check for bad Filename records\n"
309 "     4) Check for bad Path records\n"
310 "     5) Check for duplicate Filename records\n"
311 "     6) Check for duplicate Path records\n"
312 "     7) Check for orphaned Jobmedia records\n"
313 "     8) Check for orphaned File records\n"
314 "     9) Check for orphaned Path records\n"
315 "    10) Check for orphaned Filename records\n"
316 "    11) Check for orphaned FileSet records\n"
317 "    12) Check for orphaned Client records\n"
318 "    13) Check for orphaned Job records\n"
319 "    14) Check for all Admin records\n"
320 "    15) Check for all Restore records\n"
321 "    16) All (3-15)\n"
322 "    17) Quit\n"));
323        }
324
325       cmd = get_cmd(_("Select function number: "));
326       if (cmd) {
327          int item = atoi(cmd);
328          switch (item) {
329          case 1:
330             fix = !fix;
331             if (fix)
332                printf(_("Database will be modified.\n"));
333             else
334                printf(_("Database will NOT be modified.\n"));
335             break;
336          case 2:
337             verbose = verbose?0:1;
338             if (verbose)
339                printf(_(" Verbose is on.\n"));
340             else
341                printf(_(" Verbose is off.\n"));
342             break;
343          case 3:
344             repair_bad_filenames();
345             break;
346          case 4:
347             repair_bad_paths();
348             break;
349          case 5:
350             eliminate_duplicate_filenames();
351             break;
352          case 6:
353             eliminate_duplicate_paths();
354             break;
355          case 7:
356             eliminate_orphaned_jobmedia_records();
357             break;
358          case 8:
359             eliminate_orphaned_file_records();
360             break;
361          case 9:
362             eliminate_orphaned_path_records();
363             break;
364          case 10:
365             eliminate_orphaned_filename_records();
366             break;
367          case 11:
368             eliminate_orphaned_fileset_records();
369             break;
370          case 12:
371             eliminate_orphaned_client_records();
372             break;
373          case 13:
374             eliminate_orphaned_job_records();
375             break;
376          case 14:
377             eliminate_admin_records();
378             break;
379          case 15:
380             eliminate_restore_records();
381             break;
382          case 16:
383             repair_bad_filenames();
384             repair_bad_paths();
385             eliminate_duplicate_filenames();
386             eliminate_duplicate_paths();
387             eliminate_orphaned_jobmedia_records();
388             eliminate_orphaned_file_records();
389             eliminate_orphaned_path_records();
390             eliminate_orphaned_filename_records();
391             eliminate_orphaned_fileset_records();
392             eliminate_orphaned_client_records();
393             eliminate_orphaned_job_records();
394             eliminate_admin_records();
395             eliminate_restore_records();
396             break;
397          case 17:
398             quit = true;
399             break;
400          }
401       }
402    }
403 }
404
405 static int print_name_handler(void *ctx, int num_fields, char **row)
406 {
407    if (row[0]) {
408       printf("%s\n", row[0]);
409    }
410    return 0;
411 }
412
413 static int get_name_handler(void *ctx, int num_fields, char **row)
414 {
415    POOLMEM *buf = (POOLMEM *)ctx;
416    if (row[0]) {
417       pm_strcpy(&buf, row[0]);
418    }
419    return 0;
420 }
421
422 static int print_job_handler(void *ctx, int num_fields, char **row)
423 {
424    printf(_("JobId=%s Name=\"%s\" StartTime=%s\n"),
425               NPRT(row[0]), NPRT(row[1]), NPRT(row[2]));
426    return 0;
427 }
428
429
430 static int print_jobmedia_handler(void *ctx, int num_fields, char **row)
431 {
432    printf(_("Orphaned JobMediaId=%s JobId=%s Volume=\"%s\"\n"),
433               NPRT(row[0]), NPRT(row[1]), NPRT(row[2]));
434    return 0;
435 }
436
437 static int print_file_handler(void *ctx, int num_fields, char **row)
438 {
439    printf(_("Orphaned FileId=%s JobId=%s Volume=\"%s\"\n"),
440               NPRT(row[0]), NPRT(row[1]), NPRT(row[2]));
441    return 0;
442 }
443
444 static int print_fileset_handler(void *ctx, int num_fields, char **row)
445 {
446    printf(_("Orphaned FileSetId=%s FileSet=\"%s\" MD5=%s\n"),
447               NPRT(row[0]), NPRT(row[1]), NPRT(row[2]));
448    return 0;
449 }
450
451 static int print_client_handler(void *ctx, int num_fields, char **row)
452 {
453    printf(_("Orphaned ClientId=%s Name=\"%s\"\n"),
454               NPRT(row[0]), NPRT(row[1]));
455    return 0;
456 }
457
458
459 /*
460  * Called here with each id to be added to the list
461  */
462 static int id_list_handler(void *ctx, int num_fields, char **row)
463 {
464    ID_LIST *lst = (ID_LIST *)ctx;
465
466    if (lst->num_ids == MAX_ID_LIST_LEN) {
467       return 1;
468    }
469    if (lst->num_ids == lst->max_ids) {
470       if (lst->max_ids == 0) {
471          lst->max_ids = 10000;
472          lst->Id = (uint32_t *)bmalloc(sizeof(uint32_t) * lst->max_ids);
473       } else {
474          lst->max_ids = (lst->max_ids * 3) / 2;
475          lst->Id = (uint32_t *)brealloc(lst->Id, sizeof(uint32_t) * lst->max_ids);
476       }
477    }
478    lst->Id[lst->num_ids++] = (uint32_t)strtod(row[0], NULL);
479    return 0;
480 }
481
482 /*
483  * Construct record id list
484  */
485 static int make_id_list(const char *query, ID_LIST *id_list)
486 {
487    id_list->num_ids = 0;
488    id_list->num_del = 0;
489    id_list->tot_ids = 0;
490
491    if (!db_sql_query(db, query, id_list_handler, (void *)id_list)) {
492       printf("%s", db_strerror(db));
493       return 0;
494    }
495    return 1;
496 }
497
498 /*
499  * Delete all entries in the list
500  */
501 static int delete_id_list(const char *query, ID_LIST *id_list)
502 {
503    for (int i=0; i < id_list->num_ids; i++) {
504       bsnprintf(buf, sizeof(buf), query, id_list->Id[i]);
505       if (verbose) {
506          printf(_("Deleting: %s\n"), buf);
507       }
508       db_sql_query(db, buf, NULL, NULL);
509    }
510    return 1;
511 }
512
513 /*
514  * Called here with each name to be added to the list
515  */
516 static int name_list_handler(void *ctx, int num_fields, char **row)
517 {
518    NAME_LIST *name = (NAME_LIST *)ctx;
519
520    if (name->num_ids == MAX_ID_LIST_LEN) {
521       return 1;
522    }
523    if (name->num_ids == name->max_ids) {
524       if (name->max_ids == 0) {
525          name->max_ids = 10000;
526          name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
527       } else {
528          name->max_ids = (name->max_ids * 3) / 2;
529          name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
530       }
531    }
532    name->name[name->num_ids++] = bstrdup(row[0]);
533    return 0;
534 }
535
536
537 /*
538  * Construct name list
539  */
540 static int make_name_list(const char *query, NAME_LIST *name_list)
541 {
542    name_list->num_ids = 0;
543    name_list->num_del = 0;
544    name_list->tot_ids = 0;
545
546    if (!db_sql_query(db, query, name_list_handler, (void *)name_list)) {
547       printf("%s", db_strerror(db));
548       return 0;
549    }
550    return 1;
551 }
552
553 /*
554  * Print names in the list
555  */
556 static void print_name_list(NAME_LIST *name_list)
557 {
558    for (int i=0; i < name_list->num_ids; i++) {
559       printf("%s\n", name_list->name[i]);
560    }
561 }
562
563
564 /*
565  * Free names in the list
566  */
567 static void free_name_list(NAME_LIST *name_list)
568 {
569    for (int i=0; i < name_list->num_ids; i++) {
570       free(name_list->name[i]);
571    }
572    name_list->num_ids = 0;
573 }
574
575 static void eliminate_duplicate_filenames()
576 {
577    const char *query;
578    char esc_name[5000];
579
580    printf(_("Checking for duplicate Filename entries.\n"));
581
582    /* Make list of duplicated names */
583    query = "SELECT Name, count(Name) as Count FROM Filename GROUP BY  Name "
584            "HAVING count(Name) > 1";
585
586    if (!make_name_list(query, &name_list)) {
587       exit(1);
588    }
589    printf(_("Found %d duplicate Filename records.\n"), name_list.num_ids);
590    if (name_list.num_ids && verbose && yes_no(_("Print the list? (yes/no): "))) {
591       print_name_list(&name_list);
592    }
593    if (quit) {
594       return;
595    }
596    if (fix) {
597       /* Loop through list of duplicate names */
598       for (int i=0; i<name_list.num_ids; i++) {
599          /* Get all the Ids of each name */
600          db_escape_string(esc_name, name_list.name[i], strlen(name_list.name[i]));
601          bsnprintf(buf, sizeof(buf), "SELECT FilenameId FROM Filename WHERE Name='%s'", esc_name);
602          if (verbose > 1) {
603             printf("%s\n", buf);
604          }
605          if (!make_id_list(buf, &id_list)) {
606             exit(1);
607          }
608          if (verbose) {
609             printf(_("Found %d for: %s\n"), id_list.num_ids, name_list.name[i]);
610          }
611          /* Force all records to use the first id then delete the other ids */
612          for (int j=1; j<id_list.num_ids; j++) {
613             bsnprintf(buf, sizeof(buf), "UPDATE File SET FilenameId=%u WHERE FilenameId=%u",
614                id_list.Id[0], id_list.Id[j]);
615             if (verbose > 1) {
616                printf("%s\n", buf);
617             }
618             db_sql_query(db, buf, NULL, NULL);
619             bsnprintf(buf, sizeof(buf), "DELETE FROM Filename WHERE FilenameId=%u",
620                id_list.Id[j]);
621             if (verbose > 2) {
622                printf("%s\n", buf);
623             }
624             db_sql_query(db, buf, NULL, NULL);
625          }
626       }
627    }
628    free_name_list(&name_list);
629 }
630
631 static void eliminate_duplicate_paths()
632 {
633    const char *query;
634    char esc_name[5000];
635
636    printf(_("Checking for duplicate Path entries.\n"));
637
638    /* Make list of duplicated names */
639
640    query = "SELECT Path, count(Path) as Count FROM Path "
641            "GROUP BY Path HAVING count(Path) > 1";
642
643    if (!make_name_list(query, &name_list)) {
644       exit(1);
645    }
646    printf(_("Found %d duplicate Path records.\n"), name_list.num_ids);
647    if (name_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
648       print_name_list(&name_list);
649    }
650    if (quit) {
651       return;
652    }
653    if (fix) {
654       /* Loop through list of duplicate names */
655       for (int i=0; i<name_list.num_ids; i++) {
656          /* Get all the Ids of each name */
657          db_escape_string(esc_name, name_list.name[i], strlen(name_list.name[i]));
658          bsnprintf(buf, sizeof(buf), "SELECT PathId FROM Path WHERE Path='%s'", esc_name);
659          if (verbose > 1) {
660             printf("%s\n", buf);
661          }
662          if (!make_id_list(buf, &id_list)) {
663             exit(1);
664          }
665          if (verbose) {
666             printf(_("Found %d for: %s\n"), id_list.num_ids, name_list.name[i]);
667          }
668          /* Force all records to use the first id then delete the other ids */
669          for (int j=1; j<id_list.num_ids; j++) {
670             bsnprintf(buf, sizeof(buf), "UPDATE File SET PathId=%u WHERE PathId=%u",
671                id_list.Id[0], id_list.Id[j]);
672             if (verbose > 1) {
673                printf("%s\n", buf);
674             }
675             db_sql_query(db, buf, NULL, NULL);
676             bsnprintf(buf, sizeof(buf), "DELETE FROM Path WHERE PathId=%u",
677                id_list.Id[j]);
678             if (verbose > 2) {
679                printf("%s\n", buf);
680             }
681             db_sql_query(db, buf, NULL, NULL);
682          }
683       }
684    }
685    free_name_list(&name_list);
686 }
687
688 static void eliminate_orphaned_jobmedia_records()
689 {
690    const char *query;
691
692    printf(_("Checking for orphaned JobMedia entries.\n"));
693    query = "SELECT JobMedia.JobMediaId,Job.JobId FROM JobMedia "
694            "LEFT OUTER JOIN Job ON (JobMedia.JobId=Job.JobId) "
695            "WHERE Job.JobId IS NULL";
696    if (!make_id_list(query, &id_list)) {
697       exit(1);
698    }
699    printf(_("Found %d orphaned JobMedia records.\n"), id_list.num_ids);
700    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
701       for (int i=0; i < id_list.num_ids; i++) {
702          bsnprintf(buf, sizeof(buf),
703 "SELECT JobMedia.JobMediaId,JobMedia.JobId,Media.VolumeName FROM JobMedia,Media "
704 "WHERE JobMedia.JobMediaId=%u AND Media.MediaId=JobMedia.MediaId", id_list.Id[i]);
705          if (!db_sql_query(db, buf, print_jobmedia_handler, NULL)) {
706             printf("%s\n", db_strerror(db));
707          }
708       }
709    }
710    if (quit) {
711       return;
712    }
713
714    if (fix && id_list.num_ids > 0) {
715       printf(_("Deleting %d orphaned JobMedia records.\n"), id_list.num_ids);
716       delete_id_list("DELETE FROM JobMedia WHERE JobMediaId=%u", &id_list);
717    }
718 }
719
720 static void eliminate_orphaned_file_records()
721 {
722    const char *query;
723
724    printf(_("Checking for orphaned File entries. This may take some time!\n"));
725    query = "SELECT File.FileId,Job.JobId FROM File "
726            "LEFT OUTER JOIN Job ON (File.JobId=Job.JobId) "
727            "WHERE Job.JobId IS NULL";
728    if (verbose > 1) {
729       printf("%s\n", query);
730    }
731    if (!make_id_list(query, &id_list)) {
732       exit(1);
733    }
734    printf(_("Found %d orphaned File records.\n"), id_list.num_ids);
735    if (name_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
736       for (int i=0; i < id_list.num_ids; i++) {
737          bsnprintf(buf, sizeof(buf),
738 "SELECT File.FileId,File.JobId,Filename.Name FROM File,Filename "
739 "WHERE File.FileId=%u AND File.FilenameId=Filename.FilenameId", id_list.Id[i]);
740          if (!db_sql_query(db, buf, print_file_handler, NULL)) {
741             printf("%s\n", db_strerror(db));
742          }
743       }
744    }
745    if (quit) {
746       return;
747    }
748    if (fix && id_list.num_ids > 0) {
749       printf(_("Deleting %d orphaned File records.\n"), id_list.num_ids);
750       delete_id_list("DELETE FROM File WHERE FileId=%u", &id_list);
751    }
752 }
753
754 static void eliminate_orphaned_path_records()
755 {
756    const char *query;
757
758    printf(_("Checking for orphaned Path entries. This may take some time!\n"));
759    query = "SELECT DISTINCT Path.PathId,File.PathId FROM Path "
760            "LEFT OUTER JOIN File ON (Path.PathId=File.PathId) "
761            "WHERE File.PathId IS NULL";
762    if (verbose > 1) {
763       printf("%s\n", query);
764    }
765    if (!make_id_list(query, &id_list)) {
766       exit(1);
767    }
768    printf(_("Found %d orphaned Path records.\n"), id_list.num_ids);
769    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
770       for (int i=0; i < id_list.num_ids; i++) {
771          bsnprintf(buf, sizeof(buf), "SELECT Path FROM Path WHERE PathId=%u", id_list.Id[i]);
772          db_sql_query(db, buf, print_name_handler, NULL);
773       }
774    }
775    if (quit) {
776       return;
777    }
778    if (fix && id_list.num_ids > 0) {
779       printf(_("Deleting %d orphaned Path records.\n"), id_list.num_ids);
780       delete_id_list("DELETE FROM Path WHERE PathId=%u", &id_list);
781    }
782 }
783
784 static void eliminate_orphaned_filename_records()
785 {
786    const char *query;
787
788    printf(_("Checking for orphaned Filename entries. This may take some time!\n"));
789    query = "SELECT Filename.FilenameId,File.FilenameId FROM Filename "
790            "LEFT OUTER JOIN File ON (Filename.FilenameId=File.FilenameId) "
791            "WHERE File.FilenameId IS NULL";
792    if (verbose > 1) {
793       printf("%s\n", query);
794    }
795    if (!make_id_list(query, &id_list)) {
796       exit(1);
797    }
798    printf(_("Found %d orphaned Filename records.\n"), id_list.num_ids);
799    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
800       for (int i=0; i < id_list.num_ids; i++) {
801          bsnprintf(buf, sizeof(buf), "SELECT Name FROM Filename WHERE FilenameId=%u", id_list.Id[i]);
802          db_sql_query(db, buf, print_name_handler, NULL);
803       }
804    }
805    if (quit) {
806       return;
807    }
808    if (fix && id_list.num_ids > 0) {
809       printf(_("Deleting %d orphaned Filename records.\n"), id_list.num_ids);
810       delete_id_list("DELETE FROM Filename WHERE FilenameId=%u", &id_list);
811    }
812 }
813
814 static void eliminate_orphaned_fileset_records()
815 {
816    const char *query;
817
818    printf(_("Checking for orphaned FileSet entries. This takes some time!\n"));
819    query = "SELECT FileSet.FileSetId,Job.FileSetId FROM FileSet "
820            "LEFT OUTER JOIN Job ON (FileSet.FileSetId=Job.FileSetId) "
821            "WHERE Job.FileSetId IS NULL";
822    if (verbose > 1) {
823       printf("%s\n", query);
824    }
825    if (!make_id_list(query, &id_list)) {
826       exit(1);
827    }
828    printf(_("Found %d orphaned FileSet records.\n"), id_list.num_ids);
829    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
830       for (int i=0; i < id_list.num_ids; i++) {
831          bsnprintf(buf, sizeof(buf), "SELECT FileSetId,FileSet,MD5 FROM FileSet "
832                       "WHERE FileSetId=%u", id_list.Id[i]);
833          if (!db_sql_query(db, buf, print_fileset_handler, NULL)) {
834             printf("%s\n", db_strerror(db));
835          }
836       }
837    }
838    if (quit) {
839       return;
840    }
841    if (fix && id_list.num_ids > 0) {
842       printf(_("Deleting %d orphaned FileSet records.\n"), id_list.num_ids);
843       delete_id_list("DELETE FROM FileSet WHERE FileSetId=%u", &id_list);
844    }
845 }
846
847 static void eliminate_orphaned_client_records()
848 {
849    const char *query;
850
851    printf(_("Checking for orphaned Client entries.\n"));
852    /* In English:
853     *   Wiffle through Client for every Client
854     *   joining with the Job table including every Client even if
855     *   there is not a match in Job (left outer join), then
856     *   filter out only those where no Job points to a Client
857     *   i.e. Job.Client is NULL
858     */
859    query = "SELECT Client.ClientId,Client.Name FROM Client "
860            "LEFT OUTER JOIN Job ON (Client.ClientId=Job.ClientId) "
861            "WHERE Job.ClientId IS NULL";
862    if (verbose > 1) {
863       printf("%s\n", query);
864    }
865    if (!make_id_list(query, &id_list)) {
866       exit(1);
867    }
868    printf(_("Found %d orphaned Client records.\n"), id_list.num_ids);
869    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
870       for (int i=0; i < id_list.num_ids; i++) {
871          bsnprintf(buf, sizeof(buf), "SELECT ClientId,Name FROM Client "
872                       "WHERE ClientId=%u", id_list.Id[i]);
873          if (!db_sql_query(db, buf, print_client_handler, NULL)) {
874             printf("%s\n", db_strerror(db));
875          }
876       }
877    }
878    if (quit) {
879       return;
880    }
881    if (fix && id_list.num_ids > 0) {
882       printf(_("Deleting %d orphaned Client records.\n"), id_list.num_ids);
883       delete_id_list("DELETE FROM Client WHERE ClientId=%u", &id_list);
884    }
885 }
886
887 static void eliminate_orphaned_job_records()
888 {
889    const char *query;
890
891    printf(_("Checking for orphaned Job entries.\n"));
892    /* In English:
893     *   Wiffle through Job for every Job
894     *   joining with the Client table including every Job even if
895     *   there is not a match in Client (left outer join), then
896     *   filter out only those where no Client exists
897     *   i.e. Client.Name is NULL
898     */
899    query = "SELECT Job.JobId,Job.Name FROM Job "
900            "LEFT OUTER JOIN Client ON (Job.ClientId=Client.ClientId) "
901            "WHERE Client.Name IS NULL";
902    if (verbose > 1) {
903       printf("%s\n", query);
904    }
905    if (!make_id_list(query, &id_list)) {
906       exit(1);
907    }
908    printf(_("Found %d orphaned Job records.\n"), id_list.num_ids);
909    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
910       for (int i=0; i < id_list.num_ids; i++) {
911          bsnprintf(buf, sizeof(buf), "SELECT JobId,Name,StartTime FROM Job "
912                       "WHERE JobId=%u", id_list.Id[i]);
913          if (!db_sql_query(db, buf, print_job_handler, NULL)) {
914             printf("%s\n", db_strerror(db));
915          }
916       }
917    }
918    if (quit) {
919       return;
920    }
921    if (fix && id_list.num_ids > 0) {
922       printf(_("Deleting %d orphaned Job records.\n"), id_list.num_ids);
923       delete_id_list("DELETE FROM Job WHERE JobId=%u", &id_list);
924       printf(_("Deleting JobMedia records of orphaned Job records.\n"));
925       delete_id_list("DELETE FROM JobMedia WHERE JobId=%u", &id_list);
926       printf(_("Deleting Log records of orphaned Job records.\n"));
927       delete_id_list("DELETE FROM Log WHERE JobId=%u", &id_list);
928    }
929 }
930
931
932 static void eliminate_admin_records()
933 {
934    const char *query;
935
936    printf(_("Checking for Admin Job entries.\n"));
937    query = "SELECT Job.JobId FROM Job "
938            "WHERE Job.Type='D'";
939    if (verbose > 1) {
940       printf("%s\n", query);
941    }
942    if (!make_id_list(query, &id_list)) {
943       exit(1);
944    }
945    printf(_("Found %d Admin Job records.\n"), id_list.num_ids);
946    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
947       for (int i=0; i < id_list.num_ids; i++) {
948          bsnprintf(buf, sizeof(buf), "SELECT JobId,Name,StartTime FROM Job "
949                       "WHERE JobId=%u", id_list.Id[i]);
950          if (!db_sql_query(db, buf, print_job_handler, NULL)) {
951             printf("%s\n", db_strerror(db));
952          }
953       }
954    }
955    if (quit) {
956       return;
957    }
958    if (fix && id_list.num_ids > 0) {
959       printf(_("Deleting %d Admin Job records.\n"), id_list.num_ids);
960       delete_id_list("DELETE FROM Job WHERE JobId=%u", &id_list);
961    }
962 }
963
964 static void eliminate_restore_records()
965 {
966    const char *query;
967
968    printf(_("Checking for Restore Job entries.\n"));
969    query = "SELECT Job.JobId FROM Job "
970            "WHERE Job.Type='R'";
971    if (verbose > 1) {
972       printf("%s\n", query);
973    }
974    if (!make_id_list(query, &id_list)) {
975       exit(1);
976    }
977    printf(_("Found %d Restore Job records.\n"), id_list.num_ids);
978    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
979       for (int i=0; i < id_list.num_ids; i++) {
980          bsnprintf(buf, sizeof(buf), "SELECT JobId,Name,StartTime FROM Job "
981                       "WHERE JobId=%u", id_list.Id[i]);
982          if (!db_sql_query(db, buf, print_job_handler, NULL)) {
983             printf("%s\n", db_strerror(db));
984          }
985       }
986    }
987    if (quit) {
988       return;
989    }
990    if (fix && id_list.num_ids > 0) {
991       printf(_("Deleting %d Restore Job records.\n"), id_list.num_ids);
992       delete_id_list("DELETE FROM Job WHERE JobId=%u", &id_list);
993    }
994 }
995
996
997
998
999 static void repair_bad_filenames()
1000 {
1001    const char *query;
1002    int i;
1003
1004    printf(_("Checking for Filenames with a trailing slash\n"));
1005    query = "SELECT FilenameId,Name from Filename "
1006            "WHERE Name LIKE '%/'";
1007    if (verbose > 1) {
1008       printf("%s\n", query);
1009    }
1010    if (!make_id_list(query, &id_list)) {
1011       exit(1);
1012    }
1013    printf(_("Found %d bad Filename records.\n"), id_list.num_ids);
1014    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
1015       for (i=0; i < id_list.num_ids; i++) {
1016          bsnprintf(buf, sizeof(buf),
1017             "SELECT Name FROM Filename WHERE FilenameId=%u", id_list.Id[i]);
1018          if (!db_sql_query(db, buf, print_name_handler, NULL)) {
1019             printf("%s\n", db_strerror(db));
1020          }
1021       }
1022    }
1023    if (quit) {
1024       return;
1025    }
1026    if (fix && id_list.num_ids > 0) {
1027       POOLMEM *name = get_pool_memory(PM_FNAME);
1028       char esc_name[5000];
1029       printf(_("Reparing %d bad Filename records.\n"), id_list.num_ids);
1030       for (i=0; i < id_list.num_ids; i++) {
1031          int len;
1032          bsnprintf(buf, sizeof(buf),
1033             "SELECT Name FROM Filename WHERE FilenameId=%u", id_list.Id[i]);
1034          if (!db_sql_query(db, buf, get_name_handler, name)) {
1035             printf("%s\n", db_strerror(db));
1036          }
1037          /* Strip trailing slash(es) */
1038          for (len=strlen(name); len > 0 && IsPathSeparator(name[len-1]); len--)
1039             {  }
1040          if (len == 0) {
1041             len = 1;
1042             esc_name[0] = ' ';
1043             esc_name[1] = 0;
1044          } else {
1045             name[len-1] = 0;
1046             db_escape_string(esc_name, name, len);
1047          }
1048          bsnprintf(buf, sizeof(buf),
1049             "UPDATE Filename SET Name='%s' WHERE FilenameId=%u",
1050             esc_name, id_list.Id[i]);
1051          if (verbose > 1) {
1052             printf("%s\n", buf);
1053          }
1054          db_sql_query(db, buf, NULL, NULL);
1055       }
1056    }
1057 }
1058
1059 static void repair_bad_paths()
1060 {
1061    const char *query;
1062    int i;
1063
1064    printf(_("Checking for Paths without a trailing slash\n"));
1065    query = "SELECT PathId,Path from Path "
1066            "WHERE Path NOT LIKE '%/'";
1067    if (verbose > 1) {
1068       printf("%s\n", query);
1069    }
1070    if (!make_id_list(query, &id_list)) {
1071       exit(1);
1072    }
1073    printf(_("Found %d bad Path records.\n"), id_list.num_ids);
1074    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
1075       for (i=0; i < id_list.num_ids; i++) {
1076          bsnprintf(buf, sizeof(buf),
1077             "SELECT Path FROM Path WHERE PathId=%u", id_list.Id[i]);
1078          if (!db_sql_query(db, buf, print_name_handler, NULL)) {
1079             printf("%s\n", db_strerror(db));
1080          }
1081       }
1082    }
1083    if (quit) {
1084       return;
1085    }
1086    if (fix && id_list.num_ids > 0) {
1087       POOLMEM *name = get_pool_memory(PM_FNAME);
1088       char esc_name[5000];
1089       printf(_("Reparing %d bad Filename records.\n"), id_list.num_ids);
1090       for (i=0; i < id_list.num_ids; i++) {
1091          int len;
1092          bsnprintf(buf, sizeof(buf),
1093             "SELECT Path FROM Path WHERE PathId=%u", id_list.Id[i]);
1094          if (!db_sql_query(db, buf, get_name_handler, name)) {
1095             printf("%s\n", db_strerror(db));
1096          }
1097          /* Strip trailing blanks */
1098          for (len=strlen(name); len > 0 && name[len-1]==' '; len--) {
1099             name[len-1] = 0;
1100          }
1101          /* Add trailing slash */
1102          len = pm_strcat(&name, "/");
1103          db_escape_string(esc_name, name, len);
1104          bsnprintf(buf, sizeof(buf), "UPDATE Path SET Path='%s' WHERE PathId=%u",
1105             esc_name, id_list.Id[i]);
1106          if (verbose > 1) {
1107             printf("%s\n", buf);
1108          }
1109          db_sql_query(db, buf, NULL, NULL);
1110       }
1111    }
1112 }
1113
1114
1115 /*
1116  * Gen next input command from the terminal
1117  */
1118 static char *get_cmd(const char *prompt)
1119 {
1120    static char cmd[1000];
1121
1122    printf("%s", prompt);
1123    if (fgets(cmd, sizeof(cmd), stdin) == NULL) {
1124       printf("\n");
1125       quit = true;
1126       return NULL;
1127    }
1128    strip_trailing_junk(cmd);
1129    return cmd;
1130 }
1131
1132 static bool yes_no(const char *prompt)
1133 {
1134    char *cmd;
1135    cmd = get_cmd(prompt);
1136    if (!cmd) {
1137       quit = true;
1138       return false;
1139    }
1140    return (strcasecmp(cmd, "yes") == 0) || (strcasecmp(cmd, _("yes")) == 0);
1141 }
1142
1143 bool python_set_prog(JCR*, char const*) { return false; }