]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/tools/dbcheck.c
Put back hard coded path for consolehelper
[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 (!make_id_list(buf, &id_list)) {
470             exit(1);
471          }
472          if (verbose) {
473             printf("Found %d for: %s\n", id_list.num_ids, name_list.name[i]);
474          }
475          /* Force all records to use the first id then delete the other ids */
476          for (int j=1; j<id_list.num_ids; j++) {
477             sprintf(buf, "UPDATE File SET FilenameId=%u WHERE FilenameId=%u", 
478                id_list.Id[0], id_list.Id[j]);
479             db_sql_query(db, buf, NULL, NULL);
480             sprintf(buf, "DELETE FROM Filename WHERE FilenameId=%u", 
481                id_list.Id[j]);
482             db_sql_query(db, buf, NULL, NULL);
483          }
484       }
485    }
486    free_name_list(&name_list);
487 }
488
489 static void eliminate_duplicate_paths()
490 {
491    char *query;
492    char esc_name[5000];
493
494    printf(_("Checking for duplicate Path entries.\n"));
495    
496    /* Make list of duplicated names */
497
498    query = "SELECT Path,count(Path) as Count FROM Path "
499            "GROUP BY Path HAVING Count > 1";
500
501    if (!make_name_list(query, &name_list)) {
502       exit(1);
503    }
504    printf("Found %d duplicate Path records.\n", name_list.num_ids);
505    if (name_list.num_ids && verbose && yes_no("Print them? (yes/no): ")) {
506       print_name_list(&name_list);
507    }
508    if (fix) {
509       /* Loop through list of duplicate names */
510       for (int i=0; i<name_list.num_ids; i++) {
511          /* Get all the Ids of each name */
512          db_escape_string(esc_name, name_list.name[i], strlen(name_list.name[i]));
513          sprintf(buf, "SELECT PathId FROM Path WHERE Path='%s'", esc_name);
514          id_list.num_ids = 0;
515          if (!make_id_list(buf, &id_list)) {
516             exit(1);
517          }
518          if (verbose) {
519             printf("Found %d for: %s\n", id_list.num_ids, name_list.name[i]);
520          }
521          /* Force all records to use the first id then delete the other ids */
522          for (int j=1; j<id_list.num_ids; j++) {
523             sprintf(buf, "UPDATE File SET PathId=%u WHERE PathId=%u", 
524                id_list.Id[0], id_list.Id[j]);
525             db_sql_query(db, buf, NULL, NULL);
526             sprintf(buf, "DELETE FROM Path WHERE PathId=%u", 
527                id_list.Id[j]);
528             db_sql_query(db, buf, NULL, NULL);
529          }
530       }
531    }
532    free_name_list(&name_list);
533 }
534
535 static void eliminate_orphaned_jobmedia_records()
536 {
537    char *query;
538
539    printf("Checking for orphaned JobMedia entries.\n");
540    query = "SELECT JobMedia.JobMediaId,Job.JobId FROM JobMedia "
541            "LEFT OUTER JOIN Job ON (JobMedia.JobId=Job.JobId) "
542            "WHERE Job.JobId IS NULL";
543    if (!make_id_list(query, &id_list)) {
544       exit(1);
545    }
546    printf("Found %d orphaned JobMedia records.\n", id_list.num_ids);
547    if (id_list.num_ids && verbose && yes_no("Print them? (yes/no): ")) {
548       for (int i=0; i < id_list.num_ids; i++) {
549          sprintf(buf, 
550 "SELECT JobMedia.JobMediaId,JobMedia.JobId,Media.VolumeName FROM JobMedia,Media "
551 "WHERE JobMedia.JobMediaId=%u AND Media.MediaId=JobMedia.MediaId", id_list.Id[i]);
552          if (!db_sql_query(db, buf, print_jobmedia_handler, NULL)) {
553             printf("%s\n", db_strerror(db));
554          }
555       }
556    }
557    
558    if (fix && id_list.num_ids > 0) {
559       printf("Deleting %d orphaned JobMedia records.\n", id_list.num_ids);
560       delete_id_list("DELETE FROM JobMedia WHERE JobMediaId=%u", &id_list);
561    }
562 }
563
564 static void eliminate_orphaned_file_records()
565 {
566    char *query;
567
568    printf("Checking for orphaned File entries. This may take some time!\n");
569    query = "SELECT File.FileId,Job.JobId FROM File "
570            "LEFT OUTER JOIN Job ON (File.JobId=Job.JobId) "
571            "WHERE Job.JobId IS NULL";
572    if (!make_id_list(query, &id_list)) {
573       exit(1);
574    }
575    printf("Found %d orphaned File records.\n", id_list.num_ids);
576    if (name_list.num_ids && verbose && yes_no("Print them? (yes/no): ")) {
577       for (int i=0; i < id_list.num_ids; i++) {
578          sprintf(buf, 
579 "SELECT File.FileId,File.JobId,Filename.Name FROM File,Filename "
580 "WHERE File.FileId=%u AND File.FilenameId=Filename.FilenameId", id_list.Id[i]);
581          if (!db_sql_query(db, buf, print_file_handler, NULL)) {
582             printf("%s\n", db_strerror(db));
583          }
584       }
585    }
586       
587    if (fix && id_list.num_ids > 0) {
588       printf("Deleting %d orphaned File records.\n", id_list.num_ids);
589       delete_id_list("DELETE FROM File WHERE FileId=%u", &id_list);
590    }
591 }
592
593 static void eliminate_orphaned_path_records()
594 {
595    char *query;
596
597    printf("Checking for orphaned Path entries. This may take some time!\n");
598    query = "SELECT Path.PathId,File.PathId FROM Path "
599            "LEFT OUTER JOIN File ON (Path.PathId=File.PathId) "
600            "GROUP BY Path.PathId HAVING File.PathId IS NULL";
601    if (!make_id_list(query, &id_list)) {
602       exit(1);
603    }
604    printf("Found %d orphaned Path records.\n", id_list.num_ids);
605    if (id_list.num_ids && verbose && yes_no("Print them? (yes/no): ")) {
606       for (int i=0; i < id_list.num_ids; i++) {
607          sprintf(buf, "SELECT Path FROM Path WHERE PathId=%u", id_list.Id[i]);
608          db_sql_query(db, buf, print_name_handler, NULL);
609       }
610    }
611    
612    if (fix && id_list.num_ids > 0) {
613       printf("Deleting %d orphaned Path records.\n", id_list.num_ids);
614       delete_id_list("DELETE FROM Path WHERE PathId=%u", &id_list);
615    }
616 }
617
618 static void eliminate_orphaned_filename_records()
619 {
620    char *query;
621
622    printf("Checking for orphaned Filename entries. This may take some time!\n");
623    query = "SELECT Filename.FilenameId,File.FilenameId FROM Filename "
624            "LEFT OUTER JOIN File ON (Filename.FilenameId=File.FilenameId) "
625            "WHERE File.FilenameId IS NULL";
626
627    if (!make_id_list(query, &id_list)) {
628       exit(1);
629    }
630    printf("Found %d orphaned Filename records.\n", id_list.num_ids);
631    if (id_list.num_ids && verbose && yes_no("Print them? (yes/no): ")) {
632       for (int i=0; i < id_list.num_ids; i++) {
633          sprintf(buf, "SELECT Name FROM Filename WHERE FilenameId=%u", id_list.Id[i]);
634          db_sql_query(db, buf, print_name_handler, NULL);
635       }
636    }
637    
638    if (fix && id_list.num_ids > 0) {
639       printf("Deleting %d orphaned Filename records.\n", id_list.num_ids);
640       delete_id_list("DELETE FROM Filename WHERE FilenameId=%u", &id_list);
641    }
642 }
643
644 static void eliminate_orphaned_fileset_records()
645 {
646    char *query;
647
648    printf("Checking for orphaned FileSet entries. This takes some time!\n");
649    query = "SELECT FileSet.FileSetId,Job.FileSetId FROM FileSet "
650            "LEFT OUTER JOIN Job ON (FileSet.FileSetId=Job.FileSetId) "
651            "WHERE Job.FileSetId IS NULL";
652    if (!make_id_list(query, &id_list)) {
653       exit(1);
654    }
655    printf("Found %d orphaned FileSet 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 FileSetId,FileSet,MD5 FROM FileSet "
659                       "WHERE FileSetId=%u", id_list.Id[i]);
660          if (!db_sql_query(db, buf, print_fileset_handler, NULL)) {
661             printf("%s\n", db_strerror(db));
662          }
663       }
664    }
665    
666    if (fix && id_list.num_ids > 0) {
667       printf("Deleting %d orphaned FileSet records.\n", id_list.num_ids);
668       delete_id_list("DELETE FROM FileSet WHERE FileSetId=%u", &id_list);
669    }
670 }
671
672 static void repair_bad_filenames()
673 {
674    char *query;
675    int i;
676
677    printf("Checking for Filenames with a trailing slash\n");
678    query = "SELECT FilenameId,Name from Filename "
679            "WHERE Name LIKE '%/'";
680    if (!make_id_list(query, &id_list)) {
681       exit(1);
682    }
683    printf("Found %d bad Filename records.\n", id_list.num_ids);
684    if (name_list.num_ids && verbose && yes_no("Print them? (yes/no): ")) {
685       for (i=0; i < id_list.num_ids; i++) {
686          sprintf(buf, 
687             "SELECT Name FROM Filename WHERE FilenameId=%u", id_list.Id[i]);
688          if (!db_sql_query(db, buf, print_name_handler, NULL)) {
689             printf("%s\n", db_strerror(db));
690          }
691       }
692    }
693       
694    if (fix && id_list.num_ids > 0) {
695       POOLMEM *name = get_pool_memory(PM_FNAME);
696       char esc_name[5000];
697       printf("Reparing %d bad Filename records.\n", id_list.num_ids);
698       for (i=0; i < id_list.num_ids; i++) {
699          int len;
700          sprintf(buf, 
701             "SELECT Name FROM Filename WHERE FilenameId=%u", id_list.Id[i]);
702          if (!db_sql_query(db, buf, get_name_handler, name)) {
703             printf("%s\n", db_strerror(db));
704          }
705          /* Strip trailing slash(es) */
706          for (len=strlen(name); len > 1 && name[len-1]; len--)
707             {  }
708          db_escape_string(esc_name, name, len);
709          bsnprintf(buf, sizeof(buf), 
710             "UPDATE Filename SET Name='%s' WHERE FilenameId=%u", id_list.Id[i]);
711          db_sql_query(db, buf, NULL, NULL);
712       }
713    }
714 }
715
716 static void repair_bad_paths()
717 {
718    char *query;
719    int i;
720
721    printf("Checking for Paths without a trailing slash\n");
722    query = "SELECT PathId,Path from Path "
723            "WHERE Path IS NOT LIKE '%/'";
724    if (!make_id_list(query, &id_list)) {
725       exit(1);
726    }
727    printf("Found %d bad Path records.\n", id_list.num_ids);
728    if (name_list.num_ids && verbose && yes_no("Print them? (yes/no): ")) {
729       for (i=0; i < id_list.num_ids; i++) {
730          sprintf(buf, 
731             "SELECT Path FROM Path WHERE PathId=%u", id_list.Id[i]);
732          if (!db_sql_query(db, buf, print_name_handler, NULL)) {
733             printf("%s\n", db_strerror(db));
734          }
735       }
736    }
737       
738    if (fix && id_list.num_ids > 0) {
739       POOLMEM *name = get_pool_memory(PM_FNAME);
740       char esc_name[5000];
741       printf("Reparing %d bad Filename records.\n", id_list.num_ids);
742       for (i=0; i < id_list.num_ids; i++) {
743          sprintf(buf, 
744             "SELECT Path FROM Path WHERE PathId=%u", id_list.Id[i]);
745          if (!db_sql_query(db, buf, get_name_handler, name)) {
746             printf("%s\n", db_strerror(db));
747          }
748          /* Add trailing slash */
749          int len = pm_strcat(&name, "/");
750          db_escape_string(esc_name, name, len);
751          bsnprintf(buf, sizeof(buf), 
752             "UPDATE Path SET Path='%s' WHERE PathId=%u", id_list.Id[i]);
753          db_sql_query(db, buf, NULL, NULL);
754       }
755    }
756 }
757
758
759
760
761 /*
762  * Gen next input command from the terminal
763  */
764 static char *get_cmd(char *prompt)
765 {
766    static char cmd[1000];
767
768    printf("%s", prompt);
769    if (fgets(cmd, sizeof(cmd), stdin) == NULL)
770       return NULL;
771    printf("\n");
772    strip_trailing_junk(cmd);
773    return cmd;
774 }
775
776 static int yes_no(char *prompt)
777 {
778    char *cmd;  
779    cmd = get_cmd(prompt);
780    return strcasecmp(cmd, "yes") == 0;
781 }