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