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