]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/tools/dbcheck.c
5c67fd91c154e8014df6b439da819c4f1948064c
[bacula/bacula] / bacula / src / tools / dbcheck.c
1 /*
2    Bacula(R) - The Network Backup Solution
3
4    Copyright (C) 2000-2016 Kern Sibbald
5
6    The original author of Bacula is Kern Sibbald, with contributions
7    from many others, a complete list can be found in the file AUTHORS.
8
9    You may use this file and others of this release according to the
10    license defined in the LICENSE file, which includes the Affero General
11    Public License, v3.0 ("AGPLv3") and some additional permissions and
12    terms pursuant to its AGPLv3 Section 7.
13
14    This notice must be preserved when any source code is 
15    conveyed and/or propagated.
16
17    Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19 /*
20  *
21  *  Program to check a Bacula database for consistency and to
22  *   make repairs
23  *
24  *   Kern E. Sibbald, August 2002
25  *
26  */
27
28 #include "bacula.h"
29 #include "cats/cats.h"
30 #include "lib/runscript.h"
31 #include "dird/dird_conf.h"
32
33 extern bool parse_dir_config(CONFIG *config, const char *configfile, int exit_code);
34
35 typedef struct s_id_ctx {
36    int64_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 /* Global variables */ 
52
53 static bool fix = false;
54 static bool batch = false;
55 static BDB *db;
56 static ID_LIST id_list;
57 static NAME_LIST name_list;
58 static char buf[20000];
59 static bool quit = false;
60 static CONFIG *config;
61 static const char *idx_tmp_name; 
62
63 #define MAX_ID_LIST_LEN 10000000
64
65 /* Forward referenced functions */ 
66 static int make_id_list(const char *query, ID_LIST *id_list);
67 static int delete_id_list(const char *query, ID_LIST *id_list);
68 static int make_name_list(const char *query, NAME_LIST *name_list);
69 static void print_name_list(NAME_LIST *name_list);
70 static void free_name_list(NAME_LIST *name_list);
71 static char *get_cmd(const char *prompt);
72 static void eliminate_duplicate_filenames();
73 static void eliminate_duplicate_paths();
74 static void eliminate_orphaned_jobmedia_records();
75 static void eliminate_orphaned_file_records();
76 static void eliminate_orphaned_path_records();
77 static void eliminate_orphaned_filename_records();
78 static void eliminate_orphaned_fileset_records();
79 static void eliminate_orphaned_client_records();
80 static void eliminate_orphaned_job_records();
81 static void eliminate_admin_records();
82 static void eliminate_restore_records();
83 static void repair_bad_paths();
84 static void repair_bad_filenames();
85 static void do_interactive_mode();
86 static bool yes_no(const char *prompt);
87 static bool check_idx(const char *col_name);
88 static bool create_tmp_idx(const char *idx_name, const char *table_name, 
89                const char *col_name);
90 static bool drop_tmp_idx(const char *idx_name, const char *table_name);
91 static int check_idx_handler(void *ctx, int num_fields, char **row);
92
93 static void usage()
94 {
95    fprintf(stderr,
96 PROG_COPYRIGHT
97 "\n%sVersion: %s (%s)\n\n"
98 "Usage: dbcheck [-c config ] [-B] [-C catalog name] [-d debug_level] <working-directory> <bacula-database> <user> <password> [<dbhost>] [<dbport>] [<dbport>] [<dbsslkey>] [<dbsslcert>] [<dbsslca>]\n"
99 "       -b              batch mode\n"
100 "       -C              catalog name in the director conf file\n"
101 "       -c              Director conf filename\n"
102 "       -B              print catalog configuration and exit\n"
103 "       -d <nn>         set debug level to <nn>\n"
104 "       -dt             print a timestamp in debug output\n"
105 "       -f              fix inconsistencies\n"
106 "       -t              test if client library is thread-safe\n"
107 "       -v              verbose\n"
108 "       -?              print this message\n"
109 "\n", 2002, "", VERSION, BDATE);
110
111    exit(1);
112 }
113
114 int main (int argc, char *argv[])
115 {
116    int ch;
117    const char *user, *password, *db_name, *dbhost;
118    const char *dbsslkey = NULL, *dbsslcert = NULL, *dbsslca = NULL;
119    const char *dbsslcapath = NULL, *dbsslcipher = NULL;
120    int dbport = 0;
121    bool print_catalog=false;
122    char *configfile = NULL;
123    char *catalogname = NULL;
124    char *endptr;
125
126    setlocale(LC_ALL, "");
127    bindtextdomain("bacula", LOCALEDIR);
128    textdomain("bacula");
129    lmgr_init_thread();
130
131    my_name_is(argc, argv, "dbcheck");
132    init_msg(NULL, NULL);             /* setup message handler */
133
134    memset(&id_list, 0, sizeof(id_list));
135    memset(&name_list, 0, sizeof(name_list));
136
137    while ((ch = getopt(argc, argv, "bc:C:d:fvB?")) != -1) { 
138       switch (ch) {
139       case 'B':
140          print_catalog = true;     /* get catalog information from config */
141          break;
142       case 'b':                    /* batch */
143          batch = true;
144          break;
145       case 'C':                    /* CatalogName */
146           catalogname = optarg;
147          break;
148       case 'c':                    /* configfile */
149           configfile = optarg;
150          break;
151       case 'd':                    /* debug level */
152          if (*optarg == 't') {
153             dbg_timestamp = true;
154          } else {
155             debug_level = atoi(optarg);
156             if (debug_level <= 0) {
157                debug_level = 1;
158             }
159          }
160          break;
161       case 'f':                    /* fix inconsistencies */
162          fix = true;
163          break;
164       case 'v':
165          verbose++;
166          break;
167       case '?':
168       default:
169          usage();
170       }
171    }
172    argc -= optind;
173    argv += optind;
174
175    OSDependentInit();
176
177    if (configfile) {
178       CAT *catalog = NULL;
179       int found = 0;
180       if (argc > 0) {
181          Pmsg0(0, _("Warning skipping the additional parameters for working directory/dbname/user/password/host.\n"));
182       }
183       config = New(CONFIG());
184       parse_dir_config(config, configfile, M_ERROR_TERM);
185       LockRes();
186       foreach_res(catalog, R_CATALOG) {
187          if (catalogname && !strcmp(catalog->hdr.name, catalogname)) {
188             ++found;
189             break;
190          } else if (!catalogname) { // stop on first if no catalogname is given
191            ++found;
192            break;
193          }
194       }
195       UnlockRes();
196       if (!found) {
197          if (catalogname) {
198             Pmsg2(0, _("Error can not find the Catalog name[%s] in the given config file [%s]\n"), catalogname, configfile);
199          } else {
200             Pmsg1(0, _("Error there is no Catalog section in the given config file [%s]\n"), configfile);
201          }
202          exit(1);
203       } else {
204          DIRRES *director;
205          LockRes();
206          director = (DIRRES *)GetNextRes(R_DIRECTOR, NULL);
207          UnlockRes();
208          if (!director) {
209             Pmsg0(0, _("Error no Director resource defined.\n"));
210             exit(1);
211          }
212          set_working_directory(director->working_directory);
213
214          /* Print catalog information and exit (-B) */
215          if (print_catalog) {
216
217             POOLMEM *catalog_details = get_pool_memory(PM_MESSAGE);
218             db = db_init_database(NULL, catalog->db_driver, catalog->db_name, catalog->db_user,
219                     catalog->db_password, catalog->db_address,
220                     catalog->db_port, catalog->db_socket,
221                     catalog->db_ssl_key, catalog->db_ssl_cert, catalog->db_ssl_ca,
222                     catalog->db_ssl_capath, catalog->db_ssl_cipher,
223                     catalog->mult_db_connections,
224                     catalog->disable_batch_insert);
225             if (db) {
226                printf("%sdb_type=%s\nworking_dir=%s\n", catalog->display(catalog_details),
227                   db_get_engine_name(db), working_directory);
228                db_close_database(NULL, db);
229             }
230             free_pool_memory(catalog_details);
231             exit(0);
232          }
233
234          db_name = catalog->db_name;
235          user = catalog->db_user;
236          password = catalog->db_password;
237          dbhost = catalog->db_address;
238          if (dbhost && dbhost[0] == 0) {
239             dbhost = NULL;
240          }
241          dbport = catalog->db_port;
242          dbsslkey = catalog->db_ssl_key;
243          dbsslcert = catalog->db_ssl_cert;
244          dbsslca = catalog->db_ssl_ca;
245          dbsslcapath = catalog->db_ssl_capath;
246          dbsslcipher = catalog->db_ssl_cipher;
247       }
248    } else {
249       if (argc > 9) {
250          Pmsg0(0, _("Wrong number of arguments.\n"));
251          usage();
252       }
253
254       if (argc < 1) {
255          Pmsg0(0, _("Working directory not supplied.\n"));
256          usage();
257       }
258
259       /* This is needed by SQLite to find the db */
260       working_directory = argv[0];
261       db_name = "bacula";
262       user = db_name;
263       password = "";
264       dbhost = NULL;
265
266       if (argc >= 2) {
267          db_name = argv[1];
268          user = db_name;
269          if (argc >= 3) {
270             user = argv[2];
271             if (argc >= 4) {
272                password = argv[3];
273                if (argc >= 5) {
274                   dbhost = argv[4];
275                   if (argc >= 6) {
276                      errno = 0;
277                      dbport = strtol(argv[5], &endptr, 10);
278                      if (*endptr != '\0') {
279                         Pmsg0(0, _("Database port must be a numeric value.\n"));
280                         exit(1);
281                      } else if (errno == ERANGE) {
282                         Pmsg0(0, _("Database port must be a int value.\n"));
283                         exit(1);
284                      }
285                      if (argc >= 7) {
286                         dbsslkey = argv[6];
287                         dbsslcert = argv[7];
288                         if (argc == 9) {
289                            dbsslca = argv[8];
290                         } /* if (argc == 9) */
291                      } /* if (argc >= 7) */
292                   } /* if (argc >= 6) */
293                } /* if (argc >= 5) */
294             } /* if (argc >= 4) */
295          } /* if (argc >= 3) */
296       } /* if (argc >= 2) */
297    }
298
299    /* Open database */
300    db = db_init_database(NULL, NULL, db_name, user, password, dbhost,
301            dbport, NULL, dbsslkey, dbsslcert, dbsslca, dbsslcapath, dbsslcipher, false, false);
302    if (!db || !db_open_database(NULL, db)) {
303       Emsg1(M_FATAL, 0, "%s", db_strerror(db));
304           return 1;
305    }
306
307    /* Drop temporary index idx_tmp_name if it already exists */
308    drop_tmp_idx("idxPIchk", "File");
309
310    if (batch) {
311       repair_bad_paths();
312       repair_bad_filenames();
313       eliminate_duplicate_filenames();
314       eliminate_duplicate_paths();
315       eliminate_orphaned_jobmedia_records();
316       eliminate_orphaned_file_records();
317       eliminate_orphaned_path_records();
318       eliminate_orphaned_filename_records();
319       eliminate_orphaned_fileset_records();
320       eliminate_orphaned_client_records();
321       eliminate_orphaned_job_records();
322       eliminate_admin_records();
323       eliminate_restore_records();
324    } else {
325       do_interactive_mode();
326    }
327
328    /* Drop temporary index idx_tmp_name */
329    drop_tmp_idx("idxPIchk", "File");
330
331    if (db) db_close_database(NULL, db);
332    close_msg(NULL);
333    term_msg();
334    lmgr_cleanup_main();
335    return 0;
336 }
337
338 static void do_interactive_mode()
339 {
340    const char *cmd;
341
342    printf(_("Hello, this is the database check/correct program.\n"));
343    if (fix)
344       printf(_("Modify database is on."));
345    else
346       printf(_("Modify database is off."));
347    if (verbose)
348       printf(_(" Verbose is on.\n"));
349    else
350       printf(_(" Verbose is off.\n"));
351
352    printf(_("Please select the function you want to perform.\n"));
353
354    while (!quit) {
355       if (fix) {
356          printf(_("\n"
357 "     1) Toggle modify database flag\n"
358 "     2) Toggle verbose flag\n"
359 "     3) Repair bad Filename records\n"
360 "     4) Repair bad Path records\n"
361 "     5) Eliminate duplicate Filename records\n"
362 "     6) Eliminate duplicate Path records\n"
363 "     7) Eliminate orphaned Jobmedia records\n"
364 "     8) Eliminate orphaned File records\n"
365 "     9) Eliminate orphaned Path records\n"
366 "    10) Eliminate orphaned Filename records\n"
367 "    11) Eliminate orphaned FileSet records\n"
368 "    12) Eliminate orphaned Client records\n"
369 "    13) Eliminate orphaned Job records\n"
370 "    14) Eliminate all Admin records\n"
371 "    15) Eliminate all Restore records\n"
372 "    16) All (3-15)\n"
373 "    17) Quit\n"));
374        } else {
375          printf(_("\n"
376 "     1) Toggle modify database flag\n"
377 "     2) Toggle verbose flag\n"
378 "     3) Check for bad Filename records\n"
379 "     4) Check for bad Path records\n"
380 "     5) Check for duplicate Filename records\n"
381 "     6) Check for duplicate Path records\n"
382 "     7) Check for orphaned Jobmedia records\n"
383 "     8) Check for orphaned File records\n"
384 "     9) Check for orphaned Path records\n"
385 "    10) Check for orphaned Filename records\n"
386 "    11) Check for orphaned FileSet records\n"
387 "    12) Check for orphaned Client records\n"
388 "    13) Check for orphaned Job records\n"
389 "    14) Check for all Admin records\n"
390 "    15) Check for all Restore records\n"
391 "    16) All (3-15)\n"
392 "    17) Quit\n"));
393        }
394
395       cmd = get_cmd(_("Select function number: "));
396       if (cmd) {
397          int item = atoi(cmd);
398          switch (item) {
399          case 1:
400             fix = !fix;
401             if (fix)
402                printf(_("Database will be modified.\n"));
403             else
404                printf(_("Database will NOT be modified.\n"));
405             break;
406          case 2:
407             verbose = verbose?0:1;
408             if (verbose)
409                printf(_(" Verbose is on.\n"));
410             else
411                printf(_(" Verbose is off.\n"));
412             break;
413          case 3:
414             repair_bad_filenames();
415             break;
416          case 4:
417             repair_bad_paths();
418             break;
419          case 5:
420             eliminate_duplicate_filenames();
421             break;
422          case 6:
423             eliminate_duplicate_paths();
424             break;
425          case 7:
426             eliminate_orphaned_jobmedia_records();
427             break;
428          case 8:
429             eliminate_orphaned_file_records();
430             break;
431          case 9:
432             eliminate_orphaned_path_records();
433             break;
434          case 10:
435             eliminate_orphaned_filename_records();
436             break;
437          case 11:
438             eliminate_orphaned_fileset_records();
439             break;
440          case 12:
441             eliminate_orphaned_client_records();
442             break;
443          case 13:
444             eliminate_orphaned_job_records();
445             break;
446          case 14:
447             eliminate_admin_records();
448             break;
449          case 15:
450             eliminate_restore_records();
451             break;
452          case 16:
453             repair_bad_filenames();
454             repair_bad_paths();
455             eliminate_duplicate_filenames();
456             eliminate_duplicate_paths();
457             eliminate_orphaned_jobmedia_records();
458             eliminate_orphaned_file_records();
459             eliminate_orphaned_path_records();
460             eliminate_orphaned_filename_records();
461             eliminate_orphaned_fileset_records();
462             eliminate_orphaned_client_records();
463             eliminate_orphaned_job_records();
464             eliminate_admin_records();
465             eliminate_restore_records();
466             break;
467          case 17:
468             quit = true;
469             break;
470          }
471       }
472    }
473 }
474
475 static int print_name_handler(void *ctx, int num_fields, char **row)
476 {
477    if (row[0]) {
478       printf("%s\n", row[0]);
479    }
480    return 0;
481 }
482
483 static int get_name_handler(void *ctx, int num_fields, char **row)
484 {
485    POOLMEM *buf = (POOLMEM *)ctx;
486    if (row[0]) {
487       pm_strcpy(&buf, row[0]);
488    }
489    return 0;
490 }
491
492 static int print_job_handler(void *ctx, int num_fields, char **row)
493 {
494    printf(_("JobId=%s Name=\"%s\" StartTime=%s\n"),
495               NPRT(row[0]), NPRT(row[1]), NPRT(row[2]));
496    return 0;
497 }
498
499 static int print_jobmedia_handler(void *ctx, int num_fields, char **row)
500 {
501    printf(_("Orphaned JobMediaId=%s JobId=%s Volume=\"%s\"\n"),
502               NPRT(row[0]), NPRT(row[1]), NPRT(row[2]));
503    return 0;
504 }
505
506 static int print_file_handler(void *ctx, int num_fields, char **row)
507 {
508    printf(_("Orphaned FileId=%s JobId=%s Volume=\"%s\"\n"),
509               NPRT(row[0]), NPRT(row[1]), NPRT(row[2]));
510    return 0;
511 }
512
513 static int print_fileset_handler(void *ctx, int num_fields, char **row)
514 {
515    printf(_("Orphaned FileSetId=%s FileSet=\"%s\" MD5=%s\n"),
516               NPRT(row[0]), NPRT(row[1]), NPRT(row[2]));
517    return 0;
518 }
519
520 static int print_client_handler(void *ctx, int num_fields, char **row)
521 {
522    printf(_("Orphaned ClientId=%s Name=\"%s\"\n"),
523               NPRT(row[0]), NPRT(row[1]));
524    return 0;
525 }
526
527 /*
528  * Called here with each id to be added to the list
529  */
530 static int id_list_handler(void *ctx, int num_fields, char **row)
531 {
532    ID_LIST *lst = (ID_LIST *)ctx;
533
534    if (lst->num_ids == MAX_ID_LIST_LEN) {
535       return 1;
536    }
537    if (lst->num_ids == lst->max_ids) {
538       if (lst->max_ids == 0) {
539          lst->max_ids = 10000;
540          lst->Id = (int64_t *)bmalloc(sizeof(int64_t) * lst->max_ids);
541       } else {
542          lst->max_ids = (lst->max_ids * 3) / 2;
543          lst->Id = (int64_t *)brealloc(lst->Id, sizeof(int64_t) * lst->max_ids);
544       }
545    }
546    lst->Id[lst->num_ids++] = str_to_int64(row[0]);
547    return 0;
548 }
549
550 /*
551  * Construct record id list
552  */
553 static int make_id_list(const char *query, ID_LIST *id_list)
554 {
555    id_list->num_ids = 0;
556    id_list->num_del = 0;
557    id_list->tot_ids = 0;
558
559    if (!db_sql_query(db, query, id_list_handler, (void *)id_list)) {
560       printf("%s", db_strerror(db));
561       return 0;
562    }
563    return 1;
564 }
565
566 /*
567  * Delete all entries in the list
568  */
569 static int delete_id_list(const char *query, ID_LIST *id_list)
570 {
571    char ed1[50];
572    for (int i=0; i < id_list->num_ids; i++) {
573       bsnprintf(buf, sizeof(buf), query, edit_int64(id_list->Id[i], ed1));
574       if (verbose) {
575          printf(_("Deleting: %s\n"), buf);
576       }
577       db_sql_query(db, buf, NULL, NULL);
578    }
579    return 1;
580 }
581
582 /*
583  * Called here with each name to be added to the list
584  */
585 static int name_list_handler(void *ctx, int num_fields, char **row)
586 {
587    NAME_LIST *name = (NAME_LIST *)ctx;
588
589    if (name->num_ids == MAX_ID_LIST_LEN) {
590       return 1;
591    }
592    if (name->num_ids == name->max_ids) {
593       if (name->max_ids == 0) {
594          name->max_ids = 10000;
595          name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
596       } else {
597          name->max_ids = (name->max_ids * 3) / 2;
598          name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
599       }
600    }
601    name->name[name->num_ids++] = bstrdup(row[0]);
602    return 0;
603 }
604
605 /*
606  * Construct name list
607  */
608 static int make_name_list(const char *query, NAME_LIST *name_list)
609 {
610    name_list->num_ids = 0;
611    name_list->num_del = 0;
612    name_list->tot_ids = 0;
613
614    if (!db_sql_query(db, query, name_list_handler, (void *)name_list)) {
615       printf("%s", db_strerror(db));
616       return 0;
617    }
618    return 1;
619 }
620
621 /*
622  * Print names in the list
623  */
624 static void print_name_list(NAME_LIST *name_list)
625 {
626    for (int i=0; i < name_list->num_ids; i++) {
627       printf("%s\n", name_list->name[i]);
628    }
629 }
630
631 /*
632  * Free names in the list
633  */
634 static void free_name_list(NAME_LIST *name_list)
635 {
636    for (int i=0; i < name_list->num_ids; i++) {
637       free(name_list->name[i]);
638    }
639    name_list->num_ids = 0;
640 }
641
642 static void eliminate_duplicate_filenames()
643 {
644    const char *query;
645    char esc_name[5000];
646
647    printf(_("Checking for duplicate Filename entries.\n"));
648
649    /* Make list of duplicated names */
650    query = "SELECT Name, count(Name) as Count FROM Filename GROUP BY  Name "
651            "HAVING count(Name) > 1";
652
653    if (!make_name_list(query, &name_list)) {
654       exit(1);
655    }
656    printf(_("Found %d duplicate Filename records.\n"), name_list.num_ids);
657    if (name_list.num_ids && verbose && yes_no(_("Print the list? (yes/no): "))) {
658       print_name_list(&name_list);
659    }
660    if (quit) {
661       return;
662    }
663    if (fix) {
664       /* Loop through list of duplicate names */
665       for (int i=0; i<name_list.num_ids; i++) {
666          /* Get all the Ids of each name */
667          db_escape_string(NULL, db, esc_name, name_list.name[i], strlen(name_list.name[i]));
668          bsnprintf(buf, sizeof(buf), "SELECT FilenameId FROM Filename WHERE Name='%s'", esc_name);
669          if (verbose > 1) {
670             printf("%s\n", buf);
671          }
672          if (!make_id_list(buf, &id_list)) {
673             exit(1);
674          }
675          if (verbose) {
676             printf(_("Found %d for: %s\n"), id_list.num_ids, name_list.name[i]);
677          }
678          /* Force all records to use the first id then delete the other ids */
679          for (int j=1; j<id_list.num_ids; j++) {
680             char ed1[50], ed2[50];
681             bsnprintf(buf, sizeof(buf), "UPDATE File SET FilenameId=%s WHERE FilenameId=%s",
682                edit_int64(id_list.Id[0], ed1), edit_int64(id_list.Id[j], ed2));
683             if (verbose > 1) {
684                printf("%s\n", buf);
685             }
686             db_sql_query(db, buf, NULL, NULL);
687             bsnprintf(buf, sizeof(buf), "DELETE FROM Filename WHERE FilenameId=%s",
688                ed2);
689             if (verbose > 2) {
690                printf("%s\n", buf);
691             }
692             db_sql_query(db, buf, NULL, NULL);
693          }
694       }
695    }
696    free_name_list(&name_list);
697 }
698
699 static void eliminate_duplicate_paths()
700 {
701    const char *query;
702    char esc_name[5000];
703
704    printf(_("Checking for duplicate Path entries.\n"));
705
706    /* Make list of duplicated names */
707    query = "SELECT Path, count(Path) as Count FROM Path "
708            "GROUP BY Path HAVING count(Path) > 1";
709
710    if (!make_name_list(query, &name_list)) {
711       exit(1);
712    }
713    printf(_("Found %d duplicate Path records.\n"), name_list.num_ids);
714    if (name_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
715       print_name_list(&name_list);
716    }
717    if (quit) {
718       return;
719    }
720    if (fix) {
721       /* Loop through list of duplicate names */
722       for (int i=0; i<name_list.num_ids; i++) {
723          /* Get all the Ids of each name */
724          db_escape_string(NULL, db,  esc_name, name_list.name[i], strlen(name_list.name[i]));
725          bsnprintf(buf, sizeof(buf), "SELECT PathId FROM Path WHERE Path='%s'", esc_name);
726          if (verbose > 1) {
727             printf("%s\n", buf);
728          }
729          if (!make_id_list(buf, &id_list)) {
730             exit(1);
731          }
732          if (verbose) {
733             printf(_("Found %d for: %s\n"), id_list.num_ids, name_list.name[i]);
734          }
735          /* Force all records to use the first id then delete the other ids */
736          for (int j=1; j<id_list.num_ids; j++) {
737             char ed1[50], ed2[50];
738             bsnprintf(buf, sizeof(buf), "UPDATE File SET PathId=%s WHERE PathId=%s",
739                edit_int64(id_list.Id[0], ed1), edit_int64(id_list.Id[j], ed2));
740             if (verbose > 1) {
741                printf("%s\n", buf);
742             }
743             db_sql_query(db, buf, NULL, NULL);
744             bsnprintf(buf, sizeof(buf), "DELETE FROM Path WHERE PathId=%s", ed2);
745             if (verbose > 2) {
746                printf("%s\n", buf);
747             }
748             db_sql_query(db, buf, NULL, NULL);
749          }
750       }
751    }
752    free_name_list(&name_list);
753 }
754
755 static void eliminate_orphaned_jobmedia_records()
756 {
757    const char *query = "SELECT JobMedia.JobMediaId,Job.JobId FROM JobMedia "
758                 "LEFT OUTER JOIN Job ON (JobMedia.JobId=Job.JobId) "
759                 "WHERE Job.JobId IS NULL LIMIT 300000";
760
761    printf(_("Checking for orphaned JobMedia entries.\n"));
762    if (!make_id_list(query, &id_list)) {
763       exit(1);
764    }
765    /* Loop doing 300000 at a time */
766    while (id_list.num_ids != 0) {
767       printf(_("Found %d orphaned JobMedia records.\n"), id_list.num_ids);
768       if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
769          for (int i=0; i < id_list.num_ids; i++) {
770             char ed1[50];
771             bsnprintf(buf, sizeof(buf),
772 "SELECT JobMedia.JobMediaId,JobMedia.JobId,Media.VolumeName FROM JobMedia,Media "
773         "WHERE JobMedia.JobMediaId=%s AND Media.MediaId=JobMedia.MediaId",
774                edit_int64(id_list.Id[i], ed1));
775             if (!db_sql_query(db, buf, print_jobmedia_handler, NULL)) {
776                printf("%s\n", db_strerror(db));
777             }
778          }
779       }
780       if (quit) {
781          return;
782       }
783
784       if (fix && id_list.num_ids > 0) {
785          printf(_("Deleting %d orphaned JobMedia records.\n"), id_list.num_ids);
786          delete_id_list("DELETE FROM JobMedia WHERE JobMediaId=%s", &id_list);
787       } else {
788          break;                       /* get out if not updating db */
789       }
790       if (!make_id_list(query, &id_list)) {
791          exit(1);
792       }
793    }
794 }
795
796 static void eliminate_orphaned_file_records()
797 {
798    const char *query = "SELECT File.FileId,Job.JobId FROM File "
799                 "LEFT OUTER JOIN Job ON (File.JobId=Job.JobId) "
800                "WHERE Job.JobId IS NULL LIMIT 300000";
801
802    printf(_("Checking for orphaned File entries. This may take some time!\n"));
803    if (verbose > 1) {
804       printf("%s\n", query);
805    }
806    if (!make_id_list(query, &id_list)) {
807       exit(1);
808    }
809    /* Loop doing 300000 at a time */
810    while (id_list.num_ids != 0) {
811       printf(_("Found %d orphaned File records.\n"), id_list.num_ids);
812       if (name_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
813          for (int i=0; i < id_list.num_ids; i++) {
814             char ed1[50];
815             bsnprintf(buf, sizeof(buf),
816 "SELECT File.FileId,File.JobId,Filename.Name FROM File,Filename "
817    "WHERE File.FileId=%s AND File.FilenameId=Filename.FilenameId",
818                edit_int64(id_list.Id[i], ed1));
819             if (!db_sql_query(db, buf, print_file_handler, NULL)) {
820                printf("%s\n", db_strerror(db));
821             }
822          }
823       }
824       if (quit) {
825          return;
826       }
827       if (fix && id_list.num_ids > 0) {
828          printf(_("Deleting %d orphaned File records.\n"), id_list.num_ids);
829          delete_id_list("DELETE FROM File WHERE FileId=%s", &id_list);
830       } else {
831          break;                       /* get out if not updating db */
832       }
833       if (!make_id_list(query, &id_list)) {
834          exit(1);
835       }
836    }
837 }
838
839 static void eliminate_orphaned_path_records()
840 {
841    db_int64_ctx lctx;
842    lctx.count=0;
843    db_sql_query(db, "SELECT 1 FROM Job WHERE HasCache=1 LIMIT 1", 
844                 db_int64_handler, &lctx);
845    
846    if (lctx.count == 1) {
847       printf(_("Pruning orphaned Path entries isn't possible when using BVFS.\n"));
848       return;
849    }
850
851    idx_tmp_name = NULL;
852    /* Check the existence of the required "one column" index */
853    if (!check_idx("PathId"))  {
854       if (yes_no(_("Create temporary index? (yes/no): "))) {
855          /* create temporary index PathId */
856          create_tmp_idx("idxPIchk", "File", "PathId");
857       }
858    }
859
860    const char *query = "SELECT DISTINCT Path.PathId,File.PathId FROM Path "
861                "LEFT OUTER JOIN File ON (Path.PathId=File.PathId) "
862                "WHERE File.PathId IS NULL LIMIT 300000";
863
864    printf(_("Checking for orphaned Path entries. This may take some time!\n"));
865    if (verbose > 1) {
866       printf("%s\n", query);
867    }
868    if (!make_id_list(query, &id_list)) {
869       exit(1);
870    }
871    /* Loop doing 300000 at a time */
872    while (id_list.num_ids != 0) {
873       printf(_("Found %d orphaned Path records.\n"), id_list.num_ids);
874       if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
875          for (int i=0; i < id_list.num_ids; i++) {
876             char ed1[50];
877             bsnprintf(buf, sizeof(buf), "SELECT Path FROM Path WHERE PathId=%s", 
878                edit_int64(id_list.Id[i], ed1));
879             db_sql_query(db, buf, print_name_handler, NULL);
880          }
881       }
882       if (quit) {
883          return;
884       }
885       if (fix && id_list.num_ids > 0) {
886          printf(_("Deleting %d orphaned Path records.\n"), id_list.num_ids);
887          delete_id_list("DELETE FROM Path WHERE PathId=%s", &id_list);
888       } else {
889          break;                       /* get out if not updating db */
890       }
891       if (!make_id_list(query, &id_list)) {
892          exit(1);
893       }
894    } 
895    /* Drop temporary index idx_tmp_name */
896    drop_tmp_idx("idxPIchk", "File");
897 }
898
899 static void eliminate_orphaned_filename_records()
900 {
901    idx_tmp_name = NULL;
902    /* Check the existence of the required "one column" index */
903    if (!check_idx("FilenameId") )      {
904       if (yes_no(_("Create temporary index? (yes/no): "))) {
905          /* Create temporary index FilenameId */
906          create_tmp_idx("idxFIchk", "File", "FilenameId");
907       }
908    }
909
910    const char *query = "SELECT Filename.FilenameId,File.FilenameId FROM Filename "
911                 "LEFT OUTER JOIN File ON (Filename.FilenameId=File.FilenameId) "
912                 "WHERE File.FilenameId IS NULL LIMIT 300000";
913
914    printf(_("Checking for orphaned Filename entries. This may take some time!\n"));
915    if (verbose > 1) {
916       printf("%s\n", query);
917    }
918    if (!make_id_list(query, &id_list)) {
919       exit(1);
920    }
921    /* Loop doing 300000 at a time */
922    while (id_list.num_ids != 0) {
923       printf(_("Found %d orphaned Filename records.\n"), id_list.num_ids);
924       if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
925          for (int i=0; i < id_list.num_ids; i++) {
926             char ed1[50];
927             bsnprintf(buf, sizeof(buf), "SELECT Name FROM Filename WHERE FilenameId=%s", 
928                edit_int64(id_list.Id[i], ed1));
929             db_sql_query(db, buf, print_name_handler, NULL);
930          }
931       }
932       if (quit) {
933          return;
934       }
935       if (fix && id_list.num_ids > 0) {
936          printf(_("Deleting %d orphaned Filename records.\n"), id_list.num_ids);
937          delete_id_list("DELETE FROM Filename WHERE FilenameId=%s", &id_list);
938       } else {
939          break;                       /* get out if not updating db */
940       }
941       if (!make_id_list(query, &id_list)) {
942          exit(1);
943       }
944    }
945    /* Drop temporary index idx_tmp_name */
946    drop_tmp_idx("idxFIchk", "File");
947
948 }
949
950 static void eliminate_orphaned_fileset_records()
951 {
952    const char *query;
953
954    printf(_("Checking for orphaned FileSet entries. This takes some time!\n"));
955    query = "SELECT FileSet.FileSetId,Job.FileSetId FROM FileSet "
956            "LEFT OUTER JOIN Job ON (FileSet.FileSetId=Job.FileSetId) "
957            "WHERE Job.FileSetId IS NULL";
958    if (verbose > 1) {
959       printf("%s\n", query);
960    }
961    if (!make_id_list(query, &id_list)) {
962       exit(1);
963    }
964    printf(_("Found %d orphaned FileSet records.\n"), id_list.num_ids);
965    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
966       for (int i=0; i < id_list.num_ids; i++) {
967          char ed1[50];
968          bsnprintf(buf, sizeof(buf), "SELECT FileSetId,FileSet,MD5 FROM FileSet "
969                       "WHERE FileSetId=%s", edit_int64(id_list.Id[i], ed1));
970          if (!db_sql_query(db, buf, print_fileset_handler, NULL)) {
971             printf("%s\n", db_strerror(db));
972          }
973       }
974    }
975    if (quit) {
976       return;
977    }
978    if (fix && id_list.num_ids > 0) {
979       printf(_("Deleting %d orphaned FileSet records.\n"), id_list.num_ids);
980       delete_id_list("DELETE FROM FileSet WHERE FileSetId=%s", &id_list);
981    }
982 }
983
984 static void eliminate_orphaned_client_records()
985 {
986    const char *query;
987
988    printf(_("Checking for orphaned Client entries.\n"));
989    /* In English:
990     *   Wiffle through Client for every Client
991     *   joining with the Job table including every Client even if
992     *   there is not a match in Job (left outer join), then
993     *   filter out only those where no Job points to a Client
994     *   i.e. Job.Client is NULL
995     */
996    query = "SELECT Client.ClientId,Client.Name FROM Client "
997            "LEFT OUTER JOIN Job ON (Client.ClientId=Job.ClientId) "
998            "WHERE Job.ClientId IS NULL";
999    if (verbose > 1) {
1000       printf("%s\n", query);
1001    }
1002    if (!make_id_list(query, &id_list)) {
1003       exit(1);
1004    }
1005    printf(_("Found %d orphaned Client records.\n"), id_list.num_ids);
1006    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
1007       for (int i=0; i < id_list.num_ids; i++) {
1008          char ed1[50];
1009          bsnprintf(buf, sizeof(buf), "SELECT ClientId,Name FROM Client "
1010                       "WHERE ClientId=%s", edit_int64(id_list.Id[i], ed1));
1011          if (!db_sql_query(db, buf, print_client_handler, NULL)) {
1012             printf("%s\n", db_strerror(db));
1013          }
1014       }
1015    }
1016    if (quit) {
1017       return;
1018    }
1019    if (fix && id_list.num_ids > 0) {
1020       printf(_("Deleting %d orphaned Client records.\n"), id_list.num_ids);
1021       delete_id_list("DELETE FROM Client WHERE ClientId=%s", &id_list);
1022    }
1023 }
1024
1025 static void eliminate_orphaned_job_records()
1026 {
1027    const char *query;
1028
1029    printf(_("Checking for orphaned Job entries.\n"));
1030    /* In English:
1031     *   Wiffle through Job for every Job
1032     *   joining with the Client table including every Job even if
1033     *   there is not a match in Client (left outer join), then
1034     *   filter out only those where no Client exists
1035     *   i.e. Client.Name is NULL
1036     */
1037    query = "SELECT Job.JobId,Job.Name FROM Job "
1038            "LEFT OUTER JOIN Client ON (Job.ClientId=Client.ClientId) "
1039            "WHERE Client.Name IS NULL";
1040    if (verbose > 1) {
1041       printf("%s\n", query);
1042    }
1043    if (!make_id_list(query, &id_list)) {
1044       exit(1);
1045    }
1046    printf(_("Found %d orphaned Job records.\n"), id_list.num_ids);
1047    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
1048       for (int i=0; i < id_list.num_ids; i++) {
1049          char ed1[50];
1050          bsnprintf(buf, sizeof(buf), "SELECT JobId,Name,StartTime FROM Job "
1051                       "WHERE JobId=%s", edit_int64(id_list.Id[i], ed1));
1052          if (!db_sql_query(db, buf, print_job_handler, NULL)) {
1053             printf("%s\n", db_strerror(db));
1054          }
1055       }
1056    }
1057    if (quit) {
1058       return;
1059    }
1060    if (fix && id_list.num_ids > 0) {
1061       printf(_("Deleting %d orphaned Job records.\n"), id_list.num_ids);
1062       delete_id_list("DELETE FROM Job WHERE JobId=%s", &id_list);
1063       printf(_("Deleting JobMedia records of orphaned Job records.\n"));
1064       delete_id_list("DELETE FROM JobMedia WHERE JobId=%s", &id_list);
1065       printf(_("Deleting Log records of orphaned Job records.\n"));
1066       delete_id_list("DELETE FROM Log WHERE JobId=%s", &id_list);
1067    }
1068 }
1069
1070 static void eliminate_admin_records()
1071 {
1072    const char *query;
1073
1074    printf(_("Checking for Admin Job entries.\n"));
1075    query = "SELECT Job.JobId FROM Job "
1076            "WHERE Job.Type='D'";
1077    if (verbose > 1) {
1078       printf("%s\n", query);
1079    }
1080    if (!make_id_list(query, &id_list)) {
1081       exit(1);
1082    }
1083    printf(_("Found %d Admin Job records.\n"), id_list.num_ids);
1084    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
1085       for (int i=0; i < id_list.num_ids; i++) {
1086          char ed1[50];
1087          bsnprintf(buf, sizeof(buf), "SELECT JobId,Name,StartTime FROM Job "
1088                       "WHERE JobId=%s", edit_int64(id_list.Id[i], ed1));
1089          if (!db_sql_query(db, buf, print_job_handler, NULL)) {
1090             printf("%s\n", db_strerror(db));
1091          }
1092       }
1093    }
1094    if (quit) {
1095       return;
1096    }
1097    if (fix && id_list.num_ids > 0) {
1098       printf(_("Deleting %d Admin Job records.\n"), id_list.num_ids);
1099       delete_id_list("DELETE FROM Job WHERE JobId=%s", &id_list);
1100    }
1101 }
1102
1103 static void eliminate_restore_records()
1104 {
1105    const char *query;
1106
1107    printf(_("Checking for Restore Job entries.\n"));
1108    query = "SELECT Job.JobId FROM Job "
1109            "WHERE Job.Type='R'";
1110    if (verbose > 1) {
1111       printf("%s\n", query);
1112    }
1113    if (!make_id_list(query, &id_list)) {
1114       exit(1);
1115    }
1116    printf(_("Found %d Restore Job records.\n"), id_list.num_ids);
1117    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
1118       for (int i=0; i < id_list.num_ids; i++) {
1119          char ed1[50];
1120          bsnprintf(buf, sizeof(buf), "SELECT JobId,Name,StartTime FROM Job "
1121                       "WHERE JobId=%s", edit_int64(id_list.Id[i], ed1));
1122          if (!db_sql_query(db, buf, print_job_handler, NULL)) {
1123             printf("%s\n", db_strerror(db));
1124          }
1125       }
1126    }
1127    if (quit) {
1128       return;
1129    }
1130    if (fix && id_list.num_ids > 0) {
1131       printf(_("Deleting %d Restore Job records.\n"), id_list.num_ids);
1132       delete_id_list("DELETE FROM Job WHERE JobId=%s", &id_list);
1133    }
1134 }
1135
1136 static void repair_bad_filenames()
1137 {
1138    const char *query;
1139    int i;
1140
1141    printf(_("Checking for Filenames with a trailing slash\n"));
1142    query = "SELECT FilenameId,Name from Filename "
1143            "WHERE Name LIKE '%/'";
1144    if (verbose > 1) {
1145       printf("%s\n", query);
1146    }
1147    if (!make_id_list(query, &id_list)) {
1148       exit(1);
1149    }
1150    printf(_("Found %d bad Filename records.\n"), id_list.num_ids);
1151    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
1152       for (i=0; i < id_list.num_ids; i++) {
1153          char ed1[50];
1154          bsnprintf(buf, sizeof(buf),
1155             "SELECT Name FROM Filename WHERE FilenameId=%s", 
1156                 edit_int64(id_list.Id[i], ed1));
1157          if (!db_sql_query(db, buf, print_name_handler, NULL)) {
1158             printf("%s\n", db_strerror(db));
1159          }
1160       }
1161    }
1162    if (quit) {
1163       return;
1164    }
1165    if (fix && id_list.num_ids > 0) {
1166       POOLMEM *name = get_pool_memory(PM_FNAME);
1167       char esc_name[5000];
1168       printf(_("Reparing %d bad Filename records.\n"), id_list.num_ids);
1169       for (i=0; i < id_list.num_ids; i++) {
1170          int len;
1171          char ed1[50];
1172          bsnprintf(buf, sizeof(buf),
1173             "SELECT Name FROM Filename WHERE FilenameId=%s", 
1174                edit_int64(id_list.Id[i], ed1));
1175          if (!db_sql_query(db, buf, get_name_handler, name)) {
1176             printf("%s\n", db_strerror(db));
1177          }
1178          /* Strip trailing slash(es) */
1179          for (len=strlen(name); len > 0 && IsPathSeparator(name[len-1]); len--)
1180             {  }
1181          if (len == 0) {
1182             len = 1;
1183             esc_name[0] = ' ';
1184             esc_name[1] = 0;
1185          } else {
1186             name[len-1] = 0;
1187             db_escape_string(NULL, db, esc_name, name, len);
1188          }
1189          bsnprintf(buf, sizeof(buf),
1190             "UPDATE Filename SET Name='%s' WHERE FilenameId=%s",
1191             esc_name, edit_int64(id_list.Id[i], ed1));
1192          if (verbose > 1) {
1193             printf("%s\n", buf);
1194          }
1195          db_sql_query(db, buf, NULL, NULL);
1196       }
1197       free_pool_memory(name); 
1198    }
1199 }
1200
1201 static void repair_bad_paths()
1202 {
1203    const char *query;
1204    int i;
1205
1206    printf(_("Checking for Paths without a trailing slash\n"));
1207    query = "SELECT PathId,Path from Path "
1208            "WHERE Path NOT LIKE '%/'";
1209    if (verbose > 1) {
1210       printf("%s\n", query);
1211    }
1212    if (!make_id_list(query, &id_list)) {
1213       exit(1);
1214    }
1215    printf(_("Found %d bad Path records.\n"), id_list.num_ids);
1216    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
1217       for (i=0; i < id_list.num_ids; i++) {
1218          char ed1[50];
1219          bsnprintf(buf, sizeof(buf),
1220             "SELECT Path FROM Path WHERE PathId=%s", edit_int64(id_list.Id[i], ed1));
1221          if (!db_sql_query(db, buf, print_name_handler, NULL)) {
1222             printf("%s\n", db_strerror(db));
1223          }
1224       }
1225    }
1226    if (quit) {
1227       return;
1228    }
1229    if (fix && id_list.num_ids > 0) {
1230       POOLMEM *name = get_pool_memory(PM_FNAME);
1231       char esc_name[5000];
1232       printf(_("Reparing %d bad Filename records.\n"), id_list.num_ids);
1233       for (i=0; i < id_list.num_ids; i++) {
1234          int len;
1235          char ed1[50];
1236          bsnprintf(buf, sizeof(buf),
1237             "SELECT Path FROM Path WHERE PathId=%s", edit_int64(id_list.Id[i], ed1));
1238          if (!db_sql_query(db, buf, get_name_handler, name)) {
1239             printf("%s\n", db_strerror(db));
1240          }
1241          /* Strip trailing blanks */
1242          for (len=strlen(name); len > 0 && name[len-1]==' '; len--) {
1243             name[len-1] = 0;
1244          }
1245          /* Add trailing slash */
1246          len = pm_strcat(&name, "/");
1247          db_escape_string(NULL, db,  esc_name, name, len);
1248          bsnprintf(buf, sizeof(buf), "UPDATE Path SET Path='%s' WHERE PathId=%s",
1249             esc_name, edit_int64(id_list.Id[i], ed1));
1250          if (verbose > 1) {
1251             printf("%s\n", buf);
1252          }
1253          db_sql_query(db, buf, NULL, NULL);
1254       }
1255       free_pool_memory(name); 
1256    }
1257 }
1258
1259 /*
1260  * Gen next input command from the terminal
1261  */
1262 static char *get_cmd(const char *prompt)
1263 {
1264    static char cmd[1000];
1265
1266    printf("%s", prompt);
1267    if (fgets(cmd, sizeof(cmd), stdin) == NULL) {
1268       printf("\n");
1269       quit = true;
1270       return NULL;
1271    }
1272    strip_trailing_junk(cmd);
1273    return cmd;
1274 }
1275
1276 static bool yes_no(const char *prompt)
1277 {
1278    char *cmd;
1279    cmd = get_cmd(prompt);
1280    if (!cmd) {
1281       quit = true;
1282       return false;
1283    }
1284    return (strcasecmp(cmd, "yes") == 0) || (strcasecmp(cmd, _("yes")) == 0);
1285 }
1286
1287 bool python_set_prog(JCR*, char const*) { return false; }
1288
1289 /*
1290  * The code below to add indexes is needed only for MySQL, and
1291  *  that to improve the performance.
1292  */
1293
1294 #define MAXIDX          100
1295 typedef struct s_idx_list {
1296    char *key_name;
1297    int  count_key; /* how many times the index meets *key_name */
1298    int  count_col; /* how many times meets the desired column name */
1299 } IDX_LIST;
1300
1301 static IDX_LIST idx_list[MAXIDX];
1302
1303 /* 
1304  * Called here with each table index to be added to the list
1305  */
1306 static int check_idx_handler(void *ctx, int num_fields, char **row)
1307 {
1308    /* 
1309     * Table | Non_unique | Key_name | Seq_in_index | Column_name |... 
1310     * File  |          0 | PRIMARY  |            1 | FileId      |... 
1311     */ 
1312    char *name, *key_name, *col_name;
1313    int i, len;
1314    int found = false;
1315  
1316    name = (char *)ctx;
1317    key_name = row[2];
1318    col_name = row[4];
1319    for(i = 0; (idx_list[i].key_name != NULL) && (i < MAXIDX); i++) {
1320       if (strcasecmp(idx_list[i].key_name, key_name) == 0 ) {
1321          idx_list[i].count_key++;
1322          found = true;
1323          if (strcasecmp(col_name, name) == 0) {
1324             idx_list[i].count_col++;
1325          }
1326          break;
1327       }
1328    }
1329    /* If the new Key_name, add it to the list */
1330    if (!found) {
1331       len = strlen(key_name) + 1;
1332       idx_list[i].key_name = (char *)malloc(len);
1333       bstrncpy(idx_list[i].key_name, key_name, len);
1334       idx_list[i].count_key = 1;
1335       if (strcasecmp(col_name, name) == 0) {
1336          idx_list[i].count_col = 1;
1337       } else {
1338          idx_list[i].count_col = 0;
1339       }
1340    }
1341    return 0;
1342 }
1343
1344 /*
1345  * Return TRUE if "one column" index over *col_name exists
1346  */
1347 static bool check_idx(const char *col_name)
1348 {
1349    int i;
1350    int found = false;
1351    const char *query = "SHOW INDEX FROM File";
1352
1353    if (db_get_type_index(db) != SQL_TYPE_MYSQL) {
1354       return true;
1355    }
1356    /* Continue for MySQL */
1357    memset(&idx_list, 0, sizeof(idx_list));
1358    if (!db_sql_query(db, query, check_idx_handler, (void *)col_name)) {
1359       printf("%s\n", db_strerror(db));
1360    }
1361    for (i = 0; (idx_list[i].key_name != NULL) && (i < MAXIDX) ; i++) {
1362       /*
1363        * NOTE : if (idx_list[i].count_key > 1) then index idx_list[i].key_name is "multiple-column" index
1364        */
1365       if ((idx_list[i].count_key == 1) && (idx_list[i].count_col == 1)) {
1366          /* "one column" index over *col_name found */
1367          found = true;
1368       }
1369    }
1370    if (found) {
1371       if (verbose) {
1372          printf(_("Ok. Index over the %s column already exists and dbcheck will work faster.\n"), col_name);
1373       }
1374    } else {
1375       printf(_("Note. Index over the %s column not found, that can greatly slow down dbcheck.\n"), col_name);
1376    }
1377    return found;
1378 }
1379
1380 /*
1381  * Create temporary one-column index
1382  */
1383 static bool create_tmp_idx(const char *idx_name, const char *table_name, 
1384                            const char *col_name) 
1385 {
1386    idx_tmp_name = NULL;
1387    printf(_("Create temporary index... This may take some time!\n"));
1388    bsnprintf(buf, sizeof(buf), "CREATE INDEX %s ON %s (%s)", idx_name, table_name, col_name); 
1389    if (verbose) {
1390       printf("%s\n", buf);
1391    }
1392    if (db_sql_query(db, buf, NULL, NULL)) {
1393       idx_tmp_name = idx_name;
1394       if (verbose) {
1395          printf(_("Temporary index created.\n"));
1396       }
1397    } else {
1398       printf("%s\n", db_strerror(db));
1399       return false;
1400    }
1401    return true;
1402 }
1403
1404 /*
1405  * Drop temporary index
1406  */
1407 static bool drop_tmp_idx(const char *idx_name, const char *table_name)
1408 {
1409    if (idx_tmp_name != NULL) {
1410       printf(_("Drop temporary index.\n"));
1411       bsnprintf(buf, sizeof(buf), "DROP INDEX %s ON %s", idx_name, table_name);
1412       if (verbose) {
1413          printf("%s\n", buf);
1414       }
1415       if (!db_sql_query(db, buf, NULL, NULL)) {
1416          printf("%s\n", db_strerror(db));
1417          return false;
1418       } else {
1419          if (verbose) {
1420             printf(_("Temporary index %s deleted.\n"), idx_tmp_name);
1421          }
1422       }
1423    }
1424    idx_tmp_name = NULL;
1425    return true;
1426 }