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