]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/tools/dbcheck.c
Correct VolCatBytes bug from media patch
[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-2007 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    char ed1[50];
517    for (int i=0; i < id_list->num_ids; i++) {
518       bsnprintf(buf, sizeof(buf), query, edit_int64(id_list->Id[i], ed1));
519       if (verbose) {
520          printf(_("Deleting: %s\n"), buf);
521       }
522       db_sql_query(db, buf, NULL, NULL);
523    }
524    return 1;
525 }
526
527 /*
528  * Called here with each name to be added to the list
529  */
530 static int name_list_handler(void *ctx, int num_fields, char **row)
531 {
532    NAME_LIST *name = (NAME_LIST *)ctx;
533
534    if (name->num_ids == MAX_ID_LIST_LEN) {
535       return 1;
536    }
537    if (name->num_ids == name->max_ids) {
538       if (name->max_ids == 0) {
539          name->max_ids = 10000;
540          name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
541       } else {
542          name->max_ids = (name->max_ids * 3) / 2;
543          name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
544       }
545    }
546    name->name[name->num_ids++] = bstrdup(row[0]);
547    return 0;
548 }
549
550
551 /*
552  * Construct name list
553  */
554 static int make_name_list(const char *query, NAME_LIST *name_list)
555 {
556    name_list->num_ids = 0;
557    name_list->num_del = 0;
558    name_list->tot_ids = 0;
559
560    if (!db_sql_query(db, query, name_list_handler, (void *)name_list)) {
561       printf("%s", db_strerror(db));
562       return 0;
563    }
564    return 1;
565 }
566
567 /*
568  * Print names in the list
569  */
570 static void print_name_list(NAME_LIST *name_list)
571 {
572    for (int i=0; i < name_list->num_ids; i++) {
573       printf("%s\n", name_list->name[i]);
574    }
575 }
576
577
578 /*
579  * Free names in the list
580  */
581 static void free_name_list(NAME_LIST *name_list)
582 {
583    for (int i=0; i < name_list->num_ids; i++) {
584       free(name_list->name[i]);
585    }
586    name_list->num_ids = 0;
587 }
588
589 static void eliminate_duplicate_filenames()
590 {
591    const char *query;
592    char esc_name[5000];
593
594    printf(_("Checking for duplicate Filename entries.\n"));
595
596    /* Make list of duplicated names */
597    query = "SELECT Name, count(Name) as Count FROM Filename GROUP BY  Name "
598            "HAVING count(Name) > 1";
599
600    if (!make_name_list(query, &name_list)) {
601       exit(1);
602    }
603    printf(_("Found %d duplicate Filename records.\n"), name_list.num_ids);
604    if (name_list.num_ids && verbose && yes_no(_("Print the list? (yes/no): "))) {
605       print_name_list(&name_list);
606    }
607    if (quit) {
608       return;
609    }
610    if (fix) {
611       /* Loop through list of duplicate names */
612       for (int i=0; i<name_list.num_ids; i++) {
613          /* Get all the Ids of each name */
614          db_escape_string(esc_name, name_list.name[i], strlen(name_list.name[i]));
615          bsnprintf(buf, sizeof(buf), "SELECT FilenameId FROM Filename WHERE Name='%s'", esc_name);
616          if (verbose > 1) {
617             printf("%s\n", buf);
618          }
619          if (!make_id_list(buf, &id_list)) {
620             exit(1);
621          }
622          if (verbose) {
623             printf(_("Found %d for: %s\n"), id_list.num_ids, name_list.name[i]);
624          }
625          /* Force all records to use the first id then delete the other ids */
626          for (int j=1; j<id_list.num_ids; j++) {
627             char ed1[50], ed2[50];
628             bsnprintf(buf, sizeof(buf), "UPDATE File SET FilenameId=%s WHERE FilenameId=%s",
629                edit_int64(id_list.Id[0], ed1), edit_int64(id_list.Id[j], ed2));
630             if (verbose > 1) {
631                printf("%s\n", buf);
632             }
633             db_sql_query(db, buf, NULL, NULL);
634             bsnprintf(buf, sizeof(buf), "DELETE FROM Filename WHERE FilenameId=%s",
635                ed2);
636             if (verbose > 2) {
637                printf("%s\n", buf);
638             }
639             db_sql_query(db, buf, NULL, NULL);
640          }
641       }
642    }
643    free_name_list(&name_list);
644 }
645
646 static void eliminate_duplicate_paths()
647 {
648    const char *query;
649    char esc_name[5000];
650
651    printf(_("Checking for duplicate Path entries.\n"));
652
653    /* Make list of duplicated names */
654
655    query = "SELECT Path, count(Path) as Count FROM Path "
656            "GROUP BY Path HAVING count(Path) > 1";
657
658    if (!make_name_list(query, &name_list)) {
659       exit(1);
660    }
661    printf(_("Found %d duplicate Path records.\n"), name_list.num_ids);
662    if (name_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
663       print_name_list(&name_list);
664    }
665    if (quit) {
666       return;
667    }
668    if (fix) {
669       /* Loop through list of duplicate names */
670       for (int i=0; i<name_list.num_ids; i++) {
671          /* Get all the Ids of each name */
672          db_escape_string(esc_name, name_list.name[i], strlen(name_list.name[i]));
673          bsnprintf(buf, sizeof(buf), "SELECT PathId FROM Path WHERE Path='%s'", esc_name);
674          if (verbose > 1) {
675             printf("%s\n", buf);
676          }
677          if (!make_id_list(buf, &id_list)) {
678             exit(1);
679          }
680          if (verbose) {
681             printf(_("Found %d for: %s\n"), id_list.num_ids, name_list.name[i]);
682          }
683          /* Force all records to use the first id then delete the other ids */
684          for (int j=1; j<id_list.num_ids; j++) {
685             char ed1[50], ed2[50];
686             bsnprintf(buf, sizeof(buf), "UPDATE File SET PathId=%s WHERE PathId=%s",
687                edit_int64(id_list.Id[0], ed1), edit_int64(id_list.Id[j], ed2));
688             if (verbose > 1) {
689                printf("%s\n", buf);
690             }
691             db_sql_query(db, buf, NULL, NULL);
692             bsnprintf(buf, sizeof(buf), "DELETE FROM Path WHERE PathId=%s", ed2);
693             if (verbose > 2) {
694                printf("%s\n", buf);
695             }
696             db_sql_query(db, buf, NULL, NULL);
697          }
698       }
699    }
700    free_name_list(&name_list);
701 }
702
703 static void eliminate_orphaned_jobmedia_records()
704 {
705    const char *query = "SELECT JobMedia.JobMediaId,Job.JobId FROM JobMedia "
706                 "LEFT OUTER JOIN Job ON (JobMedia.JobId=Job.JobId) "
707                 "WHERE Job.JobId IS NULL LIMIT 300000";
708
709    printf(_("Checking for orphaned JobMedia entries.\n"));
710    if (!make_id_list(query, &id_list)) {
711       exit(1);
712    }
713    /* Loop doing 300000 at a time */
714    while (id_list.num_ids != 0) {
715       printf(_("Found %d orphaned JobMedia records.\n"), id_list.num_ids);
716       if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
717          for (int i=0; i < id_list.num_ids; i++) {
718             char ed1[50];
719             bsnprintf(buf, sizeof(buf),
720 "SELECT JobMedia.JobMediaId,JobMedia.JobId,Media.VolumeName FROM JobMedia,Media "
721 "WHERE JobMedia.JobMediaId=%s AND Media.MediaId=JobMedia.MediaId", 
722                edit_int64(id_list.Id[i], ed1));
723             if (!db_sql_query(db, buf, print_jobmedia_handler, NULL)) {
724                printf("%s\n", db_strerror(db));
725             }
726          }
727       }
728       if (quit) {
729          return;
730       }
731
732       if (fix && id_list.num_ids > 0) {
733          printf(_("Deleting %d orphaned JobMedia records.\n"), id_list.num_ids);
734          delete_id_list("DELETE FROM JobMedia WHERE JobMediaId=%s", &id_list);
735       }
736       if (!make_id_list(query, &id_list)) {
737          exit(1);
738       }
739    }
740 }
741
742 static void eliminate_orphaned_file_records()
743 {
744    const char *query = "SELECT File.FileId,Job.JobId FROM File "
745                 "LEFT OUTER JOIN Job ON (File.JobId=Job.JobId) "
746                "WHERE Job.JobId IS NULL LIMIT 300000";
747
748    printf(_("Checking for orphaned File entries. This may take some time!\n"));
749    if (verbose > 1) {
750       printf("%s\n", query);
751    }
752    if (!make_id_list(query, &id_list)) {
753       exit(1);
754    }
755    /* Loop doing 300000 at a time */
756    while (id_list.num_ids != 0) {
757       printf(_("Found %d orphaned File records.\n"), id_list.num_ids);
758       if (name_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
759          for (int i=0; i < id_list.num_ids; i++) {
760             char ed1[50];
761             bsnprintf(buf, sizeof(buf),
762 "SELECT File.FileId,File.JobId,Filename.Name FROM File,Filename "
763 "WHERE File.FileId=%s AND File.FilenameId=Filename.FilenameId", 
764                edit_int64(id_list.Id[i], ed1));
765             if (!db_sql_query(db, buf, print_file_handler, NULL)) {
766                printf("%s\n", db_strerror(db));
767             }
768          }
769       }
770       if (quit) {
771          return;
772       }
773       if (fix && id_list.num_ids > 0) {
774          printf(_("Deleting %d orphaned File records.\n"), id_list.num_ids);
775          delete_id_list("DELETE FROM File WHERE FileId=%s", &id_list);
776       }
777       if (!make_id_list(query, &id_list)) {
778          exit(1);
779       }
780    }
781 }
782
783 static void eliminate_orphaned_path_records()
784 {
785    const char *query = "SELECT DISTINCT Path.PathId,File.PathId FROM Path "
786                "LEFT OUTER JOIN File ON (Path.PathId=File.PathId) "
787                "WHERE File.PathId IS NULL LIMIT 300000";
788
789    printf(_("Checking for orphaned Path entries. This may take some time!\n"));
790    if (verbose > 1) {
791       printf("%s\n", query);
792    }
793    if (!make_id_list(query, &id_list)) {
794       exit(1);
795    }
796    /* Loop doing 300000 at a time */
797    while (id_list.num_ids != 0) {
798       printf(_("Found %d orphaned Path 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             char ed1[50];
802             bsnprintf(buf, sizeof(buf), "SELECT Path FROM Path WHERE PathId=%s", 
803                edit_int64(id_list.Id[i], ed1));
804             db_sql_query(db, buf, print_name_handler, NULL);
805          }
806       }
807       if (quit) {
808          return;
809       }
810       if (fix && id_list.num_ids > 0) {
811          printf(_("Deleting %d orphaned Path records.\n"), id_list.num_ids);
812          delete_id_list("DELETE FROM Path WHERE PathId=%s", &id_list);
813       }
814       if (!make_id_list(query, &id_list)) {
815          exit(1);
816       }
817    }
818 }
819
820 static void eliminate_orphaned_filename_records()
821 {
822    const char *query = "SELECT Filename.FilenameId,File.FilenameId FROM Filename "
823                 "LEFT OUTER JOIN File ON (Filename.FilenameId=File.FilenameId) "
824                 "WHERE File.FilenameId IS NULL LIMIT 300000";
825
826    printf(_("Checking for orphaned Filename entries. This may take some time!\n"));
827    if (verbose > 1) {
828       printf("%s\n", query);
829    }
830    if (!make_id_list(query, &id_list)) {
831       exit(1);
832    }
833    /* Loop doing 300000 at a time */
834    while (id_list.num_ids != 0) {
835       printf(_("Found %d orphaned Filename records.\n"), id_list.num_ids);
836       if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
837          for (int i=0; i < id_list.num_ids; i++) {
838             char ed1[50];
839             bsnprintf(buf, sizeof(buf), "SELECT Name FROM Filename WHERE FilenameId=%s", 
840                edit_int64(id_list.Id[i], ed1));
841             db_sql_query(db, buf, print_name_handler, NULL);
842          }
843       }
844       if (quit) {
845          return;
846       }
847       if (fix && id_list.num_ids > 0) {
848          printf(_("Deleting %d orphaned Filename records.\n"), id_list.num_ids);
849          delete_id_list("DELETE FROM Filename WHERE FilenameId=%s", &id_list);
850       }
851       if (!make_id_list(query, &id_list)) {
852          exit(1);
853       }
854    }
855 }
856
857 static void eliminate_orphaned_fileset_records()
858 {
859    const char *query;
860
861    printf(_("Checking for orphaned FileSet entries. This takes some time!\n"));
862    query = "SELECT FileSet.FileSetId,Job.FileSetId FROM FileSet "
863            "LEFT OUTER JOIN Job ON (FileSet.FileSetId=Job.FileSetId) "
864            "WHERE Job.FileSetId IS NULL";
865    if (verbose > 1) {
866       printf("%s\n", query);
867    }
868    if (!make_id_list(query, &id_list)) {
869       exit(1);
870    }
871    printf(_("Found %d orphaned FileSet records.\n"), id_list.num_ids);
872    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
873       for (int i=0; i < id_list.num_ids; i++) {
874          char ed1[50];
875          bsnprintf(buf, sizeof(buf), "SELECT FileSetId,FileSet,MD5 FROM FileSet "
876                       "WHERE FileSetId=%s", edit_int64(id_list.Id[i], ed1));
877          if (!db_sql_query(db, buf, print_fileset_handler, NULL)) {
878             printf("%s\n", db_strerror(db));
879          }
880       }
881    }
882    if (quit) {
883       return;
884    }
885    if (fix && id_list.num_ids > 0) {
886       printf(_("Deleting %d orphaned FileSet records.\n"), id_list.num_ids);
887       delete_id_list("DELETE FROM FileSet WHERE FileSetId=%s", &id_list);
888    }
889 }
890
891 static void eliminate_orphaned_client_records()
892 {
893    const char *query;
894
895    printf(_("Checking for orphaned Client entries.\n"));
896    /* In English:
897     *   Wiffle through Client for every Client
898     *   joining with the Job table including every Client even if
899     *   there is not a match in Job (left outer join), then
900     *   filter out only those where no Job points to a Client
901     *   i.e. Job.Client is NULL
902     */
903    query = "SELECT Client.ClientId,Client.Name FROM Client "
904            "LEFT OUTER JOIN Job ON (Client.ClientId=Job.ClientId) "
905            "WHERE Job.ClientId IS NULL";
906    if (verbose > 1) {
907       printf("%s\n", query);
908    }
909    if (!make_id_list(query, &id_list)) {
910       exit(1);
911    }
912    printf(_("Found %d orphaned Client records.\n"), id_list.num_ids);
913    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
914       for (int i=0; i < id_list.num_ids; i++) {
915          char ed1[50];
916          bsnprintf(buf, sizeof(buf), "SELECT ClientId,Name FROM Client "
917                       "WHERE ClientId=%s", edit_int64(id_list.Id[i], ed1));
918          if (!db_sql_query(db, buf, print_client_handler, NULL)) {
919             printf("%s\n", db_strerror(db));
920          }
921       }
922    }
923    if (quit) {
924       return;
925    }
926    if (fix && id_list.num_ids > 0) {
927       printf(_("Deleting %d orphaned Client records.\n"), id_list.num_ids);
928       delete_id_list("DELETE FROM Client WHERE ClientId=%s", &id_list);
929    }
930 }
931
932 static void eliminate_orphaned_job_records()
933 {
934    const char *query;
935
936    printf(_("Checking for orphaned Job entries.\n"));
937    /* In English:
938     *   Wiffle through Job for every Job
939     *   joining with the Client table including every Job even if
940     *   there is not a match in Client (left outer join), then
941     *   filter out only those where no Client exists
942     *   i.e. Client.Name is NULL
943     */
944    query = "SELECT Job.JobId,Job.Name FROM Job "
945            "LEFT OUTER JOIN Client ON (Job.ClientId=Client.ClientId) "
946            "WHERE Client.Name IS NULL";
947    if (verbose > 1) {
948       printf("%s\n", query);
949    }
950    if (!make_id_list(query, &id_list)) {
951       exit(1);
952    }
953    printf(_("Found %d orphaned Job records.\n"), id_list.num_ids);
954    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
955       for (int i=0; i < id_list.num_ids; i++) {
956          char ed1[50];
957          bsnprintf(buf, sizeof(buf), "SELECT JobId,Name,StartTime FROM Job "
958                       "WHERE JobId=%s", edit_int64(id_list.Id[i], ed1));
959          if (!db_sql_query(db, buf, print_job_handler, NULL)) {
960             printf("%s\n", db_strerror(db));
961          }
962       }
963    }
964    if (quit) {
965       return;
966    }
967    if (fix && id_list.num_ids > 0) {
968       printf(_("Deleting %d orphaned Job records.\n"), id_list.num_ids);
969       delete_id_list("DELETE FROM Job WHERE JobId=%s", &id_list);
970       printf(_("Deleting JobMedia records of orphaned Job records.\n"));
971       delete_id_list("DELETE FROM JobMedia WHERE JobId=%s", &id_list);
972       printf(_("Deleting Log records of orphaned Job records.\n"));
973       delete_id_list("DELETE FROM Log WHERE JobId=%s", &id_list);
974    }
975 }
976
977
978 static void eliminate_admin_records()
979 {
980    const char *query;
981
982    printf(_("Checking for Admin Job entries.\n"));
983    query = "SELECT Job.JobId FROM Job "
984            "WHERE Job.Type='D'";
985    if (verbose > 1) {
986       printf("%s\n", query);
987    }
988    if (!make_id_list(query, &id_list)) {
989       exit(1);
990    }
991    printf(_("Found %d Admin Job records.\n"), id_list.num_ids);
992    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
993       for (int i=0; i < id_list.num_ids; i++) {
994          char ed1[50];
995          bsnprintf(buf, sizeof(buf), "SELECT JobId,Name,StartTime FROM Job "
996                       "WHERE JobId=%s", edit_int64(id_list.Id[i], ed1));
997          if (!db_sql_query(db, buf, print_job_handler, NULL)) {
998             printf("%s\n", db_strerror(db));
999          }
1000       }
1001    }
1002    if (quit) {
1003       return;
1004    }
1005    if (fix && id_list.num_ids > 0) {
1006       printf(_("Deleting %d Admin Job records.\n"), id_list.num_ids);
1007       delete_id_list("DELETE FROM Job WHERE JobId=%s", &id_list);
1008    }
1009 }
1010
1011 static void eliminate_restore_records()
1012 {
1013    const char *query;
1014
1015    printf(_("Checking for Restore Job entries.\n"));
1016    query = "SELECT Job.JobId FROM Job "
1017            "WHERE Job.Type='R'";
1018    if (verbose > 1) {
1019       printf("%s\n", query);
1020    }
1021    if (!make_id_list(query, &id_list)) {
1022       exit(1);
1023    }
1024    printf(_("Found %d Restore Job records.\n"), id_list.num_ids);
1025    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
1026       for (int i=0; i < id_list.num_ids; i++) {
1027          char ed1[50];
1028          bsnprintf(buf, sizeof(buf), "SELECT JobId,Name,StartTime FROM Job "
1029                       "WHERE JobId=%s", edit_int64(id_list.Id[i], ed1));
1030          if (!db_sql_query(db, buf, print_job_handler, NULL)) {
1031             printf("%s\n", db_strerror(db));
1032          }
1033       }
1034    }
1035    if (quit) {
1036       return;
1037    }
1038    if (fix && id_list.num_ids > 0) {
1039       printf(_("Deleting %d Restore Job records.\n"), id_list.num_ids);
1040       delete_id_list("DELETE FROM Job WHERE JobId=%s", &id_list);
1041    }
1042 }
1043
1044
1045
1046
1047 static void repair_bad_filenames()
1048 {
1049    const char *query;
1050    int i;
1051
1052    printf(_("Checking for Filenames with a trailing slash\n"));
1053    query = "SELECT FilenameId,Name from Filename "
1054            "WHERE Name LIKE '%/'";
1055    if (verbose > 1) {
1056       printf("%s\n", query);
1057    }
1058    if (!make_id_list(query, &id_list)) {
1059       exit(1);
1060    }
1061    printf(_("Found %d bad Filename records.\n"), id_list.num_ids);
1062    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
1063       for (i=0; i < id_list.num_ids; i++) {
1064          char ed1[50];
1065          bsnprintf(buf, sizeof(buf),
1066             "SELECT Name FROM Filename WHERE FilenameId=%s", 
1067                 edit_int64(id_list.Id[i], ed1));
1068          if (!db_sql_query(db, buf, print_name_handler, NULL)) {
1069             printf("%s\n", db_strerror(db));
1070          }
1071       }
1072    }
1073    if (quit) {
1074       return;
1075    }
1076    if (fix && id_list.num_ids > 0) {
1077       POOLMEM *name = get_pool_memory(PM_FNAME);
1078       char esc_name[5000];
1079       printf(_("Reparing %d bad Filename records.\n"), id_list.num_ids);
1080       for (i=0; i < id_list.num_ids; i++) {
1081          int len;
1082          char ed1[50];
1083          bsnprintf(buf, sizeof(buf),
1084             "SELECT Name FROM Filename WHERE FilenameId=%s", 
1085                edit_int64(id_list.Id[i], ed1));
1086          if (!db_sql_query(db, buf, get_name_handler, name)) {
1087             printf("%s\n", db_strerror(db));
1088          }
1089          /* Strip trailing slash(es) */
1090          for (len=strlen(name); len > 0 && IsPathSeparator(name[len-1]); len--)
1091             {  }
1092          if (len == 0) {
1093             len = 1;
1094             esc_name[0] = ' ';
1095             esc_name[1] = 0;
1096          } else {
1097             name[len-1] = 0;
1098             db_escape_string(esc_name, name, len);
1099          }
1100          bsnprintf(buf, sizeof(buf),
1101             "UPDATE Filename SET Name='%s' WHERE FilenameId=%s",
1102             esc_name, edit_int64(id_list.Id[i], ed1));
1103          if (verbose > 1) {
1104             printf("%s\n", buf);
1105          }
1106          db_sql_query(db, buf, NULL, NULL);
1107       }
1108    }
1109 }
1110
1111 static void repair_bad_paths()
1112 {
1113    const char *query;
1114    int i;
1115
1116    printf(_("Checking for Paths without a trailing slash\n"));
1117    query = "SELECT PathId,Path from Path "
1118            "WHERE Path NOT LIKE '%/'";
1119    if (verbose > 1) {
1120       printf("%s\n", query);
1121    }
1122    if (!make_id_list(query, &id_list)) {
1123       exit(1);
1124    }
1125    printf(_("Found %d bad Path records.\n"), id_list.num_ids);
1126    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
1127       for (i=0; i < id_list.num_ids; i++) {
1128          char ed1[50];
1129          bsnprintf(buf, sizeof(buf),
1130             "SELECT Path FROM Path WHERE PathId=%s", edit_int64(id_list.Id[i], ed1));
1131          if (!db_sql_query(db, buf, print_name_handler, NULL)) {
1132             printf("%s\n", db_strerror(db));
1133          }
1134       }
1135    }
1136    if (quit) {
1137       return;
1138    }
1139    if (fix && id_list.num_ids > 0) {
1140       POOLMEM *name = get_pool_memory(PM_FNAME);
1141       char esc_name[5000];
1142       printf(_("Reparing %d bad Filename records.\n"), id_list.num_ids);
1143       for (i=0; i < id_list.num_ids; i++) {
1144          int len;
1145          char ed1[50];
1146          bsnprintf(buf, sizeof(buf),
1147             "SELECT Path FROM Path WHERE PathId=%s", edit_int64(id_list.Id[i], ed1));
1148          if (!db_sql_query(db, buf, get_name_handler, name)) {
1149             printf("%s\n", db_strerror(db));
1150          }
1151          /* Strip trailing blanks */
1152          for (len=strlen(name); len > 0 && name[len-1]==' '; len--) {
1153             name[len-1] = 0;
1154          }
1155          /* Add trailing slash */
1156          len = pm_strcat(&name, "/");
1157          db_escape_string(esc_name, name, len);
1158          bsnprintf(buf, sizeof(buf), "UPDATE Path SET Path='%s' WHERE PathId=%s",
1159             esc_name, edit_int64(id_list.Id[i], ed1));
1160          if (verbose > 1) {
1161             printf("%s\n", buf);
1162          }
1163          db_sql_query(db, buf, NULL, NULL);
1164       }
1165    }
1166 }
1167
1168
1169 /*
1170  * Gen next input command from the terminal
1171  */
1172 static char *get_cmd(const char *prompt)
1173 {
1174    static char cmd[1000];
1175
1176    printf("%s", prompt);
1177    if (fgets(cmd, sizeof(cmd), stdin) == NULL) {
1178       printf("\n");
1179       quit = true;
1180       return NULL;
1181    }
1182    strip_trailing_junk(cmd);
1183    return cmd;
1184 }
1185
1186 static bool yes_no(const char *prompt)
1187 {
1188    char *cmd;
1189    cmd = get_cmd(prompt);
1190    if (!cmd) {
1191       quit = true;
1192       return false;
1193    }
1194    return (strcasecmp(cmd, "yes") == 0) || (strcasecmp(cmd, _("yes")) == 0);
1195 }
1196
1197 bool python_set_prog(JCR*, char const*) { return false; }