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