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