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