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