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