]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/tools/dbcheck.c
Fix dbcheck to use the new runtime checks for mysql and not compile time.
[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 /*
46  * Dummy functions
47  */
48 int generate_daemon_event(JCR *jcr, const char *event)
49    { return 1; }
50
51 typedef struct s_id_ctx {
52    int64_t *Id;                       /* ids to be modified */
53    int num_ids;                       /* ids stored */
54    int max_ids;                       /* size of array */
55    int num_del;                       /* number deleted */
56    int tot_ids;                       /* total to process */
57 } ID_LIST;
58
59 typedef struct s_name_ctx {
60    char **name;                       /* list of names */
61    int num_ids;                       /* ids stored */
62    int max_ids;                       /* size of array */
63    int num_del;                       /* number deleted */
64    int tot_ids;                       /* total to process */
65 } NAME_LIST;
66
67 /*
68  * Global variables
69  */
70 static bool fix = false;
71 static bool batch = false;
72 static B_DB *db;
73 static ID_LIST id_list;
74 static NAME_LIST name_list;
75 static char buf[20000];
76 static bool quit = false;
77 static CONFIG *config;
78 static const char *idx_tmp_name;
79
80 #define MAX_ID_LIST_LEN 10000000
81
82 /*
83  * Forward referenced functions
84  */
85 static void print_catalog_details(CAT *catalog, const char *working_dir);
86 static int make_id_list(const char *query, ID_LIST *id_list);
87 static int delete_id_list(const char *query, ID_LIST *id_list);
88 static int make_name_list(const char *query, NAME_LIST *name_list);
89 static void print_name_list(NAME_LIST *name_list);
90 static void free_name_list(NAME_LIST *name_list);
91 static char *get_cmd(const char *prompt);
92 static void eliminate_duplicate_filenames();
93 static void eliminate_duplicate_paths();
94 static void eliminate_orphaned_jobmedia_records();
95 static void eliminate_orphaned_file_records();
96 static void eliminate_orphaned_path_records();
97 static void eliminate_orphaned_filename_records();
98 static void eliminate_orphaned_fileset_records();
99 static void eliminate_orphaned_client_records();
100 static void eliminate_orphaned_job_records();
101 static void eliminate_admin_records();
102 static void eliminate_restore_records();
103 static void repair_bad_paths();
104 static void repair_bad_filenames();
105 static void do_interactive_mode();
106 static bool yes_no(const char *prompt);
107 static bool check_idx(const char *col_name);
108 static bool create_tmp_idx(const char *idx_name, const char *table_name,
109               const char *col_name);
110 static bool drop_tmp_idx(const char *idx_name, const char *table_name);
111 static int check_idx_handler(void *ctx, int num_fields, char **row);
112
113 static void usage()
114 {
115    fprintf(stderr,
116 "Usage: dbcheck [-c config ] [-B] [-C catalog name] [-d debug_level] <working-directory> <bacula-database> <user> <password> [<dbhost>] [<dbport>]\n"
117 "       -b              batch mode\n"
118 "       -C              catalog name in the director conf file\n"
119 "       -c              Director conf filename\n"
120 "       -B              print catalog configuration and exit\n"
121 "       -d <nn>         set debug level to <nn>\n"
122 "       -dt             print a timestamp in debug output\n"
123 "       -f              fix inconsistencies\n"
124 "       -t              test if client library is thread-safe\n"
125 "       -v              verbose\n"
126 "       -?              print this message\n\n");
127    exit(1);
128 }
129
130 int main (int argc, char *argv[])
131 {
132    int ch;
133    const char *user, *password, *db_name, *dbhost;
134    int dbport = 0;
135    bool print_catalog=false;
136    char *configfile = NULL;
137    char *catalogname = NULL;
138    char *endptr;
139
140    setlocale(LC_ALL, "");
141    bindtextdomain("bacula", LOCALEDIR);
142    textdomain("bacula");
143    lmgr_init_thread();
144
145    my_name_is(argc, argv, "dbcheck");
146    init_msg(NULL, NULL);           /* setup message handler */
147
148    memset(&id_list, 0, sizeof(id_list));
149    memset(&name_list, 0, sizeof(name_list));
150
151    while ((ch = getopt(argc, argv, "bc:C:d:fvB?")) != -1) {
152       switch (ch) {
153       case 'B':
154          print_catalog = true;     /* get catalog information from config */
155          break;
156       case 'b':                    /* batch */
157          batch = true;
158          break;
159       case 'C':                    /* CatalogName */
160           catalogname = optarg;
161          break;
162       case 'c':                    /* configfile */
163           configfile = optarg;
164          break;
165       case 'd':                    /* debug level */
166          if (*optarg == 't') {
167             dbg_timestamp = true;
168          } else {
169             debug_level = atoi(optarg);
170             if (debug_level <= 0) {
171                debug_level = 1;
172             }
173          }
174          break;
175       case 'f':                    /* fix inconsistencies */
176          fix = true;
177          break;
178       case 'v':
179          verbose++;
180          break;
181       case '?':
182       default:
183          usage();
184       }
185    }
186    argc -= optind;
187    argv += optind;
188
189    OSDependentInit();
190
191    if (configfile) {
192       CAT *catalog = NULL;
193       int found = 0;
194       if (argc > 0) {
195          Pmsg0(0, _("Warning skipping the additional parameters for working directory/dbname/user/password/host.\n"));
196       }
197       config = new_config_parser();
198       parse_dir_config(config, configfile, M_ERROR_TERM);
199       LockRes();
200       foreach_res(catalog, R_CATALOG) {
201          if (catalogname && !strcmp(catalog->hdr.name, catalogname)) {
202             ++found;
203             break;
204          } else if (!catalogname) { // stop on first if no catalogname is given
205            ++found;
206            break;
207          }
208       }
209       UnlockRes();
210       if (!found) {
211          if (catalogname) {
212             Pmsg2(0, _("Error can not find the Catalog name[%s] in the given config file [%s]\n"), catalogname, configfile);
213          } else {
214             Pmsg1(0, _("Error there is no Catalog section in the given config file [%s]\n"), configfile);
215          }
216          exit(1);
217       } else {
218          DIRRES *director;
219          LockRes();
220          director = (DIRRES *)GetNextRes(R_DIRECTOR, NULL);
221          UnlockRes();
222          if (!director) {
223             Pmsg0(0, _("Error no Director resource defined.\n"));
224             exit(1);
225          }
226          set_working_directory(director->working_directory);
227
228          /*
229           * Print catalog information and exit (-B)
230           */
231          if (print_catalog) {
232             print_catalog_details(catalog, director->working_directory);
233             exit(0);
234          }
235
236          db_name = catalog->db_name;
237          user = catalog->db_user;
238          password = catalog->db_password;
239          dbhost = catalog->db_address;
240          if (dbhost && dbhost[0] == 0) {
241             dbhost = NULL;
242          }
243          dbport = catalog->db_port;
244       }
245    } else {
246       if (argc > 6) {
247          Pmsg0(0, _("Wrong number of arguments.\n"));
248          usage();
249       }
250
251       if (argc < 1) {
252          Pmsg0(0, _("Working directory not supplied.\n"));
253          usage();
254       }
255
256       /*
257        * This is needed by SQLite to find the db
258        */
259       working_directory = argv[0];
260       db_name = "bacula";
261       user = db_name;
262       password = "";
263       dbhost = NULL;
264
265       if (argc == 2) {
266          db_name = argv[1];
267          user = db_name;
268       } else if (argc == 3) {
269          db_name = argv[1];
270          user = argv[2];
271       } else if (argc == 4) {
272          db_name = argv[1];
273          user = argv[2];
274          password = argv[3];
275       } else if (argc == 5) {
276          db_name = argv[1];
277          user = argv[2];
278          password = argv[3];
279          dbhost = argv[4];
280       } else if (argc == 6) {
281          db_name = argv[1];
282          user = argv[2];
283          password = argv[3];
284          dbhost = argv[4];
285          errno = 0;
286          dbport = strtol(argv[5], &endptr, 10);
287          if (*endptr != '\0') {
288             Pmsg0(0, _("Database port must be a numeric value.\n"));
289             exit(1);
290          } else if (errno == ERANGE) {
291             Pmsg0(0, _("Database port must be a int value.\n"));
292             exit(1);
293          }
294       }
295    }
296
297    /*
298     * Open database
299     */
300    db = db_init_database(NULL, NULL, db_name, user, password, dbhost, dbport, NULL, false, false);
301    if (!db_open_database(NULL, db)) {
302       Emsg1(M_FATAL, 0, "%s", db_strerror(db));
303           return 1;
304    }
305
306    /*
307     * Drop temporary index idx_tmp_name if it already exists
308     */
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    /*
330     * Drop temporary index idx_tmp_name
331     */
332    drop_tmp_idx("idxPIchk", "File");
333
334    db_close_database(NULL, db);
335    close_msg(NULL);
336    term_msg();
337    lmgr_cleanup_main();
338    return 0;
339 }
340
341 static void print_catalog_details(CAT *catalog, const char *working_dir)
342 {
343    POOLMEM *buf = get_pool_memory(PM_MESSAGE);
344
345    /*
346     * Instantiate a B_DB class and see what db_type gets assigned to it.
347     */
348    db = db_init_database(NULL, catalog->db_driver, catalog->db_name, catalog->db_user,
349                          catalog->db_password, catalog->db_address,
350                          catalog->db_port, catalog->db_socket,
351                          catalog->mult_db_connections,
352                          catalog->disable_batch_insert);
353    if (db) {
354       printf("%sdb_type=%s\nworking_dir=%s\n", catalog->display(buf),
355              db->db_get_type(), working_directory);
356       db_close_database(NULL, db);
357    }
358    free_pool_memory(buf);
359 }
360
361 static void do_interactive_mode()
362 {
363    const char *cmd;
364
365    printf(_("Hello, this is the database check/correct program.\n"));
366    if (fix)
367       printf(_("Modify database is on."));
368    else
369       printf(_("Modify database is off."));
370    if (verbose)
371       printf(_(" Verbose is on.\n"));
372    else
373       printf(_(" Verbose is off.\n"));
374
375    printf(_("Please select the function you want to perform.\n"));
376
377    while (!quit) {
378       if (fix) {
379          printf(_("\n"
380 "     1) Toggle modify database flag\n"
381 "     2) Toggle verbose flag\n"
382 "     3) Repair bad Filename records\n"
383 "     4) Repair bad Path records\n"
384 "     5) Eliminate duplicate Filename records\n"
385 "     6) Eliminate duplicate Path records\n"
386 "     7) Eliminate orphaned Jobmedia records\n"
387 "     8) Eliminate orphaned File records\n"
388 "     9) Eliminate orphaned Path records\n"
389 "    10) Eliminate orphaned Filename records\n"
390 "    11) Eliminate orphaned FileSet records\n"
391 "    12) Eliminate orphaned Client records\n"
392 "    13) Eliminate orphaned Job records\n"
393 "    14) Eliminate all Admin records\n"
394 "    15) Eliminate all Restore records\n"
395 "    16) All (3-15)\n"
396 "    17) Quit\n"));
397        } else {
398          printf(_("\n"
399 "     1) Toggle modify database flag\n"
400 "     2) Toggle verbose flag\n"
401 "     3) Check for bad Filename records\n"
402 "     4) Check for bad Path records\n"
403 "     5) Check for duplicate Filename records\n"
404 "     6) Check for duplicate Path records\n"
405 "     7) Check for orphaned Jobmedia records\n"
406 "     8) Check for orphaned File records\n"
407 "     9) Check for orphaned Path records\n"
408 "    10) Check for orphaned Filename records\n"
409 "    11) Check for orphaned FileSet records\n"
410 "    12) Check for orphaned Client records\n"
411 "    13) Check for orphaned Job records\n"
412 "    14) Check for all Admin records\n"
413 "    15) Check for all Restore records\n"
414 "    16) All (3-15)\n"
415 "    17) Quit\n"));
416        }
417
418       cmd = get_cmd(_("Select function number: "));
419       if (cmd) {
420          int item = atoi(cmd);
421          switch (item) {
422          case 1:
423             fix = !fix;
424             if (fix)
425                printf(_("Database will be modified.\n"));
426             else
427                printf(_("Database will NOT be modified.\n"));
428             break;
429          case 2:
430             verbose = verbose?0:1;
431             if (verbose)
432                printf(_(" Verbose is on.\n"));
433             else
434                printf(_(" Verbose is off.\n"));
435             break;
436          case 3:
437             repair_bad_filenames();
438             break;
439          case 4:
440             repair_bad_paths();
441             break;
442          case 5:
443             eliminate_duplicate_filenames();
444             break;
445          case 6:
446             eliminate_duplicate_paths();
447             break;
448          case 7:
449             eliminate_orphaned_jobmedia_records();
450             break;
451          case 8:
452             eliminate_orphaned_file_records();
453             break;
454          case 9:
455             eliminate_orphaned_path_records();
456             break;
457          case 10:
458             eliminate_orphaned_filename_records();
459             break;
460          case 11:
461             eliminate_orphaned_fileset_records();
462             break;
463          case 12:
464             eliminate_orphaned_client_records();
465             break;
466          case 13:
467             eliminate_orphaned_job_records();
468             break;
469          case 14:
470             eliminate_admin_records();
471             break;
472          case 15:
473             eliminate_restore_records();
474             break;
475          case 16:
476             repair_bad_filenames();
477             repair_bad_paths();
478             eliminate_duplicate_filenames();
479             eliminate_duplicate_paths();
480             eliminate_orphaned_jobmedia_records();
481             eliminate_orphaned_file_records();
482             eliminate_orphaned_path_records();
483             eliminate_orphaned_filename_records();
484             eliminate_orphaned_fileset_records();
485             eliminate_orphaned_client_records();
486             eliminate_orphaned_job_records();
487             eliminate_admin_records();
488             eliminate_restore_records();
489             break;
490          case 17:
491             quit = true;
492             break;
493          }
494       }
495    }
496 }
497
498 static int print_name_handler(void *ctx, int num_fields, char **row)
499 {
500    if (row[0]) {
501       printf("%s\n", row[0]);
502    }
503    return 0;
504 }
505
506 static int get_name_handler(void *ctx, int num_fields, char **row)
507 {
508    POOLMEM *buf = (POOLMEM *)ctx;
509    if (row[0]) {
510       pm_strcpy(&buf, row[0]);
511    }
512    return 0;
513 }
514
515 static int print_job_handler(void *ctx, int num_fields, char **row)
516 {
517    printf(_("JobId=%s Name=\"%s\" StartTime=%s\n"),
518               NPRT(row[0]), NPRT(row[1]), NPRT(row[2]));
519    return 0;
520 }
521
522 static int print_jobmedia_handler(void *ctx, int num_fields, char **row)
523 {
524    printf(_("Orphaned JobMediaId=%s JobId=%s Volume=\"%s\"\n"),
525               NPRT(row[0]), NPRT(row[1]), NPRT(row[2]));
526    return 0;
527 }
528
529 static int print_file_handler(void *ctx, int num_fields, char **row)
530 {
531    printf(_("Orphaned FileId=%s JobId=%s Volume=\"%s\"\n"),
532               NPRT(row[0]), NPRT(row[1]), NPRT(row[2]));
533    return 0;
534 }
535
536 static int print_fileset_handler(void *ctx, int num_fields, char **row)
537 {
538    printf(_("Orphaned FileSetId=%s FileSet=\"%s\" MD5=%s\n"),
539               NPRT(row[0]), NPRT(row[1]), NPRT(row[2]));
540    return 0;
541 }
542
543 static int print_client_handler(void *ctx, int num_fields, char **row)
544 {
545    printf(_("Orphaned ClientId=%s Name=\"%s\"\n"),
546               NPRT(row[0]), NPRT(row[1]));
547    return 0;
548 }
549
550 /*
551  * Called here with each id to be added to the list
552  */
553 static int id_list_handler(void *ctx, int num_fields, char **row)
554 {
555    ID_LIST *lst = (ID_LIST *)ctx;
556
557    if (lst->num_ids == MAX_ID_LIST_LEN) {
558       return 1;
559    }
560    if (lst->num_ids == lst->max_ids) {
561       if (lst->max_ids == 0) {
562          lst->max_ids = 10000;
563          lst->Id = (int64_t *)bmalloc(sizeof(int64_t) * lst->max_ids);
564       } else {
565          lst->max_ids = (lst->max_ids * 3) / 2;
566          lst->Id = (int64_t *)brealloc(lst->Id, sizeof(int64_t) * lst->max_ids);
567       }
568    }
569    lst->Id[lst->num_ids++] = str_to_int64(row[0]);
570    return 0;
571 }
572
573 /*
574  * Construct record id list
575  */
576 static int make_id_list(const char *query, ID_LIST *id_list)
577 {
578    id_list->num_ids = 0;
579    id_list->num_del = 0;
580    id_list->tot_ids = 0;
581
582    if (!db_sql_query(db, query, id_list_handler, (void *)id_list)) {
583       printf("%s", db_strerror(db));
584       return 0;
585    }
586    return 1;
587 }
588
589 /*
590  * Delete all entries in the list
591  */
592 static int delete_id_list(const char *query, ID_LIST *id_list)
593 {
594    char ed1[50];
595    for (int i=0; i < id_list->num_ids; i++) {
596       bsnprintf(buf, sizeof(buf), query, edit_int64(id_list->Id[i], ed1));
597       if (verbose) {
598          printf(_("Deleting: %s\n"), buf);
599       }
600       db_sql_query(db, buf, NULL, NULL);
601    }
602    return 1;
603 }
604
605 /*
606  * Called here with each name to be added to the list
607  */
608 static int name_list_handler(void *ctx, int num_fields, char **row)
609 {
610    NAME_LIST *name = (NAME_LIST *)ctx;
611
612    if (name->num_ids == MAX_ID_LIST_LEN) {
613       return 1;
614    }
615    if (name->num_ids == name->max_ids) {
616       if (name->max_ids == 0) {
617          name->max_ids = 10000;
618          name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
619       } else {
620          name->max_ids = (name->max_ids * 3) / 2;
621          name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
622       }
623    }
624    name->name[name->num_ids++] = bstrdup(row[0]);
625    return 0;
626 }
627
628 /*
629  * Construct name list
630  */
631 static int make_name_list(const char *query, NAME_LIST *name_list)
632 {
633    name_list->num_ids = 0;
634    name_list->num_del = 0;
635    name_list->tot_ids = 0;
636
637    if (!db_sql_query(db, query, name_list_handler, (void *)name_list)) {
638       printf("%s", db_strerror(db));
639       return 0;
640    }
641    return 1;
642 }
643
644 /*
645  * Print names in the list
646  */
647 static void print_name_list(NAME_LIST *name_list)
648 {
649    for (int i=0; i < name_list->num_ids; i++) {
650       printf("%s\n", name_list->name[i]);
651    }
652 }
653
654 /*
655  * Free names in the list
656  */
657 static void free_name_list(NAME_LIST *name_list)
658 {
659    for (int i=0; i < name_list->num_ids; i++) {
660       free(name_list->name[i]);
661    }
662    name_list->num_ids = 0;
663 }
664
665 static void eliminate_duplicate_filenames()
666 {
667    const char *query;
668    char esc_name[5000];
669
670    printf(_("Checking for duplicate Filename entries.\n"));
671
672    /*
673     * Make list of duplicated names
674     */
675    query = "SELECT Name, count(Name) as Count FROM Filename GROUP BY  Name "
676            "HAVING count(Name) > 1";
677
678    if (!make_name_list(query, &name_list)) {
679       exit(1);
680    }
681    printf(_("Found %d duplicate Filename records.\n"), name_list.num_ids);
682    if (name_list.num_ids && verbose && yes_no(_("Print the list? (yes/no): "))) {
683       print_name_list(&name_list);
684    }
685    if (quit) {
686       return;
687    }
688    if (fix) {
689       /*
690        * Loop through list of duplicate names
691        */
692       for (int i=0; i<name_list.num_ids; i++) {
693          /*
694           * Get all the Ids of each name
695           */
696          db_escape_string(NULL, db, esc_name, name_list.name[i], strlen(name_list.name[i]));
697          bsnprintf(buf, sizeof(buf), "SELECT FilenameId FROM Filename WHERE Name='%s'", esc_name);
698          if (verbose > 1) {
699             printf("%s\n", buf);
700          }
701          if (!make_id_list(buf, &id_list)) {
702             exit(1);
703          }
704          if (verbose) {
705             printf(_("Found %d for: %s\n"), id_list.num_ids, name_list.name[i]);
706          }
707          /*
708           * Force all records to use the first id then delete the other ids
709           */
710          for (int j=1; j<id_list.num_ids; j++) {
711             char ed1[50], ed2[50];
712             bsnprintf(buf, sizeof(buf), "UPDATE File SET FilenameId=%s WHERE FilenameId=%s",
713                edit_int64(id_list.Id[0], ed1), edit_int64(id_list.Id[j], ed2));
714             if (verbose > 1) {
715                printf("%s\n", buf);
716             }
717             db_sql_query(db, buf, NULL, NULL);
718             bsnprintf(buf, sizeof(buf), "DELETE FROM Filename WHERE FilenameId=%s",
719                ed2);
720             if (verbose > 2) {
721                printf("%s\n", buf);
722             }
723             db_sql_query(db, buf, NULL, NULL);
724          }
725       }
726    }
727    free_name_list(&name_list);
728 }
729
730 static void eliminate_duplicate_paths()
731 {
732    const char *query;
733    char esc_name[5000];
734
735    printf(_("Checking for duplicate Path entries.\n"));
736
737    /*
738     * Make list of duplicated names
739     */
740    query = "SELECT Path, count(Path) as Count FROM Path "
741            "GROUP BY Path HAVING count(Path) > 1";
742
743    if (!make_name_list(query, &name_list)) {
744       exit(1);
745    }
746    printf(_("Found %d duplicate Path records.\n"), name_list.num_ids);
747    if (name_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
748       print_name_list(&name_list);
749    }
750    if (quit) {
751       return;
752    }
753    if (fix) {
754       /*
755        * Loop through list of duplicate names
756        */
757       for (int i=0; i<name_list.num_ids; i++) {
758          /*
759           * Get all the Ids of each name
760           */
761          db_escape_string(NULL, db, esc_name, name_list.name[i], strlen(name_list.name[i]));
762          bsnprintf(buf, sizeof(buf), "SELECT PathId FROM Path WHERE Path='%s'", esc_name);
763          if (verbose > 1) {
764             printf("%s\n", buf);
765          }
766          if (!make_id_list(buf, &id_list)) {
767             exit(1);
768          }
769          if (verbose) {
770             printf(_("Found %d for: %s\n"), id_list.num_ids, name_list.name[i]);
771          }
772          /*
773           * Force all records to use the first id then delete the other ids
774           */
775          for (int j=1; j<id_list.num_ids; j++) {
776             char ed1[50], ed2[50];
777             bsnprintf(buf, sizeof(buf), "UPDATE File SET PathId=%s WHERE PathId=%s",
778                edit_int64(id_list.Id[0], ed1), edit_int64(id_list.Id[j], ed2));
779             if (verbose > 1) {
780                printf("%s\n", buf);
781             }
782             db_sql_query(db, buf, NULL, NULL);
783             bsnprintf(buf, sizeof(buf), "DELETE FROM Path WHERE PathId=%s", ed2);
784             if (verbose > 2) {
785                printf("%s\n", buf);
786             }
787             db_sql_query(db, buf, NULL, NULL);
788          }
789       }
790    }
791    free_name_list(&name_list);
792 }
793
794 static void eliminate_orphaned_jobmedia_records()
795 {
796    const char *query = "SELECT JobMedia.JobMediaId,Job.JobId FROM JobMedia "
797                 "LEFT OUTER JOIN Job ON (JobMedia.JobId=Job.JobId) "
798                 "WHERE Job.JobId IS NULL LIMIT 300000";
799
800    printf(_("Checking for orphaned JobMedia entries.\n"));
801    if (!make_id_list(query, &id_list)) {
802       exit(1);
803    }
804    /*
805     * Loop doing 300000 at a time
806     */
807    while (id_list.num_ids != 0) {
808       printf(_("Found %d orphaned JobMedia records.\n"), id_list.num_ids);
809       if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
810          for (int i=0; i < id_list.num_ids; i++) {
811             char ed1[50];
812             bsnprintf(buf, sizeof(buf),
813 "SELECT JobMedia.JobMediaId,JobMedia.JobId,Media.VolumeName FROM JobMedia,Media "
814 "WHERE JobMedia.JobMediaId=%s AND Media.MediaId=JobMedia.MediaId",
815                edit_int64(id_list.Id[i], ed1));
816             if (!db_sql_query(db, buf, print_jobmedia_handler, NULL)) {
817                printf("%s\n", db_strerror(db));
818             }
819          }
820       }
821       if (quit) {
822          return;
823       }
824
825       if (fix && id_list.num_ids > 0) {
826          printf(_("Deleting %d orphaned JobMedia records.\n"), id_list.num_ids);
827          delete_id_list("DELETE FROM JobMedia WHERE JobMediaId=%s", &id_list);
828       } else {
829          break;                       /* get out if not updating db */
830       }
831       if (!make_id_list(query, &id_list)) {
832          exit(1);
833       }
834    }
835 }
836
837 static void eliminate_orphaned_file_records()
838 {
839    const char *query = "SELECT File.FileId,Job.JobId FROM File "
840                 "LEFT OUTER JOIN Job ON (File.JobId=Job.JobId) "
841                "WHERE Job.JobId IS NULL LIMIT 300000";
842
843    printf(_("Checking for orphaned File entries. This may take some time!\n"));
844    if (verbose > 1) {
845       printf("%s\n", query);
846    }
847    if (!make_id_list(query, &id_list)) {
848       exit(1);
849    }
850    /*
851     * Loop doing 300000 at a time
852     */
853    while (id_list.num_ids != 0) {
854       printf(_("Found %d orphaned File records.\n"), id_list.num_ids);
855       if (name_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
856          for (int i=0; i < id_list.num_ids; i++) {
857             char ed1[50];
858             bsnprintf(buf, sizeof(buf),
859 "SELECT File.FileId,File.JobId,Filename.Name FROM File,Filename "
860 "WHERE File.FileId=%s AND File.FilenameId=Filename.FilenameId",
861                edit_int64(id_list.Id[i], ed1));
862             if (!db_sql_query(db, buf, print_file_handler, NULL)) {
863                printf("%s\n", db_strerror(db));
864             }
865          }
866       }
867       if (quit) {
868          return;
869       }
870       if (fix && id_list.num_ids > 0) {
871          printf(_("Deleting %d orphaned File records.\n"), id_list.num_ids);
872          delete_id_list("DELETE FROM File WHERE FileId=%s", &id_list);
873       } else {
874          break;                       /* get out if not updating db */
875       }
876       if (!make_id_list(query, &id_list)) {
877          exit(1);
878       }
879    }
880 }
881
882 static void eliminate_orphaned_path_records()
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    }
1251 }
1252
1253 static void repair_bad_paths()
1254 {
1255    const char *query;
1256    int i;
1257
1258    printf(_("Checking for Paths without a trailing slash\n"));
1259    query = "SELECT PathId,Path from Path "
1260            "WHERE Path NOT LIKE '%/'";
1261    if (verbose > 1) {
1262       printf("%s\n", query);
1263    }
1264    if (!make_id_list(query, &id_list)) {
1265       exit(1);
1266    }
1267    printf(_("Found %d bad Path records.\n"), id_list.num_ids);
1268    if (id_list.num_ids && verbose && yes_no(_("Print them? (yes/no): "))) {
1269       for (i=0; i < id_list.num_ids; i++) {
1270          char ed1[50];
1271          bsnprintf(buf, sizeof(buf),
1272             "SELECT Path FROM Path WHERE PathId=%s", edit_int64(id_list.Id[i], ed1));
1273          if (!db_sql_query(db, buf, print_name_handler, NULL)) {
1274             printf("%s\n", db_strerror(db));
1275          }
1276       }
1277    }
1278    if (quit) {
1279       return;
1280    }
1281    if (fix && id_list.num_ids > 0) {
1282       POOLMEM *name = get_pool_memory(PM_FNAME);
1283       char esc_name[5000];
1284       printf(_("Reparing %d bad Filename records.\n"), id_list.num_ids);
1285       for (i=0; i < id_list.num_ids; i++) {
1286          int len;
1287          char ed1[50];
1288          bsnprintf(buf, sizeof(buf),
1289             "SELECT Path FROM Path WHERE PathId=%s", edit_int64(id_list.Id[i], ed1));
1290          if (!db_sql_query(db, buf, get_name_handler, name)) {
1291             printf("%s\n", db_strerror(db));
1292          }
1293          /*
1294           * Strip trailing blanks
1295           */
1296          for (len=strlen(name); len > 0 && name[len-1]==' '; len--) {
1297             name[len-1] = 0;
1298          }
1299          /*
1300           * Add trailing slash
1301           */
1302          len = pm_strcat(&name, "/");
1303          db_escape_string(NULL, db, esc_name, name, len);
1304          bsnprintf(buf, sizeof(buf), "UPDATE Path SET Path='%s' WHERE PathId=%s",
1305             esc_name, edit_int64(id_list.Id[i], ed1));
1306          if (verbose > 1) {
1307             printf("%s\n", buf);
1308          }
1309          db_sql_query(db, buf, NULL, NULL);
1310       }
1311    }
1312 }
1313
1314 /*
1315  * Gen next input command from the terminal
1316  */
1317 static char *get_cmd(const char *prompt)
1318 {
1319    static char cmd[1000];
1320
1321    printf("%s", prompt);
1322    if (fgets(cmd, sizeof(cmd), stdin) == NULL) {
1323       printf("\n");
1324       quit = true;
1325       return NULL;
1326    }
1327    strip_trailing_junk(cmd);
1328    return cmd;
1329 }
1330
1331 static bool yes_no(const char *prompt)
1332 {
1333    char *cmd;
1334    cmd = get_cmd(prompt);
1335    if (!cmd) {
1336       quit = true;
1337       return false;
1338    }
1339    return (strcasecmp(cmd, "yes") == 0) || (strcasecmp(cmd, _("yes")) == 0);
1340 }
1341
1342 bool python_set_prog(JCR*, char const*) { return false; }
1343
1344 /*
1345  * The code below to add indexes is needed only for MySQL, and
1346  *  that to improve the performance.
1347  */
1348
1349 #define MAXIDX          100
1350 typedef struct s_idx_list {
1351    char *key_name;
1352    int  count_key; /* how many times the index meets *key_name */
1353    int  count_col; /* how many times meets the desired column name */
1354 } IDX_LIST;
1355
1356 static IDX_LIST idx_list[MAXIDX];
1357
1358 /*
1359  * Called here with each table index to be added to the list
1360  */
1361 static int check_idx_handler(void *ctx, int num_fields, char **row)
1362 {
1363    /*
1364     * Table | Non_unique | Key_name | Seq_in_index | Column_name |...
1365     * File  |          0 | PRIMARY  |            1 | FileId      |...
1366     */
1367    char *name, *key_name, *col_name;
1368    int i, len;
1369    int found = false;
1370
1371    name = (char *)ctx;
1372    key_name = row[2];
1373    col_name = row[4];
1374    for(i = 0; (idx_list[i].key_name != NULL) && (i < MAXIDX); i++) {
1375       if (strcasecmp(idx_list[i].key_name, key_name) == 0 ) {
1376          idx_list[i].count_key++;
1377          found = true;
1378          if (strcasecmp(col_name, name) == 0) {
1379             idx_list[i].count_col++;
1380          }
1381          break;
1382       }
1383    }
1384    /*
1385     * If the new Key_name, add it to the list
1386     */
1387    if (!found) {
1388       len = strlen(key_name) + 1;
1389       idx_list[i].key_name = (char *)malloc(len);
1390       bstrncpy(idx_list[i].key_name, key_name, len);
1391       idx_list[i].count_key = 1;
1392       if (strcasecmp(col_name, name) == 0) {
1393          idx_list[i].count_col = 1;
1394       } else {
1395          idx_list[i].count_col = 0;
1396       }
1397    }
1398    return 0;
1399 }
1400
1401 /*
1402  * Return TRUE if "one column" index over *col_name exists
1403  */
1404 static bool check_idx(const char *col_name)
1405 {
1406    int i;
1407    int found = false;
1408    const char *query = "SHOW INDEX FROM File";
1409
1410    switch (db_get_type_index(db)) {
1411    case SQL_TYPE_MYSQL:
1412       memset(&idx_list, 0, sizeof(idx_list));
1413       if (!db_sql_query(db, query, check_idx_handler, (void *)col_name)) {
1414          printf("%s\n", db_strerror(db));
1415       }
1416       for (i = 0; (idx_list[i].key_name != NULL) && (i < MAXIDX) ; i++) {
1417          /*
1418           * NOTE : if (idx_list[i].count_key > 1) then index idx_list[i].key_name is "multiple-column" index
1419           */
1420          if ((idx_list[i].count_key == 1) && (idx_list[i].count_col == 1)) {
1421             /*
1422              * "one column" index over *col_name found
1423              */
1424             found = true;
1425          }
1426       }
1427       if (found) {
1428          if (verbose) {
1429             printf(_("Ok. Index over the %s column already exists and dbcheck will work faster.\n"), col_name);
1430          }
1431       } else {
1432          printf(_("Note. Index over the %s column not found, that can greatly slow down dbcheck.\n"), col_name);
1433       }
1434       return found;
1435    default:
1436       return true;
1437    }
1438 }
1439
1440 /*
1441  * Create temporary one-column index
1442  */
1443 static bool create_tmp_idx(const char *idx_name, const char *table_name,
1444                            const char *col_name)
1445 {
1446    idx_tmp_name = NULL;
1447    printf(_("Create temporary index... This may take some time!\n"));
1448    bsnprintf(buf, sizeof(buf), "CREATE INDEX %s ON %s (%s)", idx_name, table_name, col_name);
1449    if (verbose) {
1450       printf("%s\n", buf);
1451    }
1452    if (db_sql_query(db, buf, NULL, NULL)) {
1453       idx_tmp_name = idx_name;
1454       if (verbose) {
1455          printf(_("Temporary index created.\n"));
1456       }
1457    } else {
1458       printf("%s\n", db_strerror(db));
1459       return false;
1460    }
1461    return true;
1462 }
1463
1464 /*
1465  * Drop temporary index
1466  */
1467 static bool drop_tmp_idx(const char *idx_name, const char *table_name)
1468 {
1469    if (idx_tmp_name != NULL) {
1470       printf(_("Drop temporary index.\n"));
1471       bsnprintf(buf, sizeof(buf), "DROP INDEX %s ON %s", idx_name, table_name);
1472       if (verbose) {
1473          printf("%s\n", buf);
1474       }
1475       if (!db_sql_query(db, buf, NULL, NULL)) {
1476          printf("%s\n", db_strerror(db));
1477          return false;
1478       } else {
1479          if (verbose) {
1480             printf(_("Temporary index %s deleted.\n"), idx_tmp_name);
1481          }
1482       }
1483    }
1484    idx_tmp_name = NULL;
1485    return true;
1486 }