1 /*
2    Copyright (c) 2001, 2017, Oracle and/or its affiliates. All rights reserved.
3 
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License, version 2.0,
6    as published by the Free Software Foundation.
7 
8    This program is also distributed with certain software (including
9    but not limited to OpenSSL) that is licensed under separate terms,
10    as designated in a particular file or component or in included license
11    documentation.  The authors of MySQL hereby grant you an additional
12    permission to link the program and your derivative works with the
13    separately licensed software that they have included with MySQL.
14 
15    This program is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18    GNU General Public License, version 2.0, for more details.
19 
20    You should have received a copy of the GNU General Public License
21    along with this program; if not, write to the Free Software
22    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
23 */
24 
25 #define CHECK_VERSION "2.5.1"
26 
27 #include "client_priv.h"
28 #include "my_default.h"
29 #include <m_ctype.h>
30 #include <mysql_version.h>
31 #include <mysqld_error.h>
32 #include <sslopt-vars.h>
33 #include <welcome_copyright_notice.h> /* ORACLE_WELCOME_COPYRIGHT_NOTICE */
34 
35 /* Exit codes */
36 
37 #define EX_USAGE 1
38 #define EX_MYSQLERR 2
39 
40 /* ALTER instead of repair. */
41 #define MAX_ALTER_STR_SIZE 128 * 1024
42 #define KEY_PARTITIONING_CHANGED_STR "KEY () partitioning changed"
43 
44 static MYSQL mysql_connection, *sock = 0;
45 static my_bool opt_alldbs = 0, opt_check_only_changed = 0, opt_extended = 0,
46                opt_compress = 0, opt_databases = 0, opt_fast = 0,
47                opt_medium_check = 0, opt_quick = 0, opt_all_in_1 = 0,
48                opt_silent = 0, opt_auto_repair = 0, ignore_errors = 0,
49                tty_password= 0, opt_frm= 0, debug_info_flag= 0, debug_check_flag= 0,
50                opt_fix_table_names= 0, opt_fix_db_names= 0, opt_upgrade= 0,
51                opt_write_binlog= 1, opt_secure_auth=TRUE;
52 static uint verbose = 0, opt_mysql_port=0;
53 static uint opt_enable_cleartext_plugin= 0;
54 static my_bool using_opt_enable_cleartext_plugin= 0;
55 static int my_end_arg;
56 static char * opt_mysql_unix_port = 0;
57 static char *opt_password = 0, *current_user = 0,
58 	    *default_charset= 0, *current_host= 0;
59 static char *opt_plugin_dir= 0, *opt_default_auth= 0;
60 static int first_error = 0;
61 static char *opt_skip_database;
62 DYNAMIC_ARRAY tables4repair, tables4rebuild, alter_table_cmds;
63 #ifdef HAVE_SMEM
64 static char *shared_memory_base_name=0;
65 #endif
66 static uint opt_protocol=0;
67 static char *opt_bind_addr = NULL;
68 
69 enum operations { DO_CHECK=1, DO_REPAIR, DO_ANALYZE, DO_OPTIMIZE, DO_UPGRADE };
70 
71 static struct my_option my_long_options[] =
72 {
73   {"all-databases", 'A',
74    "Check all the databases. This is the same as --databases with all databases selected.",
75    &opt_alldbs, &opt_alldbs, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0,
76    0, 0},
77   {"analyze", 'a', "Analyze given tables.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0,
78    0, 0, 0, 0},
79   {"all-in-1", '1',
80    "Instead of issuing one query for each table, use one query per database, naming all tables in the database in a comma-separated list.",
81    &opt_all_in_1, &opt_all_in_1, 0, GET_BOOL, NO_ARG, 0, 0, 0,
82    0, 0, 0},
83   {"auto-repair", OPT_AUTO_REPAIR,
84    "If a checked table is corrupted, automatically fix it. Repairing will be done after all tables have been checked, if corrupted ones were found.",
85    &opt_auto_repair, &opt_auto_repair, 0, GET_BOOL, NO_ARG, 0,
86    0, 0, 0, 0, 0},
87   {"bind-address", 0, "IP address to bind to.",
88    (uchar**) &opt_bind_addr, (uchar**) &opt_bind_addr, 0, GET_STR,
89    REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
90   {"character-sets-dir", OPT_CHARSETS_DIR,
91    "Directory for character set files.", &charsets_dir,
92    &charsets_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
93   {"check", 'c', "Check table for errors.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0,
94    0, 0, 0, 0},
95   {"check-only-changed", 'C',
96    "Check only tables that have changed since last check or haven't been closed properly.",
97    0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
98   {"check-upgrade", 'g',
99    "Check tables for version-dependent changes. May be used with --auto-repair to correct tables requiring version-dependent updates.",
100    0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
101   {"compress", OPT_COMPRESS, "Use compression in server/client protocol.",
102    &opt_compress, &opt_compress, 0, GET_BOOL, NO_ARG, 0, 0, 0,
103    0, 0, 0},
104   {"databases", 'B',
105    "Check several databases. Note the difference in usage; in this case no tables are given. All name arguments are regarded as database names.",
106    &opt_databases, &opt_databases, 0, GET_BOOL, NO_ARG,
107    0, 0, 0, 0, 0, 0},
108 #ifdef DBUG_OFF
109   {"debug", '#', "This is a non-debug version. Catch this and exit.",
110    0, 0, 0, GET_DISABLED, OPT_ARG, 0, 0, 0, 0, 0, 0},
111 #else
112   {"debug", '#', "Output debug log. Often this is 'd:t:o,filename'.",
113    0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0},
114 #endif
115   {"debug-check", OPT_DEBUG_CHECK, "Check memory and open file usage at exit.",
116    &debug_check_flag, &debug_check_flag, 0,
117    GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
118   {"debug-info", OPT_DEBUG_INFO, "Print some debug info at exit.",
119    &debug_info_flag, &debug_info_flag,
120    0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
121   {"default-character-set", OPT_DEFAULT_CHARSET,
122    "Set the default character set.", &default_charset,
123    &default_charset, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
124   {"default_auth", OPT_DEFAULT_AUTH,
125    "Default authentication client-side plugin to use.",
126    &opt_default_auth, &opt_default_auth, 0,
127    GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
128   {"enable_cleartext_plugin", OPT_ENABLE_CLEARTEXT_PLUGIN,
129    "Enable/disable the clear text authentication plugin.",
130    &opt_enable_cleartext_plugin, &opt_enable_cleartext_plugin,
131    0, GET_BOOL, OPT_ARG, 0, 0, 0, 0, 0, 0},
132   {"fast",'F', "Check only tables that haven't been closed properly.",
133    &opt_fast, &opt_fast, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0,
134    0},
135   {"fix-db-names", OPT_FIX_DB_NAMES, "Fix database names.",
136     &opt_fix_db_names, &opt_fix_db_names,
137     0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
138   {"fix-table-names", OPT_FIX_TABLE_NAMES, "Fix table names.",
139     &opt_fix_table_names, &opt_fix_table_names,
140     0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
141   {"force", 'f', "Continue even if we get an SQL error.",
142    &ignore_errors, &ignore_errors, 0, GET_BOOL, NO_ARG, 0, 0,
143    0, 0, 0, 0},
144   {"extended", 'e',
145    "If you are using this option with CHECK TABLE, it will ensure that the table is 100 percent consistent, but will take a long time. If you are using this option with REPAIR TABLE, it will force using old slow repair with keycache method, instead of much faster repair by sorting.",
146    &opt_extended, &opt_extended, 0, GET_BOOL, NO_ARG, 0, 0, 0,
147    0, 0, 0},
148   {"help", '?', "Display this help message and exit.", 0, 0, 0, GET_NO_ARG,
149    NO_ARG, 0, 0, 0, 0, 0, 0},
150   {"host",'h', "Connect to host.", &current_host,
151    &current_host, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
152   {"medium-check", 'm',
153    "Faster than extended-check, but only finds 99.99 percent of all errors. Should be good enough for most cases.",
154    0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
155   {"write-binlog", OPT_WRITE_BINLOG,
156    "Log ANALYZE, OPTIMIZE and REPAIR TABLE commands. Use --skip-write-binlog "
157    "when commands should not be sent to replication slaves.",
158    &opt_write_binlog, &opt_write_binlog, 0, GET_BOOL, NO_ARG,
159    1, 0, 0, 0, 0, 0},
160   {"secure-auth", OPT_SECURE_AUTH, "Refuse client connecting to server if it"
161     " uses old (pre-4.1.1) protocol.", &opt_secure_auth,
162     &opt_secure_auth, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0},
163   {"optimize", 'o', "Optimize table.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0,
164    0, 0},
165   {"password", 'p',
166    "Password to use when connecting to server. If password is not given, it's solicited on the tty.",
167    0, 0, 0, GET_PASSWORD, OPT_ARG, 0, 0, 0, 0, 0, 0},
168 #ifdef __WIN__
169   {"pipe", 'W', "Use named pipes to connect to server.", 0, 0, 0, GET_NO_ARG,
170    NO_ARG, 0, 0, 0, 0, 0, 0},
171 #endif
172   {"plugin_dir", OPT_PLUGIN_DIR, "Directory for client-side plugins.",
173    &opt_plugin_dir, &opt_plugin_dir, 0,
174    GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
175   {"port", 'P', "Port number to use for connection or 0 for default to, in "
176    "order of preference, my.cnf, $MYSQL_TCP_PORT, "
177 #if MYSQL_PORT_DEFAULT == 0
178    "/etc/services, "
179 #endif
180    "built-in default (" STRINGIFY_ARG(MYSQL_PORT) ").",
181    &opt_mysql_port, &opt_mysql_port, 0, GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0,
182    0},
183   {"protocol", OPT_MYSQL_PROTOCOL, "The protocol to use for connection (tcp, socket, pipe, memory).",
184    0, 0, 0, GET_STR,  REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
185   {"quick", 'q',
186    "If you are using this option with CHECK TABLE, it prevents the check from scanning the rows to check for wrong links. This is the fastest check. If you are using this option with REPAIR TABLE, it will try to repair only the index tree. This is the fastest repair method for a table.",
187    &opt_quick, &opt_quick, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0,
188    0},
189   {"repair", 'r',
190    "Can fix almost anything except unique keys that aren't unique.",
191    0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
192 #ifdef HAVE_SMEM
193   {"shared-memory-base-name", OPT_SHARED_MEMORY_BASE_NAME,
194    "Base name of shared memory.", &shared_memory_base_name, &shared_memory_base_name,
195    0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
196 #endif
197   {"silent", 's', "Print only error messages.", &opt_silent,
198    &opt_silent, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
199   {"skip_database", 0, "Don't process the database specified as argument",
200    &opt_skip_database, &opt_skip_database, 0, GET_STR, REQUIRED_ARG,
201    0, 0, 0, 0, 0, 0},
202   {"socket", 'S', "The socket file to use for connection.",
203    &opt_mysql_unix_port, &opt_mysql_unix_port, 0, GET_STR,
204    REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
205 #include <sslopt-longopts.h>
206   {"tables", OPT_TABLES, "Overrides option --databases (-B).", 0, 0, 0,
207    GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
208   {"use-frm", OPT_FRM,
209    "When used with REPAIR, get table structure from .frm file, so the table can be repaired even if .MYI header is corrupted.",
210    &opt_frm, &opt_frm, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0,
211    0},
212 #ifndef DONT_ALLOW_USER_CHANGE
213   {"user", 'u', "User for login if not current user.", &current_user,
214    &current_user, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
215 #endif
216   {"verbose", 'v', "Print info about the various stages.", 0, 0, 0, GET_NO_ARG,
217    NO_ARG, 0, 0, 0, 0, 0, 0},
218   {"version", 'V', "Output version information and exit.", 0, 0, 0, GET_NO_ARG,
219    NO_ARG, 0, 0, 0, 0, 0, 0},
220   {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
221 };
222 
223 static const char *load_default_groups[] = { "mysqlcheck", "client", 0 };
224 
225 
226 static void print_version(void);
227 static void usage(void);
228 static int get_options(int *argc, char ***argv);
229 static int process_all_databases();
230 static int process_databases(char **db_names);
231 static int process_selected_tables(char *db, char **table_names, int tables);
232 static int process_all_tables_in_db(char *database);
233 static int process_one_db(char *database);
234 static int use_db(char *database);
235 static int handle_request_for_tables(char *tables, size_t length);
236 static int dbConnect(char *host, char *user,char *passwd);
237 static void dbDisconnect(char *host);
238 static void DBerror(MYSQL *mysql, const char *when);
239 static void safe_exit(int error);
240 static void print_result();
241 static size_t fixed_name_length(const char *name);
242 static char *fix_table_name(char *dest, char *src);
243 int what_to_do = 0;
244 
245 
print_version(void)246 static void print_version(void)
247 {
248   printf("%s  Ver %s Distrib %s, for %s (%s)\n", my_progname, CHECK_VERSION,
249    MYSQL_SERVER_VERSION, SYSTEM_TYPE, MACHINE_TYPE);
250 } /* print_version */
251 
252 
usage(void)253 static void usage(void)
254 {
255   print_version();
256   puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2000"));
257   puts("This program can be used to CHECK (-c, -m, -C), REPAIR (-r), ANALYZE (-a),");
258   puts("or OPTIMIZE (-o) tables. Some of the options (like -e or -q) can be");
259   puts("used at the same time. Not all options are supported by all storage engines.");
260   puts("Please consult the MySQL manual for latest information about the");
261   puts("above. The options -c, -r, -a, and -o are exclusive to each other, which");
262   puts("means that the last option will be used, if several was specified.\n");
263   puts("The option -c will be used by default, if none was specified. You");
264   puts("can change the default behavior by making a symbolic link, or");
265   puts("copying this file somewhere with another name, the alternatives are:");
266   puts("mysqlrepair:   The default option will be -r");
267   puts("mysqlanalyze:  The default option will be -a");
268   puts("mysqloptimize: The default option will be -o\n");
269   printf("Usage: %s [OPTIONS] database [tables]\n", my_progname);
270   printf("OR     %s [OPTIONS] --databases DB1 [DB2 DB3...]\n",
271 	 my_progname);
272   printf("OR     %s [OPTIONS] --all-databases\n", my_progname);
273   print_defaults("my", load_default_groups);
274   my_print_help(my_long_options);
275   my_print_variables(my_long_options);
276 } /* usage */
277 
278 
279 static my_bool
get_one_option(int optid,const struct my_option * opt MY_ATTRIBUTE ((unused)),char * argument)280 get_one_option(int optid, const struct my_option *opt MY_ATTRIBUTE((unused)),
281 	       char *argument)
282 {
283   int orig_what_to_do= what_to_do;
284 
285   switch(optid) {
286   case 'a':
287     what_to_do = DO_ANALYZE;
288     break;
289   case 'c':
290     what_to_do = DO_CHECK;
291     break;
292   case 'C':
293     what_to_do = DO_CHECK;
294     opt_check_only_changed = 1;
295     break;
296   case 'I': /* Fall through */
297   case '?':
298     usage();
299     exit(0);
300   case 'm':
301     what_to_do = DO_CHECK;
302     opt_medium_check = 1;
303     break;
304   case 'o':
305     what_to_do = DO_OPTIMIZE;
306     break;
307   case OPT_FIX_DB_NAMES:
308     what_to_do= DO_UPGRADE;
309     opt_databases= 1;
310     break;
311   case OPT_FIX_TABLE_NAMES:
312     what_to_do= DO_UPGRADE;
313     break;
314   case 'p':
315     if (argument == disabled_my_option)
316       argument= (char*) "";			/* Don't require password */
317     if (argument)
318     {
319       char *start = argument;
320       my_free(opt_password);
321       opt_password = my_strdup(argument, MYF(MY_FAE));
322       while (*argument) *argument++= 'x';		/* Destroy argument */
323       if (*start)
324 	start[1] = 0;                             /* Cut length of argument */
325       tty_password= 0;
326     }
327     else
328       tty_password = 1;
329     break;
330   case 'r':
331     what_to_do = DO_REPAIR;
332     break;
333   case 'g':
334     what_to_do= DO_CHECK;
335     opt_upgrade= 1;
336     break;
337   case 'W':
338 #ifdef __WIN__
339     opt_protocol = MYSQL_PROTOCOL_PIPE;
340 #endif
341     break;
342   case '#':
343     DBUG_PUSH(argument ? argument : "d:t:o");
344     debug_check_flag= 1;
345     break;
346 #include <sslopt-case.h>
347   case OPT_TABLES:
348     opt_databases = 0;
349     break;
350   case 'v':
351     verbose++;
352     break;
353   case 'V': print_version(); exit(0);
354   case OPT_ENABLE_CLEARTEXT_PLUGIN:
355     using_opt_enable_cleartext_plugin= TRUE;
356     break;
357   case OPT_MYSQL_PROTOCOL:
358     opt_protocol= find_type_or_exit(argument, &sql_protocol_typelib,
359                                     opt->name);
360     break;
361   }
362 
363   if (orig_what_to_do && (what_to_do != orig_what_to_do))
364   {
365     fprintf(stderr, "Error:  %s doesn't support multiple contradicting commands.\n",
366             my_progname);
367     return 1;
368   }
369   return 0;
370 }
371 
372 
get_options(int * argc,char *** argv)373 static int get_options(int *argc, char ***argv)
374 {
375   int ho_error;
376 
377   if (*argc == 1)
378   {
379     usage();
380     exit(0);
381   }
382 
383   my_getopt_use_args_separator= TRUE;
384   if ((ho_error= load_defaults("my", load_default_groups, argc, argv)) ||
385       (ho_error=handle_options(argc, argv, my_long_options, get_one_option)))
386     exit(ho_error);
387   my_getopt_use_args_separator= FALSE;
388 
389   if (!what_to_do)
390   {
391     size_t pnlen= strlen(my_progname);
392 
393     if (pnlen < 6) /* name too short */
394       what_to_do = DO_CHECK;
395     else if (!strcmp("repair", my_progname + pnlen - 6))
396       what_to_do = DO_REPAIR;
397     else if (!strcmp("analyze", my_progname + pnlen - 7))
398       what_to_do = DO_ANALYZE;
399     else if  (!strcmp("optimize", my_progname + pnlen - 8))
400       what_to_do = DO_OPTIMIZE;
401     else
402       what_to_do = DO_CHECK;
403   }
404 
405   /*
406     If there's no --default-character-set option given with
407     --fix-table-name or --fix-db-name set the default character set to "utf8".
408   */
409   if (!default_charset)
410   {
411     if (opt_fix_db_names || opt_fix_table_names)
412       default_charset= (char*) "utf8";
413     else
414       default_charset= (char*) MYSQL_AUTODETECT_CHARSET_NAME;
415   }
416   if (strcmp(default_charset, MYSQL_AUTODETECT_CHARSET_NAME) &&
417       !get_charset_by_csname(default_charset, MY_CS_PRIMARY, MYF(MY_WME)))
418   {
419     printf("Unsupported character set: %s\n", default_charset);
420     return 1;
421   }
422   if (*argc > 0 && opt_alldbs)
423   {
424     printf("You should give only options, no arguments at all, with option\n");
425     printf("--all-databases. Please see %s --help for more information.\n",
426 	   my_progname);
427     return 1;
428   }
429 
430   if (*argc < 1 && !opt_alldbs)
431   {
432     printf("You forgot to give the arguments! Please see %s --help\n",
433 	   my_progname);
434     printf("for more information.\n");
435     return 1;
436   }
437   if (tty_password)
438     opt_password = get_tty_password(NullS);
439   if (debug_info_flag)
440     my_end_arg= MY_CHECK_ERROR | MY_GIVE_INFO;
441   if (debug_check_flag)
442     my_end_arg= MY_CHECK_ERROR;
443   return(0);
444 } /* get_options */
445 
446 
process_all_databases()447 static int process_all_databases()
448 {
449   MYSQL_ROW row;
450   MYSQL_RES *tableres;
451   int result = 0;
452 
453   if (mysql_query(sock, "SHOW DATABASES") ||
454       !(tableres = mysql_store_result(sock)))
455   {
456     my_printf_error(0, "Error: Couldn't execute 'SHOW DATABASES': %s",
457 		    MYF(0), mysql_error(sock));
458     return 1;
459   }
460   while ((row = mysql_fetch_row(tableres)))
461   {
462     if (process_one_db(row[0]))
463       result = 1;
464   }
465   mysql_free_result(tableres);
466   return result;
467 }
468 /* process_all_databases */
469 
470 
process_databases(char ** db_names)471 static int process_databases(char **db_names)
472 {
473   int result = 0;
474   for ( ; *db_names ; db_names++)
475   {
476     if (process_one_db(*db_names))
477       result = 1;
478   }
479   return result;
480 } /* process_databases */
481 
482 
process_selected_tables(char * db,char ** table_names,int tables)483 static int process_selected_tables(char *db, char **table_names, int tables)
484 {
485   if (use_db(db))
486     return 1;
487   if (opt_all_in_1 && what_to_do != DO_UPGRADE)
488   {
489     /*
490       We need table list in form `a`, `b`, `c`
491       that's why we need 2 more chars added to to each table name
492       space is for more readable output in logs and in case of error
493     */
494     char *table_names_comma_sep, *end;
495     size_t tot_length= 0;
496     int             i= 0;
497 
498     for (i = 0; i < tables; i++)
499       tot_length+= fixed_name_length(*(table_names + i)) + 2;
500 
501     if (!(table_names_comma_sep = (char *)
502 	  my_malloc((sizeof(char) * tot_length) + 4, MYF(MY_WME))))
503       return 1;
504 
505     for (end = table_names_comma_sep + 1; tables > 0;
506 	 tables--, table_names++)
507     {
508       end= fix_table_name(end, *table_names);
509       *end++= ',';
510     }
511     *--end = 0;
512     handle_request_for_tables(table_names_comma_sep + 1, tot_length - 1);
513     my_free(table_names_comma_sep);
514   }
515   else
516     for (; tables > 0; tables--, table_names++)
517       handle_request_for_tables(*table_names, fixed_name_length(*table_names));
518   return 0;
519 } /* process_selected_tables */
520 
521 
fixed_name_length(const char * name)522 static size_t fixed_name_length(const char *name)
523 {
524   const char *p;
525   size_t extra_length= 2;  /* count the first/last backticks */
526 
527   for (p= name; *p; p++)
528   {
529     if (*p == '`')
530       extra_length++;
531     else if (*p == '.')
532       extra_length+= 2;
533   }
534   return (size_t) ((p - name) + extra_length);
535 }
536 
537 
fix_table_name(char * dest,char * src)538 static char *fix_table_name(char *dest, char *src)
539 {
540   *dest++= '`';
541   for (; *src; src++)
542   {
543     switch (*src) {
544     case '.':            /* add backticks around '.' */
545       *dest++= '`';
546       *dest++= '.';
547       *dest++= '`';
548       break;
549     case '`':            /* escape backtick character */
550       *dest++= '`';
551       /* fall through */
552     default:
553       *dest++= *src;
554     }
555   }
556   *dest++= '`';
557   return dest;
558 }
559 
560 
process_all_tables_in_db(char * database)561 static int process_all_tables_in_db(char *database)
562 {
563   MYSQL_RES *res;
564   MYSQL_ROW row;
565   uint num_columns;
566 
567   LINT_INIT(res);
568   if (use_db(database))
569     return 1;
570   if ((mysql_query(sock, "SHOW /*!50002 FULL*/ TABLES") &&
571        mysql_query(sock, "SHOW TABLES")) ||
572       !(res= mysql_store_result(sock)))
573   {
574     my_printf_error(0, "Error: Couldn't get table list for database %s: %s",
575 		    MYF(0), database, mysql_error(sock));
576     return 1;
577   }
578 
579   num_columns= mysql_num_fields(res);
580 
581   if (opt_all_in_1 && what_to_do != DO_UPGRADE)
582   {
583     /*
584       We need table list in form `a`, `b`, `c`
585       that's why we need 2 more chars added to to each table name
586       space is for more readable output in logs and in case of error
587      */
588 
589     char *tables, *end;
590     size_t tot_length = 0;
591 
592     while ((row = mysql_fetch_row(res)))
593       tot_length+= fixed_name_length(row[0]) + 2;
594     mysql_data_seek(res, 0);
595 
596     if (!(tables=(char *) my_malloc(sizeof(char)*tot_length+4, MYF(MY_WME))))
597     {
598       mysql_free_result(res);
599       return 1;
600     }
601     for (end = tables + 1; (row = mysql_fetch_row(res)) ;)
602     {
603       if ((num_columns == 2) && (strcmp(row[1], "VIEW") == 0))
604         continue;
605 
606       end= fix_table_name(end, row[0]);
607       *end++= ',';
608     }
609     *--end = 0;
610     if (tot_length)
611       handle_request_for_tables(tables + 1, tot_length - 1);
612     my_free(tables);
613   }
614   else
615   {
616     while ((row = mysql_fetch_row(res)))
617     {
618       /* Skip views if we don't perform renaming. */
619       if ((what_to_do != DO_UPGRADE) && (num_columns == 2) && (strcmp(row[1], "VIEW") == 0))
620         continue;
621 
622       handle_request_for_tables(row[0], fixed_name_length(row[0]));
623     }
624   }
625   mysql_free_result(res);
626   return 0;
627 } /* process_all_tables_in_db */
628 
629 
run_query(const char * query)630 static int run_query(const char *query)
631 {
632   if (mysql_query(sock, query))
633   {
634     fprintf(stderr, "Failed to %s\n", query);
635     fprintf(stderr, "Error: %s\n", mysql_error(sock));
636     return 1;
637   }
638   return 0;
639 }
640 
641 
fix_table_storage_name(const char * name)642 static int fix_table_storage_name(const char *name)
643 {
644   char qbuf[100 + NAME_LEN*4];
645   int rc= 0;
646   if (strncmp(name, "#mysql50#", 9))
647     return 1;
648   my_snprintf(qbuf, sizeof(qbuf), "RENAME TABLE `%s` TO `%s`",
649               name, name + 9);
650 
651   rc= run_query(qbuf);
652   if (verbose)
653     printf("%-50s %s\n", name, rc ? "FAILED" : "OK");
654   return rc;
655 }
656 
fix_database_storage_name(const char * name)657 static int fix_database_storage_name(const char *name)
658 {
659   char qbuf[100 + NAME_LEN*4];
660   int rc= 0;
661   if (strncmp(name, "#mysql50#", 9))
662     return 1;
663   my_snprintf(qbuf, sizeof(qbuf), "ALTER DATABASE `%s` UPGRADE DATA DIRECTORY "
664               "NAME", name);
665   rc= run_query(qbuf);
666   if (verbose)
667     printf("%-50s %s\n", name, rc ? "FAILED" : "OK");
668   return rc;
669 }
670 
rebuild_table(char * name)671 static int rebuild_table(char *name)
672 {
673   char *query, *ptr;
674   int rc= 0;
675   query= (char*)my_malloc(sizeof(char) * (12 + fixed_name_length(name) + 6 + 1),
676                           MYF(MY_WME));
677   if (!query)
678     return 1;
679   ptr= strmov(query, "ALTER TABLE ");
680   ptr= fix_table_name(ptr, name);
681   ptr= strxmov(ptr, " FORCE", NullS);
682   if (mysql_real_query(sock, query, (ulong)(ptr - query)))
683   {
684     fprintf(stderr, "Failed to %s\n", query);
685     fprintf(stderr, "Error: %s\n", mysql_error(sock));
686     rc= 1;
687   }
688   my_free(query);
689   return rc;
690 }
691 
process_one_db(char * database)692 static int process_one_db(char *database)
693 {
694   if (opt_skip_database && opt_alldbs &&
695       !strcmp(database, opt_skip_database))
696     return 0;
697 
698   if (what_to_do == DO_UPGRADE)
699   {
700     int rc= 0;
701     if (opt_fix_db_names && !strncmp(database,"#mysql50#", 9))
702     {
703       rc= fix_database_storage_name(database);
704       database+= 9;
705     }
706     if (rc || !opt_fix_table_names)
707       return rc;
708   }
709   return process_all_tables_in_db(database);
710 }
711 
712 
use_db(char * database)713 static int use_db(char *database)
714 {
715   if (mysql_get_server_version(sock) >= FIRST_INFORMATION_SCHEMA_VERSION &&
716       !my_strcasecmp(&my_charset_latin1, database, INFORMATION_SCHEMA_DB_NAME))
717     return 1;
718   if (mysql_get_server_version(sock) >= FIRST_PERFORMANCE_SCHEMA_VERSION &&
719       !my_strcasecmp(&my_charset_latin1, database, PERFORMANCE_SCHEMA_DB_NAME))
720     return 1;
721   if (mysql_select_db(sock, database))
722   {
723     DBerror(sock, "when selecting the database");
724     return 1;
725   }
726   return 0;
727 } /* use_db */
728 
disable_binlog()729 static int disable_binlog()
730 {
731   const char *stmt= "SET SQL_LOG_BIN=0";
732   return run_query(stmt);
733 }
734 
handle_request_for_tables(char * tables,size_t length)735 static int handle_request_for_tables(char *tables, size_t length)
736 {
737   char *query, *end, options[100];
738   size_t query_length= 0, query_size= sizeof(char)*(length+110);
739   const char *op = 0;
740 
741   options[0] = 0;
742   end = options;
743   switch (what_to_do) {
744   case DO_CHECK:
745     op = "CHECK";
746     if (opt_quick)              end = strmov(end, " QUICK");
747     if (opt_fast)               end = strmov(end, " FAST");
748     if (opt_medium_check)       end = strmov(end, " MEDIUM"); /* Default */
749     if (opt_extended)           end = strmov(end, " EXTENDED");
750     if (opt_check_only_changed) end = strmov(end, " CHANGED");
751     if (opt_upgrade)            end = strmov(end, " FOR UPGRADE");
752     break;
753   case DO_REPAIR:
754     op= (opt_write_binlog) ? "REPAIR" : "REPAIR NO_WRITE_TO_BINLOG";
755     if (opt_quick)              end = strmov(end, " QUICK");
756     if (opt_extended)           end = strmov(end, " EXTENDED");
757     if (opt_frm)                end = strmov(end, " USE_FRM");
758     break;
759   case DO_ANALYZE:
760     op= (opt_write_binlog) ? "ANALYZE" : "ANALYZE NO_WRITE_TO_BINLOG";
761     break;
762   case DO_OPTIMIZE:
763     op= (opt_write_binlog) ? "OPTIMIZE" : "OPTIMIZE NO_WRITE_TO_BINLOG";
764     break;
765   case DO_UPGRADE:
766     return fix_table_storage_name(tables);
767   }
768 
769   if (!(query =(char *) my_malloc(query_size, MYF(MY_WME))))
770   {
771     return 1;
772   }
773   if (opt_all_in_1)
774   {
775     DBUG_ASSERT(strlen(op)+strlen(tables)+strlen(options)+8+1 <= query_size);
776 
777     /* No backticks here as we added them before */
778     query_length= sprintf(query, "%s TABLE %s %s", op, tables, options);
779   }
780   else
781   {
782     char *ptr;
783 
784     ptr= strmov(strmov(query, op), " TABLE ");
785     ptr= fix_table_name(ptr, tables);
786     ptr= strxmov(ptr, " ", options, NullS);
787     query_length= (size_t) (ptr - query);
788   }
789   if (mysql_real_query(sock, query, query_length))
790   {
791     static const char format_msg[]= "when executing '%s TABLE ... %s'";
792     char message[sizeof(options) + sizeof(format_msg) +
793                  32 /* to fit "OPTIMIZE NO_WRITE_TO_BINLOG" */];
794     snprintf(message, sizeof(message), format_msg, op, options);
795     DBerror(sock, message);
796     return 1;
797   }
798   print_result();
799   my_free(query);
800   return 0;
801 }
802 
803 
print_result()804 static void print_result()
805 {
806   MYSQL_RES *res;
807   MYSQL_ROW row;
808   char prev[NAME_LEN*2+2];
809   char prev_alter[MAX_ALTER_STR_SIZE];
810   uint i;
811   my_bool found_error=0, table_rebuild=0;
812 
813   res = mysql_use_result(sock);
814 
815   prev[0] = '\0';
816   prev_alter[0]= 0;
817   for (i = 0; (row = mysql_fetch_row(res)); i++)
818   {
819     int changed = strcmp(prev, row[0]);
820     my_bool status = !strcmp(row[2], "status");
821 
822     if (status)
823     {
824       /*
825         if there was an error with the table, we have --auto-repair set,
826         and this isn't a repair op, then add the table to the tables4repair
827         list
828       */
829       if (found_error && opt_auto_repair && what_to_do != DO_REPAIR &&
830 	  strcmp(row[3],"OK"))
831       {
832         if (table_rebuild)
833         {
834           if (prev_alter[0])
835             insert_dynamic(&alter_table_cmds, (uchar*) prev_alter);
836           else
837             insert_dynamic(&tables4rebuild, (uchar*) prev);
838         }
839         else
840           insert_dynamic(&tables4repair, prev);
841       }
842       found_error=0;
843       table_rebuild=0;
844       prev_alter[0]= 0;
845       if (opt_silent)
846 	continue;
847     }
848     if (status && changed)
849       printf("%-50s %s", row[0], row[3]);
850     else if (!status && changed)
851     {
852       printf("%s\n%-9s: %s", row[0], row[2], row[3]);
853       if (opt_auto_repair && strcmp(row[2],"note"))
854       {
855         const char *alter_txt= strstr(row[3], "ALTER TABLE");
856         found_error=1;
857         if (alter_txt)
858         {
859           table_rebuild=1;
860           if (!strncmp(row[3], KEY_PARTITIONING_CHANGED_STR,
861                        strlen(KEY_PARTITIONING_CHANGED_STR)) &&
862               strstr(alter_txt, "PARTITION BY"))
863           {
864             if (strlen(alter_txt) >= MAX_ALTER_STR_SIZE)
865             {
866               printf("Error: Alter command too long (>= %d),"
867                      " please do \"%s\" or dump/reload to fix it!\n",
868                      MAX_ALTER_STR_SIZE,
869                      alter_txt);
870               table_rebuild= 0;
871               prev_alter[0]= 0;
872             }
873             else
874             {
875               strncpy(prev_alter, alter_txt, MAX_ALTER_STR_SIZE-1);
876               prev_alter[MAX_ALTER_STR_SIZE-1]= 0;
877             }
878           }
879         }
880       }
881     }
882     else
883       printf("%-9s: %s", row[2], row[3]);
884     strmov(prev, row[0]);
885     putchar('\n');
886   }
887   /* add the last table to be repaired to the list */
888   if (found_error && opt_auto_repair && what_to_do != DO_REPAIR)
889   {
890     if (table_rebuild)
891     {
892       if (prev_alter[0])
893         insert_dynamic(&alter_table_cmds, (uchar*) prev_alter);
894       else
895         insert_dynamic(&tables4rebuild, (uchar*) prev);
896     }
897     else
898       insert_dynamic(&tables4repair, prev);
899   }
900   mysql_free_result(res);
901 }
902 
903 
dbConnect(char * host,char * user,char * passwd)904 static int dbConnect(char *host, char *user, char *passwd)
905 {
906   DBUG_ENTER("dbConnect");
907   if (verbose)
908   {
909     fprintf(stderr, "# Connecting to %s...\n", host ? host : "localhost");
910   }
911   mysql_init(&mysql_connection);
912   if (opt_compress)
913     mysql_options(&mysql_connection, MYSQL_OPT_COMPRESS, NullS);
914 #ifdef HAVE_OPENSSL
915   if (opt_use_ssl)
916   {
917     mysql_ssl_set(&mysql_connection, opt_ssl_key, opt_ssl_cert, opt_ssl_ca,
918 		  opt_ssl_capath, opt_ssl_cipher);
919     mysql_options(&mysql_connection, MYSQL_OPT_SSL_CRL, opt_ssl_crl);
920     mysql_options(&mysql_connection, MYSQL_OPT_SSL_CRLPATH, opt_ssl_crlpath);
921   }
922 #endif
923   if (opt_protocol)
924     mysql_options(&mysql_connection,MYSQL_OPT_PROTOCOL,(char*)&opt_protocol);
925   if (opt_bind_addr)
926     mysql_options(&mysql_connection, MYSQL_OPT_BIND, opt_bind_addr);
927   if (!opt_secure_auth)
928     mysql_options(&mysql_connection, MYSQL_SECURE_AUTH,(char*)&opt_secure_auth);
929 #ifdef HAVE_SMEM
930   if (shared_memory_base_name)
931     mysql_options(&mysql_connection,MYSQL_SHARED_MEMORY_BASE_NAME,shared_memory_base_name);
932 #endif
933 
934   if (opt_plugin_dir && *opt_plugin_dir)
935     mysql_options(&mysql_connection, MYSQL_PLUGIN_DIR, opt_plugin_dir);
936 
937   if (opt_default_auth && *opt_default_auth)
938     mysql_options(&mysql_connection, MYSQL_DEFAULT_AUTH, opt_default_auth);
939 
940   if (using_opt_enable_cleartext_plugin)
941     mysql_options(&mysql_connection, MYSQL_ENABLE_CLEARTEXT_PLUGIN,
942                   (char *) &opt_enable_cleartext_plugin);
943 
944   mysql_options(&mysql_connection, MYSQL_SET_CHARSET_NAME, default_charset);
945   mysql_options(&mysql_connection, MYSQL_OPT_CONNECT_ATTR_RESET, 0);
946   mysql_options4(&mysql_connection, MYSQL_OPT_CONNECT_ATTR_ADD,
947                  "program_name", "mysqlcheck");
948   if (!(sock = mysql_connect_ssl_check(&mysql_connection, host, user, passwd,
949                                        NULL, opt_mysql_port,
950                                        opt_mysql_unix_port, 0,
951                                        opt_ssl_mode == SSL_MODE_REQUIRED)))
952   {
953     DBerror(&mysql_connection, "when trying to connect");
954     DBUG_RETURN(1);
955   }
956   mysql_connection.reconnect= 1;
957   DBUG_RETURN(0);
958 } /* dbConnect */
959 
960 
dbDisconnect(char * host)961 static void dbDisconnect(char *host)
962 {
963   if (verbose)
964     fprintf(stderr, "# Disconnecting from %s...\n", host ? host : "localhost");
965   mysql_close(sock);
966 } /* dbDisconnect */
967 
968 
DBerror(MYSQL * mysql,const char * when)969 static void DBerror(MYSQL *mysql, const char *when)
970 {
971   DBUG_ENTER("DBerror");
972   my_printf_error(0,"Got error: %d: %s %s", MYF(0),
973 		  mysql_errno(mysql), mysql_error(mysql), when);
974   safe_exit(EX_MYSQLERR);
975   DBUG_VOID_RETURN;
976 } /* DBerror */
977 
978 
safe_exit(int error)979 static void safe_exit(int error)
980 {
981   if (!first_error)
982     first_error= error;
983   if (ignore_errors)
984     return;
985   if (sock)
986     mysql_close(sock);
987   exit(error);
988 }
989 
990 
main(int argc,char ** argv)991 int main(int argc, char **argv)
992 {
993   MY_INIT(argv[0]);
994   /*
995   ** Check out the args
996   */
997   if (get_options(&argc, &argv))
998   {
999     my_end(my_end_arg);
1000     exit(EX_USAGE);
1001   }
1002   if (dbConnect(current_host, current_user, opt_password))
1003     exit(EX_MYSQLERR);
1004 
1005   if (!opt_write_binlog)
1006   {
1007     if (disable_binlog()) {
1008       first_error= 1;
1009       goto end;
1010     }
1011   }
1012 
1013   if (opt_auto_repair &&
1014       (my_init_dynamic_array(&tables4repair, sizeof(char)*(NAME_LEN*2+2),16,64) ||
1015        my_init_dynamic_array(&tables4rebuild, sizeof(char)*(NAME_LEN*2+2),16,64) ||
1016        my_init_dynamic_array(&alter_table_cmds, MAX_ALTER_STR_SIZE, 0, 1)))
1017   {
1018     first_error = 1;
1019     goto end;
1020   }
1021 
1022   if (opt_alldbs)
1023     process_all_databases();
1024   /* Only one database and selected table(s) */
1025   else if (argc > 1 && !opt_databases)
1026     process_selected_tables(*argv, (argv + 1), (argc - 1));
1027   /* One or more databases, all tables */
1028   else
1029     process_databases(argv);
1030   if (opt_auto_repair)
1031   {
1032     size_t i;
1033 
1034     if (!opt_silent && (tables4repair.elements || tables4rebuild.elements))
1035       puts("\nRepairing tables");
1036     what_to_do = DO_REPAIR;
1037     for (i = 0; i < tables4repair.elements ; i++)
1038     {
1039       char *name= (char*) dynamic_array_ptr(&tables4repair, i);
1040       handle_request_for_tables(name, fixed_name_length(name));
1041     }
1042     for (i = 0; i < tables4rebuild.elements ; i++)
1043       rebuild_table((char*) dynamic_array_ptr(&tables4rebuild, i));
1044     for (i = 0; i < alter_table_cmds.elements ; i++)
1045       run_query((char*) dynamic_array_ptr(&alter_table_cmds, i));
1046   }
1047  end:
1048   dbDisconnect(current_host);
1049   if (opt_auto_repair)
1050   {
1051     delete_dynamic(&tables4repair);
1052     delete_dynamic(&tables4rebuild);
1053   }
1054   my_free(opt_password);
1055 #ifdef HAVE_SMEM
1056   my_free(shared_memory_base_name);
1057 #endif
1058   my_end(my_end_arg);
1059   return(first_error!=0);
1060 } /* main */
1061