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