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