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