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