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