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