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