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