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