]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/tools/dbcheck.c
3dba7d8f5f9325bc9c6ae6f1b516a4c36aabe0c0
[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 }