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