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