]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/tools/dbcheck.c
ebl update
[bacula/bacula] / bacula / src / tools / dbcheck.c
1 /*
2  *
3  *  Program to check a Bacula database for consistency and to
4  *   make repairs
5  *
6  *   Kern E. Sibbald, August 2002
7  *
8  *   Version $Id$
9  *
10  */
11 /*
12    Bacula® - The Network Backup Solution
13
14    Copyright (C) 2002-2006 Free Software Foundation Europe e.V.
15
16    The main author of Bacula is Kern Sibbald, with contributions from
17    many others, a complete list can be found in the file AUTHORS.
18    This program is Free Software; you can redistribute it and/or
19    modify it under the terms of version two of the GNU General Public
20    License as published by the Free Software Foundation plus additions
21    that are listed in the file LICENSE.
22
23    This program is distributed in the hope that it will be useful, but
24    WITHOUT ANY WARRANTY; without even the implied warranty of
25    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
26    General Public License for more details.
27
28    You should have received a copy of the GNU General Public License
29    along with this program; if not, write to the Free Software
30    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
31    02110-1301, USA.
32
33    Bacula® is a registered trademark of John Walker.
34    The licensor of Bacula is the Free Software Foundation Europe
35    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
36    Switzerland, email:ftf@fsfeurope.org.
37 */
38
39 #include "bacula.h"
40 #include "cats/cats.h"
41 #include "lib/runscript.h"
42 #include "dird/dird_conf.h"
43
44 /* Dummy functions */
45 int generate_daemon_event(JCR *jcr, const char *event) 
46    { return 1; }
47
48 typedef struct s_id_ctx {
49    uint32_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 = (uint32_t *)bmalloc(sizeof(uint32_t) * lst->max_ids);
486       } else {
487          lst->max_ids = (lst->max_ids * 3) / 2;
488          lst->Id = (uint32_t *)brealloc(lst->Id, sizeof(uint32_t) * lst->max_ids);
489       }
490    }
491    lst->Id[lst->num_ids++] = (uint32_t)strtod(row[0], NULL);
492    return 0;
493 }
494
495 /*
496  * Construct record id list
497  */
498 static int make_id_list(const char *query, ID_LIST *id_list)
499 {
500    id_list->num_ids = 0;
501    id_list->num_del = 0;
502    id_list->tot_ids = 0;
503
504    if (!db_sql_query(db, query, id_list_handler, (void *)id_list)) {
505       printf("%s", db_strerror(db));
506       return 0;
507    }
508    return 1;
509 }
510
511 /*
512  * Delete all entries in the list
513  */
514 static int delete_id_list(const char *query, ID_LIST *id_list)
515 {
516    for (int i=0; i < id_list->num_ids; i++) {
517       bsnprintf(buf, sizeof(buf), query, id_list->Id[i]);
518       if (verbose) {
519          printf(_("Deleting: %s\n"), buf);
520       }
521       db_sql_query(db, buf, NULL, NULL);
522    }
523    return 1;
524 }
525
526 /*
527  * Called here with each name to be added to the list
528  */
529 static int name_list_handler(void *ctx, int num_fields, char **row)
530 {
531    NAME_LIST *name = (NAME_LIST *)ctx;
532
533    if (name->num_ids == MAX_ID_LIST_LEN) {
534       return 1;
535    }
536    if (name->num_ids == name->max_ids) {
537       if (name->max_ids == 0) {
538          name->max_ids = 10000;
539          name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
540       } else {
541          name->max_ids = (name->max_ids * 3) / 2;
542          name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
543       }
544    }
545    name->name[name->num_ids++] = bstrdup(row[0]);
546    return 0;
547 }
548
549
550 /*
551  * Construct name list
552  */
553 static int make_name_list(const char *query, NAME_LIST *name_list)
554 {
555    name_list->num_ids = 0;
556    name_list->num_del = 0;
557    name_list->tot_ids = 0;
558
559    if (!db_sql_query(db, query, name_list_handler, (void *)name_list)) {
560       printf("%s", db_strerror(db));
561       return 0;
562    }
563    return 1;
564 }
565
566 /*
567  * Print names in the list
568  */
569 static void print_name_list(NAME_LIST *name_list)
570 {
571    for (int i=0; i < name_list->num_ids; i++) {
572       printf("%s\n", name_list->name[i]);
573    }
574 }
575
576
577 /*
578  * Free names in the list
579  */
580 static void free_name_list(NAME_LIST *name_list)
581 {
582    for (int i=0; i < name_list->num_ids; i++) {
583       free(name_list->name[i]);
584    }
585    name_list->num_ids = 0;
586 }
587
588 static void eliminate_duplicate_filenames()
589 {
590    const char *query;
591    char esc_name[5000];
592
593    printf(_("Checking for duplicate Filename entries.\n"));
594
595    /* Make list of duplicated names */
596    query = "SELECT Name, count(Name) as Count FROM Filename GROUP BY  Name "
597            "HAVING count(Name) > 1";
598
599    if (!make_name_list(query, &name_list)) {
600       exit(1);
601    }
602    printf(_("Found %d duplicate Filename records.\n"), name_list.num_ids);
603    if (name_list.num_ids && verbose && yes_no(_("Print the list? (yes/no): "))) {
604       print_name_list(&name_list);
605    }
606    if (quit) {
607       return;
608    }
609    if (fix) {
610       /* Loop through list of duplicate names */
611       for (int i=0; i<name_list.num_ids; i++) {
612          /* Get all the Ids of each name */
613          db_escape_string(esc_name, name_list.name[i], strlen(name_list.name[i]));
614          bsnprintf(buf, sizeof(buf), "SELECT FilenameId FROM Filename WHERE Name='%s'", esc_name);
615          if (verbose > 1) {
616             printf("%s\n", buf);
617          }
618          if (!make_id_list(buf, &id_list)) {
619             exit(1);
620          }
621          if (verbose) {
622             printf(_("Found %d for: %s\n"), id_list.num_ids, name_list.name[i]);
623          }
624          /* Force all records to use the first id then delete the other ids */
625          for (int j=1; j<id_list.num_ids; j++) {
626             bsnprintf(buf, sizeof(buf), "UPDATE File SET FilenameId=%u WHERE FilenameId=%u",
627                id_list.Id[0], id_list.Id[j]);
628             if (verbose > 1) {
629                printf("%s\n", buf);
630             }
631             db_sql_query(db, buf, NULL, NULL);
632             bsnprintf(buf, sizeof(buf), "DELETE FROM Filename WHERE FilenameId=%u",
633                id_list.Id[j]);
634             if (verbose > 2) {
635                printf("%s\n", buf);
636             }
637             db_sql_query(db, buf, NULL, NULL);
638          }
639       }
640    }
641    free_name_list(&name_list);
642 }
643
644 static void eliminate_duplicate_paths()
645 {
646    const char *query;
647    char esc_name[5000];
648
649    printf(_("Checking for duplicate Path entries.\n"));
650
651    /* Make list of duplicated names */
652
653    query = "SELECT Path, count(Path) as Count FROM Path "
654            "GROUP BY Path HAVING count(Path) > 1";
655
656    if (!make_name_list(query, &name_list)) {
657       exit(1);
658    }
659    printf(_("Found %d duplicate Path records.\n"), name_list.num_ids);
660    if (name_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
661       print_name_list(&name_list);
662    }
663    if (quit) {
664       return;
665    }
666    if (fix) {
667       /* Loop through list of duplicate names */
668       for (int i=0; i<name_list.num_ids; i++) {
669          /* Get all the Ids of each name */
670          db_escape_string(esc_name, name_list.name[i], strlen(name_list.name[i]));
671          bsnprintf(buf, sizeof(buf), "SELECT PathId FROM Path WHERE Path='%s'", esc_name);
672          if (verbose > 1) {
673             printf("%s\n", buf);
674          }
675          if (!make_id_list(buf, &id_list)) {
676             exit(1);
677          }
678          if (verbose) {
679             printf(_("Found %d for: %s\n"), id_list.num_ids, name_list.name[i]);
680          }
681          /* Force all records to use the first id then delete the other ids */
682          for (int j=1; j<id_list.num_ids; j++) {
683             bsnprintf(buf, sizeof(buf), "UPDATE File SET PathId=%u WHERE PathId=%u",
684                id_list.Id[0], id_list.Id[j]);
685             if (verbose > 1) {
686                printf("%s\n", buf);
687             }
688             db_sql_query(db, buf, NULL, NULL);
689             bsnprintf(buf, sizeof(buf), "DELETE FROM Path WHERE PathId=%u",
690                id_list.Id[j]);
691             if (verbose > 2) {
692                printf("%s\n", buf);
693             }
694             db_sql_query(db, buf, NULL, NULL);
695          }
696       }
697    }
698    free_name_list(&name_list);
699 }
700
701 static void eliminate_orphaned_jobmedia_records()
702 {
703    const char *query;
704
705    printf(_("Checking for orphaned JobMedia entries.\n"));
706    query = "SELECT JobMedia.JobMediaId,Job.JobId FROM JobMedia "
707            "LEFT OUTER JOIN Job ON (JobMedia.JobId=Job.JobId) "
708            "WHERE Job.JobId IS NULL";
709    if (!make_id_list(query, &id_list)) {
710       exit(1);
711    }
712    printf(_("Found %d orphaned JobMedia records.\n"), id_list.num_ids);
713    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
714       for (int i=0; i < id_list.num_ids; i++) {
715          bsnprintf(buf, sizeof(buf),
716 "SELECT JobMedia.JobMediaId,JobMedia.JobId,Media.VolumeName FROM JobMedia,Media "
717 "WHERE JobMedia.JobMediaId=%u AND Media.MediaId=JobMedia.MediaId", id_list.Id[i]);
718          if (!db_sql_query(db, buf, print_jobmedia_handler, NULL)) {
719             printf("%s\n", db_strerror(db));
720          }
721       }
722    }
723    if (quit) {
724       return;
725    }
726
727    if (fix && id_list.num_ids > 0) {
728       printf(_("Deleting %d orphaned JobMedia records.\n"), id_list.num_ids);
729       delete_id_list("DELETE FROM JobMedia WHERE JobMediaId=%u", &id_list);
730    }
731 }
732
733 static void eliminate_orphaned_file_records()
734 {
735    const char *query;
736
737    printf(_("Checking for orphaned File entries. This may take some time!\n"));
738    query = "SELECT File.FileId,Job.JobId FROM File "
739            "LEFT OUTER JOIN Job ON (File.JobId=Job.JobId) "
740            "WHERE Job.JobId IS NULL";
741    if (verbose > 1) {
742       printf("%s\n", query);
743    }
744    if (!make_id_list(query, &id_list)) {
745       exit(1);
746    }
747    printf(_("Found %d orphaned File records.\n"), id_list.num_ids);
748    if (name_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
749       for (int i=0; i < id_list.num_ids; i++) {
750          bsnprintf(buf, sizeof(buf),
751 "SELECT File.FileId,File.JobId,Filename.Name FROM File,Filename "
752 "WHERE File.FileId=%u AND File.FilenameId=Filename.FilenameId", id_list.Id[i]);
753          if (!db_sql_query(db, buf, print_file_handler, NULL)) {
754             printf("%s\n", db_strerror(db));
755          }
756       }
757    }
758    if (quit) {
759       return;
760    }
761    if (fix && id_list.num_ids > 0) {
762       printf(_("Deleting %d orphaned File records.\n"), id_list.num_ids);
763       delete_id_list("DELETE FROM File WHERE FileId=%u", &id_list);
764    }
765 }
766
767 static void eliminate_orphaned_path_records()
768 {
769    const char *query;
770
771    printf(_("Checking for orphaned Path entries. This may take some time!\n"));
772    query = "SELECT DISTINCT Path.PathId,File.PathId FROM Path "
773            "LEFT OUTER JOIN File ON (Path.PathId=File.PathId) "
774            "WHERE File.PathId IS NULL";
775    if (verbose > 1) {
776       printf("%s\n", query);
777    }
778    if (!make_id_list(query, &id_list)) {
779       exit(1);
780    }
781    printf(_("Found %d orphaned Path records.\n"), id_list.num_ids);
782    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
783       for (int i=0; i < id_list.num_ids; i++) {
784          bsnprintf(buf, sizeof(buf), "SELECT Path FROM Path WHERE PathId=%u", id_list.Id[i]);
785          db_sql_query(db, buf, print_name_handler, NULL);
786       }
787    }
788    if (quit) {
789       return;
790    }
791    if (fix && id_list.num_ids > 0) {
792       printf(_("Deleting %d orphaned Path records.\n"), id_list.num_ids);
793       delete_id_list("DELETE FROM Path WHERE PathId=%u", &id_list);
794    }
795 }
796
797 static void eliminate_orphaned_filename_records()
798 {
799    const char *query;
800
801    printf(_("Checking for orphaned Filename entries. This may take some time!\n"));
802    query = "SELECT Filename.FilenameId,File.FilenameId FROM Filename "
803            "LEFT OUTER JOIN File ON (Filename.FilenameId=File.FilenameId) "
804            "WHERE File.FilenameId IS NULL";
805    if (verbose > 1) {
806       printf("%s\n", query);
807    }
808    if (!make_id_list(query, &id_list)) {
809       exit(1);
810    }
811    printf(_("Found %d orphaned Filename records.\n"), id_list.num_ids);
812    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
813       for (int i=0; i < id_list.num_ids; i++) {
814          bsnprintf(buf, sizeof(buf), "SELECT Name FROM Filename WHERE FilenameId=%u", id_list.Id[i]);
815          db_sql_query(db, buf, print_name_handler, NULL);
816       }
817    }
818    if (quit) {
819       return;
820    }
821    if (fix && id_list.num_ids > 0) {
822       printf(_("Deleting %d orphaned Filename records.\n"), id_list.num_ids);
823       delete_id_list("DELETE FROM Filename WHERE FilenameId=%u", &id_list);
824    }
825 }
826
827 static void eliminate_orphaned_fileset_records()
828 {
829    const char *query;
830
831    printf(_("Checking for orphaned FileSet entries. This takes some time!\n"));
832    query = "SELECT FileSet.FileSetId,Job.FileSetId FROM FileSet "
833            "LEFT OUTER JOIN Job ON (FileSet.FileSetId=Job.FileSetId) "
834            "WHERE Job.FileSetId IS NULL";
835    if (verbose > 1) {
836       printf("%s\n", query);
837    }
838    if (!make_id_list(query, &id_list)) {
839       exit(1);
840    }
841    printf(_("Found %d orphaned FileSet records.\n"), id_list.num_ids);
842    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
843       for (int i=0; i < id_list.num_ids; i++) {
844          bsnprintf(buf, sizeof(buf), "SELECT FileSetId,FileSet,MD5 FROM FileSet "
845                       "WHERE FileSetId=%u", id_list.Id[i]);
846          if (!db_sql_query(db, buf, print_fileset_handler, NULL)) {
847             printf("%s\n", db_strerror(db));
848          }
849       }
850    }
851    if (quit) {
852       return;
853    }
854    if (fix && id_list.num_ids > 0) {
855       printf(_("Deleting %d orphaned FileSet records.\n"), id_list.num_ids);
856       delete_id_list("DELETE FROM FileSet WHERE FileSetId=%u", &id_list);
857    }
858 }
859
860 static void eliminate_orphaned_client_records()
861 {
862    const char *query;
863
864    printf(_("Checking for orphaned Client entries.\n"));
865    /* In English:
866     *   Wiffle through Client for every Client
867     *   joining with the Job table including every Client even if
868     *   there is not a match in Job (left outer join), then
869     *   filter out only those where no Job points to a Client
870     *   i.e. Job.Client is NULL
871     */
872    query = "SELECT Client.ClientId,Client.Name FROM Client "
873            "LEFT OUTER JOIN Job ON (Client.ClientId=Job.ClientId) "
874            "WHERE Job.ClientId IS NULL";
875    if (verbose > 1) {
876       printf("%s\n", query);
877    }
878    if (!make_id_list(query, &id_list)) {
879       exit(1);
880    }
881    printf(_("Found %d orphaned Client records.\n"), id_list.num_ids);
882    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
883       for (int i=0; i < id_list.num_ids; i++) {
884          bsnprintf(buf, sizeof(buf), "SELECT ClientId,Name FROM Client "
885                       "WHERE ClientId=%u", id_list.Id[i]);
886          if (!db_sql_query(db, buf, print_client_handler, NULL)) {
887             printf("%s\n", db_strerror(db));
888          }
889       }
890    }
891    if (quit) {
892       return;
893    }
894    if (fix && id_list.num_ids > 0) {
895       printf(_("Deleting %d orphaned Client records.\n"), id_list.num_ids);
896       delete_id_list("DELETE FROM Client WHERE ClientId=%u", &id_list);
897    }
898 }
899
900 static void eliminate_orphaned_job_records()
901 {
902    const char *query;
903
904    printf(_("Checking for orphaned Job entries.\n"));
905    /* In English:
906     *   Wiffle through Job for every Job
907     *   joining with the Client table including every Job even if
908     *   there is not a match in Client (left outer join), then
909     *   filter out only those where no Client exists
910     *   i.e. Client.Name is NULL
911     */
912    query = "SELECT Job.JobId,Job.Name FROM Job "
913            "LEFT OUTER JOIN Client ON (Job.ClientId=Client.ClientId) "
914            "WHERE Client.Name IS NULL";
915    if (verbose > 1) {
916       printf("%s\n", query);
917    }
918    if (!make_id_list(query, &id_list)) {
919       exit(1);
920    }
921    printf(_("Found %d orphaned Job records.\n"), id_list.num_ids);
922    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
923       for (int i=0; i < id_list.num_ids; i++) {
924          bsnprintf(buf, sizeof(buf), "SELECT JobId,Name,StartTime FROM Job "
925                       "WHERE JobId=%u", id_list.Id[i]);
926          if (!db_sql_query(db, buf, print_job_handler, NULL)) {
927             printf("%s\n", db_strerror(db));
928          }
929       }
930    }
931    if (quit) {
932       return;
933    }
934    if (fix && id_list.num_ids > 0) {
935       printf(_("Deleting %d orphaned Job records.\n"), id_list.num_ids);
936       delete_id_list("DELETE FROM Job WHERE JobId=%u", &id_list);
937       printf(_("Deleting JobMedia records of orphaned Job records.\n"));
938       delete_id_list("DELETE FROM JobMedia WHERE JobId=%u", &id_list);
939       printf(_("Deleting Log records of orphaned Job records.\n"));
940       delete_id_list("DELETE FROM Log WHERE JobId=%u", &id_list);
941    }
942 }
943
944
945 static void eliminate_admin_records()
946 {
947    const char *query;
948
949    printf(_("Checking for Admin Job entries.\n"));
950    query = "SELECT Job.JobId FROM Job "
951            "WHERE Job.Type='D'";
952    if (verbose > 1) {
953       printf("%s\n", query);
954    }
955    if (!make_id_list(query, &id_list)) {
956       exit(1);
957    }
958    printf(_("Found %d Admin Job records.\n"), id_list.num_ids);
959    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
960       for (int i=0; i < id_list.num_ids; i++) {
961          bsnprintf(buf, sizeof(buf), "SELECT JobId,Name,StartTime FROM Job "
962                       "WHERE JobId=%u", id_list.Id[i]);
963          if (!db_sql_query(db, buf, print_job_handler, NULL)) {
964             printf("%s\n", db_strerror(db));
965          }
966       }
967    }
968    if (quit) {
969       return;
970    }
971    if (fix && id_list.num_ids > 0) {
972       printf(_("Deleting %d Admin Job records.\n"), id_list.num_ids);
973       delete_id_list("DELETE FROM Job WHERE JobId=%u", &id_list);
974    }
975 }
976
977 static void eliminate_restore_records()
978 {
979    const char *query;
980
981    printf(_("Checking for Restore Job entries.\n"));
982    query = "SELECT Job.JobId FROM Job "
983            "WHERE Job.Type='R'";
984    if (verbose > 1) {
985       printf("%s\n", query);
986    }
987    if (!make_id_list(query, &id_list)) {
988       exit(1);
989    }
990    printf(_("Found %d Restore Job records.\n"), id_list.num_ids);
991    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
992       for (int i=0; i < id_list.num_ids; i++) {
993          bsnprintf(buf, sizeof(buf), "SELECT JobId,Name,StartTime FROM Job "
994                       "WHERE JobId=%u", id_list.Id[i]);
995          if (!db_sql_query(db, buf, print_job_handler, NULL)) {
996             printf("%s\n", db_strerror(db));
997          }
998       }
999    }
1000    if (quit) {
1001       return;
1002    }
1003    if (fix && id_list.num_ids > 0) {
1004       printf(_("Deleting %d Restore Job records.\n"), id_list.num_ids);
1005       delete_id_list("DELETE FROM Job WHERE JobId=%u", &id_list);
1006    }
1007 }
1008
1009
1010
1011
1012 static void repair_bad_filenames()
1013 {
1014    const char *query;
1015    int i;
1016
1017    printf(_("Checking for Filenames with a trailing slash\n"));
1018    query = "SELECT FilenameId,Name from Filename "
1019            "WHERE Name LIKE '%/'";
1020    if (verbose > 1) {
1021       printf("%s\n", query);
1022    }
1023    if (!make_id_list(query, &id_list)) {
1024       exit(1);
1025    }
1026    printf(_("Found %d bad Filename records.\n"), id_list.num_ids);
1027    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
1028       for (i=0; i < id_list.num_ids; i++) {
1029          bsnprintf(buf, sizeof(buf),
1030             "SELECT Name FROM Filename WHERE FilenameId=%u", id_list.Id[i]);
1031          if (!db_sql_query(db, buf, print_name_handler, NULL)) {
1032             printf("%s\n", db_strerror(db));
1033          }
1034       }
1035    }
1036    if (quit) {
1037       return;
1038    }
1039    if (fix && id_list.num_ids > 0) {
1040       POOLMEM *name = get_pool_memory(PM_FNAME);
1041       char esc_name[5000];
1042       printf(_("Reparing %d bad Filename records.\n"), id_list.num_ids);
1043       for (i=0; i < id_list.num_ids; i++) {
1044          int len;
1045          bsnprintf(buf, sizeof(buf),
1046             "SELECT Name FROM Filename WHERE FilenameId=%u", id_list.Id[i]);
1047          if (!db_sql_query(db, buf, get_name_handler, name)) {
1048             printf("%s\n", db_strerror(db));
1049          }
1050          /* Strip trailing slash(es) */
1051          for (len=strlen(name); len > 0 && IsPathSeparator(name[len-1]); len--)
1052             {  }
1053          if (len == 0) {
1054             len = 1;
1055             esc_name[0] = ' ';
1056             esc_name[1] = 0;
1057          } else {
1058             name[len-1] = 0;
1059             db_escape_string(esc_name, name, len);
1060          }
1061          bsnprintf(buf, sizeof(buf),
1062             "UPDATE Filename SET Name='%s' WHERE FilenameId=%u",
1063             esc_name, id_list.Id[i]);
1064          if (verbose > 1) {
1065             printf("%s\n", buf);
1066          }
1067          db_sql_query(db, buf, NULL, NULL);
1068       }
1069    }
1070 }
1071
1072 static void repair_bad_paths()
1073 {
1074    const char *query;
1075    int i;
1076
1077    printf(_("Checking for Paths without a trailing slash\n"));
1078    query = "SELECT PathId,Path from Path "
1079            "WHERE Path NOT LIKE '%/'";
1080    if (verbose > 1) {
1081       printf("%s\n", query);
1082    }
1083    if (!make_id_list(query, &id_list)) {
1084       exit(1);
1085    }
1086    printf(_("Found %d bad Path records.\n"), id_list.num_ids);
1087    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
1088       for (i=0; i < id_list.num_ids; i++) {
1089          bsnprintf(buf, sizeof(buf),
1090             "SELECT Path FROM Path WHERE PathId=%u", id_list.Id[i]);
1091          if (!db_sql_query(db, buf, print_name_handler, NULL)) {
1092             printf("%s\n", db_strerror(db));
1093          }
1094       }
1095    }
1096    if (quit) {
1097       return;
1098    }
1099    if (fix && id_list.num_ids > 0) {
1100       POOLMEM *name = get_pool_memory(PM_FNAME);
1101       char esc_name[5000];
1102       printf(_("Reparing %d bad Filename records.\n"), id_list.num_ids);
1103       for (i=0; i < id_list.num_ids; i++) {
1104          int len;
1105          bsnprintf(buf, sizeof(buf),
1106             "SELECT Path FROM Path WHERE PathId=%u", id_list.Id[i]);
1107          if (!db_sql_query(db, buf, get_name_handler, name)) {
1108             printf("%s\n", db_strerror(db));
1109          }
1110          /* Strip trailing blanks */
1111          for (len=strlen(name); len > 0 && name[len-1]==' '; len--) {
1112             name[len-1] = 0;
1113          }
1114          /* Add trailing slash */
1115          len = pm_strcat(&name, "/");
1116          db_escape_string(esc_name, name, len);
1117          bsnprintf(buf, sizeof(buf), "UPDATE Path SET Path='%s' WHERE PathId=%u",
1118             esc_name, id_list.Id[i]);
1119          if (verbose > 1) {
1120             printf("%s\n", buf);
1121          }
1122          db_sql_query(db, buf, NULL, NULL);
1123       }
1124    }
1125 }
1126
1127
1128 /*
1129  * Gen next input command from the terminal
1130  */
1131 static char *get_cmd(const char *prompt)
1132 {
1133    static char cmd[1000];
1134
1135    printf("%s", prompt);
1136    if (fgets(cmd, sizeof(cmd), stdin) == NULL) {
1137       printf("\n");
1138       quit = true;
1139       return NULL;
1140    }
1141    strip_trailing_junk(cmd);
1142    return cmd;
1143 }
1144
1145 static bool yes_no(const char *prompt)
1146 {
1147    char *cmd;
1148    cmd = get_cmd(prompt);
1149    if (!cmd) {
1150       quit = true;
1151       return false;
1152    }
1153    return (strcasecmp(cmd, "yes") == 0) || (strcasecmp(cmd, _("yes")) == 0);
1154 }
1155
1156 bool python_set_prog(JCR*, char const*) { return false; }