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