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