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