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