]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/tools/dbcheck.c
Update copyrights + Do not release source pointers when restarting a failed job.
[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;
706
707    printf(_("Checking for orphaned JobMedia entries.\n"));
708    query = "SELECT JobMedia.JobMediaId,Job.JobId FROM JobMedia "
709            "LEFT OUTER JOIN Job ON (JobMedia.JobId=Job.JobId) "
710            "WHERE Job.JobId IS NULL";
711    if (!make_id_list(query, &id_list)) {
712       exit(1);
713    }
714    printf(_("Found %d orphaned JobMedia records.\n"), id_list.num_ids);
715    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
716       for (int i=0; i < id_list.num_ids; i++) {
717          char ed1[50];
718          bsnprintf(buf, sizeof(buf),
719 "SELECT JobMedia.JobMediaId,JobMedia.JobId,Media.VolumeName FROM JobMedia,Media "
720 "WHERE JobMedia.JobMediaId=%s AND Media.MediaId=JobMedia.MediaId", 
721             edit_int64(id_list.Id[i], ed1));
722          if (!db_sql_query(db, buf, print_jobmedia_handler, NULL)) {
723             printf("%s\n", db_strerror(db));
724          }
725       }
726    }
727    if (quit) {
728       return;
729    }
730
731    if (fix && id_list.num_ids > 0) {
732       printf(_("Deleting %d orphaned JobMedia records.\n"), id_list.num_ids);
733       delete_id_list("DELETE FROM JobMedia WHERE JobMediaId=%s", &id_list);
734    }
735 }
736
737 static void eliminate_orphaned_file_records()
738 {
739    const char *query;
740
741    printf(_("Checking for orphaned File entries. This may take some time!\n"));
742    query = "SELECT File.FileId,Job.JobId FROM File "
743            "LEFT OUTER JOIN Job ON (File.JobId=Job.JobId) "
744            "WHERE Job.JobId IS NULL";
745    if (verbose > 1) {
746       printf("%s\n", query);
747    }
748    if (!make_id_list(query, &id_list)) {
749       exit(1);
750    }
751    printf(_("Found %d orphaned File records.\n"), id_list.num_ids);
752    if (name_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
753       for (int i=0; i < id_list.num_ids; i++) {
754          char ed1[50];
755          bsnprintf(buf, sizeof(buf),
756 "SELECT File.FileId,File.JobId,Filename.Name FROM File,Filename "
757 "WHERE File.FileId=%s AND File.FilenameId=Filename.FilenameId", 
758             edit_int64(id_list.Id[i], ed1));
759          if (!db_sql_query(db, buf, print_file_handler, NULL)) {
760             printf("%s\n", db_strerror(db));
761          }
762       }
763    }
764    if (quit) {
765       return;
766    }
767    if (fix && id_list.num_ids > 0) {
768       printf(_("Deleting %d orphaned File records.\n"), id_list.num_ids);
769       delete_id_list("DELETE FROM File WHERE FileId=%s", &id_list);
770    }
771 }
772
773 static void eliminate_orphaned_path_records()
774 {
775    const char *query;
776
777    printf(_("Checking for orphaned Path entries. This may take some time!\n"));
778    query = "SELECT DISTINCT Path.PathId,File.PathId FROM Path "
779            "LEFT OUTER JOIN File ON (Path.PathId=File.PathId) "
780            "WHERE File.PathId IS NULL";
781    if (verbose > 1) {
782       printf("%s\n", query);
783    }
784    if (!make_id_list(query, &id_list)) {
785       exit(1);
786    }
787    printf(_("Found %d orphaned Path records.\n"), id_list.num_ids);
788    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
789       for (int i=0; i < id_list.num_ids; i++) {
790          char ed1[50];
791          bsnprintf(buf, sizeof(buf), "SELECT Path FROM Path WHERE PathId=%s", 
792             edit_int64(id_list.Id[i], ed1));
793          db_sql_query(db, buf, print_name_handler, NULL);
794       }
795    }
796    if (quit) {
797       return;
798    }
799    if (fix && id_list.num_ids > 0) {
800       printf(_("Deleting %d orphaned Path records.\n"), id_list.num_ids);
801       delete_id_list("DELETE FROM Path WHERE PathId=%s", &id_list);
802    }
803 }
804
805 static void eliminate_orphaned_filename_records()
806 {
807    const char *query;
808
809    printf(_("Checking for orphaned Filename entries. This may take some time!\n"));
810    query = "SELECT Filename.FilenameId,File.FilenameId FROM Filename "
811            "LEFT OUTER JOIN File ON (Filename.FilenameId=File.FilenameId) "
812            "WHERE File.FilenameId IS NULL";
813    if (verbose > 1) {
814       printf("%s\n", query);
815    }
816    if (!make_id_list(query, &id_list)) {
817       exit(1);
818    }
819    printf(_("Found %d orphaned Filename records.\n"), id_list.num_ids);
820    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
821       for (int i=0; i < id_list.num_ids; i++) {
822          char ed1[50];
823          bsnprintf(buf, sizeof(buf), "SELECT Name FROM Filename WHERE FilenameId=%s", 
824             edit_int64(id_list.Id[i], ed1));
825          db_sql_query(db, buf, print_name_handler, NULL);
826       }
827    }
828    if (quit) {
829       return;
830    }
831    if (fix && id_list.num_ids > 0) {
832       printf(_("Deleting %d orphaned Filename records.\n"), id_list.num_ids);
833       delete_id_list("DELETE FROM Filename WHERE FilenameId=%s", &id_list);
834    }
835 }
836
837 static void eliminate_orphaned_fileset_records()
838 {
839    const char *query;
840
841    printf(_("Checking for orphaned FileSet entries. This takes some time!\n"));
842    query = "SELECT FileSet.FileSetId,Job.FileSetId FROM FileSet "
843            "LEFT OUTER JOIN Job ON (FileSet.FileSetId=Job.FileSetId) "
844            "WHERE Job.FileSetId IS NULL";
845    if (verbose > 1) {
846       printf("%s\n", query);
847    }
848    if (!make_id_list(query, &id_list)) {
849       exit(1);
850    }
851    printf(_("Found %d orphaned FileSet records.\n"), id_list.num_ids);
852    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
853       for (int i=0; i < id_list.num_ids; i++) {
854          char ed1[50];
855          bsnprintf(buf, sizeof(buf), "SELECT FileSetId,FileSet,MD5 FROM FileSet "
856                       "WHERE FileSetId=%s", edit_int64(id_list.Id[i], ed1));
857          if (!db_sql_query(db, buf, print_fileset_handler, NULL)) {
858             printf("%s\n", db_strerror(db));
859          }
860       }
861    }
862    if (quit) {
863       return;
864    }
865    if (fix && id_list.num_ids > 0) {
866       printf(_("Deleting %d orphaned FileSet records.\n"), id_list.num_ids);
867       delete_id_list("DELETE FROM FileSet WHERE FileSetId=%s", &id_list);
868    }
869 }
870
871 static void eliminate_orphaned_client_records()
872 {
873    const char *query;
874
875    printf(_("Checking for orphaned Client entries.\n"));
876    /* In English:
877     *   Wiffle through Client for every Client
878     *   joining with the Job table including every Client even if
879     *   there is not a match in Job (left outer join), then
880     *   filter out only those where no Job points to a Client
881     *   i.e. Job.Client is NULL
882     */
883    query = "SELECT Client.ClientId,Client.Name FROM Client "
884            "LEFT OUTER JOIN Job ON (Client.ClientId=Job.ClientId) "
885            "WHERE Job.ClientId IS NULL";
886    if (verbose > 1) {
887       printf("%s\n", query);
888    }
889    if (!make_id_list(query, &id_list)) {
890       exit(1);
891    }
892    printf(_("Found %d orphaned Client records.\n"), id_list.num_ids);
893    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
894       for (int i=0; i < id_list.num_ids; i++) {
895          char ed1[50];
896          bsnprintf(buf, sizeof(buf), "SELECT ClientId,Name FROM Client "
897                       "WHERE ClientId=%s", edit_int64(id_list.Id[i], ed1));
898          if (!db_sql_query(db, buf, print_client_handler, NULL)) {
899             printf("%s\n", db_strerror(db));
900          }
901       }
902    }
903    if (quit) {
904       return;
905    }
906    if (fix && id_list.num_ids > 0) {
907       printf(_("Deleting %d orphaned Client records.\n"), id_list.num_ids);
908       delete_id_list("DELETE FROM Client WHERE ClientId=%s", &id_list);
909    }
910 }
911
912 static void eliminate_orphaned_job_records()
913 {
914    const char *query;
915
916    printf(_("Checking for orphaned Job entries.\n"));
917    /* In English:
918     *   Wiffle through Job for every Job
919     *   joining with the Client table including every Job even if
920     *   there is not a match in Client (left outer join), then
921     *   filter out only those where no Client exists
922     *   i.e. Client.Name is NULL
923     */
924    query = "SELECT Job.JobId,Job.Name FROM Job "
925            "LEFT OUTER JOIN Client ON (Job.ClientId=Client.ClientId) "
926            "WHERE Client.Name IS NULL";
927    if (verbose > 1) {
928       printf("%s\n", query);
929    }
930    if (!make_id_list(query, &id_list)) {
931       exit(1);
932    }
933    printf(_("Found %d orphaned Job records.\n"), id_list.num_ids);
934    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
935       for (int i=0; i < id_list.num_ids; i++) {
936          char ed1[50];
937          bsnprintf(buf, sizeof(buf), "SELECT JobId,Name,StartTime FROM Job "
938                       "WHERE JobId=%s", edit_int64(id_list.Id[i], ed1));
939          if (!db_sql_query(db, buf, print_job_handler, NULL)) {
940             printf("%s\n", db_strerror(db));
941          }
942       }
943    }
944    if (quit) {
945       return;
946    }
947    if (fix && id_list.num_ids > 0) {
948       printf(_("Deleting %d orphaned Job records.\n"), id_list.num_ids);
949       delete_id_list("DELETE FROM Job WHERE JobId=%s", &id_list);
950       printf(_("Deleting JobMedia records of orphaned Job records.\n"));
951       delete_id_list("DELETE FROM JobMedia WHERE JobId=%s", &id_list);
952       printf(_("Deleting Log records of orphaned Job records.\n"));
953       delete_id_list("DELETE FROM Log WHERE JobId=%s", &id_list);
954    }
955 }
956
957
958 static void eliminate_admin_records()
959 {
960    const char *query;
961
962    printf(_("Checking for Admin Job entries.\n"));
963    query = "SELECT Job.JobId FROM Job "
964            "WHERE Job.Type='D'";
965    if (verbose > 1) {
966       printf("%s\n", query);
967    }
968    if (!make_id_list(query, &id_list)) {
969       exit(1);
970    }
971    printf(_("Found %d Admin Job records.\n"), id_list.num_ids);
972    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
973       for (int i=0; i < id_list.num_ids; i++) {
974          char ed1[50];
975          bsnprintf(buf, sizeof(buf), "SELECT JobId,Name,StartTime FROM Job "
976                       "WHERE JobId=%s", edit_int64(id_list.Id[i], ed1));
977          if (!db_sql_query(db, buf, print_job_handler, NULL)) {
978             printf("%s\n", db_strerror(db));
979          }
980       }
981    }
982    if (quit) {
983       return;
984    }
985    if (fix && id_list.num_ids > 0) {
986       printf(_("Deleting %d Admin Job records.\n"), id_list.num_ids);
987       delete_id_list("DELETE FROM Job WHERE JobId=%s", &id_list);
988    }
989 }
990
991 static void eliminate_restore_records()
992 {
993    const char *query;
994
995    printf(_("Checking for Restore Job entries.\n"));
996    query = "SELECT Job.JobId FROM Job "
997            "WHERE Job.Type='R'";
998    if (verbose > 1) {
999       printf("%s\n", query);
1000    }
1001    if (!make_id_list(query, &id_list)) {
1002       exit(1);
1003    }
1004    printf(_("Found %d Restore Job records.\n"), id_list.num_ids);
1005    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
1006       for (int i=0; i < id_list.num_ids; i++) {
1007          char ed1[50];
1008          bsnprintf(buf, sizeof(buf), "SELECT JobId,Name,StartTime FROM Job "
1009                       "WHERE JobId=%s", edit_int64(id_list.Id[i], ed1));
1010          if (!db_sql_query(db, buf, print_job_handler, NULL)) {
1011             printf("%s\n", db_strerror(db));
1012          }
1013       }
1014    }
1015    if (quit) {
1016       return;
1017    }
1018    if (fix && id_list.num_ids > 0) {
1019       printf(_("Deleting %d Restore Job records.\n"), id_list.num_ids);
1020       delete_id_list("DELETE FROM Job WHERE JobId=%s", &id_list);
1021    }
1022 }
1023
1024
1025
1026
1027 static void repair_bad_filenames()
1028 {
1029    const char *query;
1030    int i;
1031
1032    printf(_("Checking for Filenames with a trailing slash\n"));
1033    query = "SELECT FilenameId,Name from Filename "
1034            "WHERE Name LIKE '%/'";
1035    if (verbose > 1) {
1036       printf("%s\n", query);
1037    }
1038    if (!make_id_list(query, &id_list)) {
1039       exit(1);
1040    }
1041    printf(_("Found %d bad Filename records.\n"), id_list.num_ids);
1042    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
1043       for (i=0; i < id_list.num_ids; i++) {
1044          char ed1[50];
1045          bsnprintf(buf, sizeof(buf),
1046             "SELECT Name FROM Filename WHERE FilenameId=%s", 
1047                 edit_int64(id_list.Id[i], ed1));
1048          if (!db_sql_query(db, buf, print_name_handler, NULL)) {
1049             printf("%s\n", db_strerror(db));
1050          }
1051       }
1052    }
1053    if (quit) {
1054       return;
1055    }
1056    if (fix && id_list.num_ids > 0) {
1057       POOLMEM *name = get_pool_memory(PM_FNAME);
1058       char esc_name[5000];
1059       printf(_("Reparing %d bad Filename records.\n"), id_list.num_ids);
1060       for (i=0; i < id_list.num_ids; i++) {
1061          int len;
1062          char ed1[50];
1063          bsnprintf(buf, sizeof(buf),
1064             "SELECT Name FROM Filename WHERE FilenameId=%s", 
1065                edit_int64(id_list.Id[i], ed1));
1066          if (!db_sql_query(db, buf, get_name_handler, name)) {
1067             printf("%s\n", db_strerror(db));
1068          }
1069          /* Strip trailing slash(es) */
1070          for (len=strlen(name); len > 0 && IsPathSeparator(name[len-1]); len--)
1071             {  }
1072          if (len == 0) {
1073             len = 1;
1074             esc_name[0] = ' ';
1075             esc_name[1] = 0;
1076          } else {
1077             name[len-1] = 0;
1078             db_escape_string(esc_name, name, len);
1079          }
1080          bsnprintf(buf, sizeof(buf),
1081             "UPDATE Filename SET Name='%s' WHERE FilenameId=%s",
1082             esc_name, edit_int64(id_list.Id[i], ed1));
1083          if (verbose > 1) {
1084             printf("%s\n", buf);
1085          }
1086          db_sql_query(db, buf, NULL, NULL);
1087       }
1088    }
1089 }
1090
1091 static void repair_bad_paths()
1092 {
1093    const char *query;
1094    int i;
1095
1096    printf(_("Checking for Paths without a trailing slash\n"));
1097    query = "SELECT PathId,Path from Path "
1098            "WHERE Path NOT LIKE '%/'";
1099    if (verbose > 1) {
1100       printf("%s\n", query);
1101    }
1102    if (!make_id_list(query, &id_list)) {
1103       exit(1);
1104    }
1105    printf(_("Found %d bad Path records.\n"), id_list.num_ids);
1106    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
1107       for (i=0; i < id_list.num_ids; i++) {
1108          char ed1[50];
1109          bsnprintf(buf, sizeof(buf),
1110             "SELECT Path FROM Path WHERE PathId=%s", edit_int64(id_list.Id[i], ed1));
1111          if (!db_sql_query(db, buf, print_name_handler, NULL)) {
1112             printf("%s\n", db_strerror(db));
1113          }
1114       }
1115    }
1116    if (quit) {
1117       return;
1118    }
1119    if (fix && id_list.num_ids > 0) {
1120       POOLMEM *name = get_pool_memory(PM_FNAME);
1121       char esc_name[5000];
1122       printf(_("Reparing %d bad Filename records.\n"), id_list.num_ids);
1123       for (i=0; i < id_list.num_ids; i++) {
1124          int len;
1125          char ed1[50];
1126          bsnprintf(buf, sizeof(buf),
1127             "SELECT Path FROM Path WHERE PathId=%s", edit_int64(id_list.Id[i], ed1));
1128          if (!db_sql_query(db, buf, get_name_handler, name)) {
1129             printf("%s\n", db_strerror(db));
1130          }
1131          /* Strip trailing blanks */
1132          for (len=strlen(name); len > 0 && name[len-1]==' '; len--) {
1133             name[len-1] = 0;
1134          }
1135          /* Add trailing slash */
1136          len = pm_strcat(&name, "/");
1137          db_escape_string(esc_name, name, len);
1138          bsnprintf(buf, sizeof(buf), "UPDATE Path SET Path='%s' WHERE PathId=%s",
1139             esc_name, edit_int64(id_list.Id[i], ed1));
1140          if (verbose > 1) {
1141             printf("%s\n", buf);
1142          }
1143          db_sql_query(db, buf, NULL, NULL);
1144       }
1145    }
1146 }
1147
1148
1149 /*
1150  * Gen next input command from the terminal
1151  */
1152 static char *get_cmd(const char *prompt)
1153 {
1154    static char cmd[1000];
1155
1156    printf("%s", prompt);
1157    if (fgets(cmd, sizeof(cmd), stdin) == NULL) {
1158       printf("\n");
1159       quit = true;
1160       return NULL;
1161    }
1162    strip_trailing_junk(cmd);
1163    return cmd;
1164 }
1165
1166 static bool yes_no(const char *prompt)
1167 {
1168    char *cmd;
1169    cmd = get_cmd(prompt);
1170    if (!cmd) {
1171       quit = true;
1172       return false;
1173    }
1174    return (strcasecmp(cmd, "yes") == 0) || (strcasecmp(cmd, _("yes")) == 0);
1175 }
1176
1177 bool python_set_prog(JCR*, char const*) { return false; }