MySQL 5.6.14 Source Code Document
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
mysql_upgrade.c
1 /*
2  Copyright (c) 2006, 2013, 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 as published by
6  the Free Software Foundation; version 2 of the License.
7 
8  This program is distributed in the hope that it will be useful,
9  but WITHOUT ANY WARRANTY; without even the implied warranty of
10  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  GNU General Public License for more details.
12 
13  You should have received a copy of the GNU General Public License
14  along with this program; if not, write to the Free Software Foundation,
15  51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
16 */
17 
18 #include "client_priv.h"
19 #include "my_default.h"
20 #include <sslopt-vars.h>
21 #include "../scripts/mysql_fix_privilege_tables_sql.c"
22 
23 #include <welcome_copyright_notice.h> /* ORACLE_WELCOME_COPYRIGHT_NOTICE */
24 
25 #define VER "1.1"
26 
27 #ifdef HAVE_SYS_WAIT_H
28 #include <sys/wait.h>
29 #endif
30 
31 #ifndef WEXITSTATUS
32 # ifdef __WIN__
33 # define WEXITSTATUS(stat_val) (stat_val)
34 # else
35 # define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
36 # endif
37 #endif
38 
39 static char mysql_path[FN_REFLEN];
40 static char mysqlcheck_path[FN_REFLEN];
41 
42 static my_bool opt_force, opt_verbose, debug_info_flag, debug_check_flag,
43  opt_systables_only, opt_version_check;
44 static uint my_end_arg= 0;
45 static char *opt_user= (char*)"root";
46 
47 static DYNAMIC_STRING ds_args;
48 static DYNAMIC_STRING conn_args;
49 
50 static char *opt_password= 0;
51 static char *opt_plugin_dir= 0, *opt_default_auth= 0;
52 
53 static my_bool tty_password= 0;
54 
55 static char opt_tmpdir[FN_REFLEN] = "";
56 
57 #ifndef DBUG_OFF
58 static char *default_dbug_option= (char*) "d:t:O,/tmp/mysql_upgrade.trace";
59 #endif
60 
61 static char **defaults_argv;
62 
63 static my_bool not_used; /* Can't use GET_BOOL without a value pointer */
64 
65 static my_bool opt_write_binlog;
66 
67 static struct my_option my_long_options[]=
68 {
69  {"help", '?', "Display this help message and exit.", 0, 0, 0, GET_NO_ARG,
70  NO_ARG, 0, 0, 0, 0, 0, 0},
71  {"basedir", 'b', "Not used by mysql_upgrade. Only for backward compatibility.",
72  0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
73  {"character-sets-dir", OPT_CHARSETS_DIR,
74  "Directory for character set files.", 0,
75  0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
76  {"compress", OPT_COMPRESS, "Use compression in server/client protocol.",
77  &not_used, &not_used, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
78  {"datadir", 'd',
79  "Not used by mysql_upgrade. Only for backward compatibility.",
80  0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
81 #ifdef DBUG_OFF
82  {"debug", '#', "This is a non-debug version. Catch this and exit.",
83  0, 0, 0, GET_DISABLED, OPT_ARG, 0, 0, 0, 0, 0, 0},
84 #else
85  {"debug", '#', "Output debug log.", &default_dbug_option,
86  &default_dbug_option, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0},
87 #endif
88  {"debug-check", OPT_DEBUG_CHECK, "Check memory and open file usage at exit.",
89  &debug_check_flag, &debug_check_flag, 0,
90  GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
91  {"debug-info", 'T', "Print some debug info at exit.", &debug_info_flag,
92  &debug_info_flag, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
93  {"default-character-set", OPT_DEFAULT_CHARSET,
94  "Set the default character set.", 0,
95  0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
96  {"default_auth", OPT_DEFAULT_AUTH,
97  "Default authentication client-side plugin to use.",
98  &opt_default_auth, &opt_default_auth, 0,
99  GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
100  {"force", 'f', "Force execution of mysqlcheck even if mysql_upgrade "
101  "has already been executed for the current version of MySQL.",
102  &opt_force, &opt_force, 0,
103  GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
104  {"host",'h', "Connect to host.", 0,
105  0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
106  {"password", 'p',
107  "Password to use when connecting to server. If password is not given,"
108  " it's solicited on the tty.", &opt_password,&opt_password,
109  0, GET_PASSWORD, OPT_ARG, 0, 0, 0, 0, 0, 0},
110 #ifdef __WIN__
111  {"pipe", 'W', "Use named pipes to connect to server.", 0, 0, 0,
112  GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
113 #endif
114  {"plugin_dir", OPT_PLUGIN_DIR, "Directory for client-side plugins.",
115  &opt_plugin_dir, &opt_plugin_dir, 0,
116  GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
117  {"port", 'P', "Port number to use for connection or 0 for default to, in "
118  "order of preference, my.cnf, $MYSQL_TCP_PORT, "
119 #if MYSQL_PORT_DEFAULT == 0
120  "/etc/services, "
121 #endif
122  "built-in default (" STRINGIFY_ARG(MYSQL_PORT) ").",
123  0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
124  {"protocol", OPT_MYSQL_PROTOCOL,
125  "The protocol to use for connection (tcp, socket, pipe, memory).",
126  0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
127 #ifdef HAVE_SMEM
128  {"shared-memory-base-name", OPT_SHARED_MEMORY_BASE_NAME,
129  "Base name of shared memory.", 0,
130  0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
131 #endif
132  {"version-check", 'k', "Run this program only if its \'server version\' "
133  "matches the version of the server to which it's connecting, (enabled by "
134  "default); use --skip-version-check to avoid this check. Note: the \'server "
135  "version\' of the program is the version of the MySQL server with which it "
136  "was built/distributed.", &opt_version_check, &opt_version_check, 0,
137  GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0},
138  {"socket", 'S', "The socket file to use for connection.",
139  0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
140 #include <sslopt-longopts.h>
141  {"tmpdir", 't', "Directory for temporary files.",
142  0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
143  {"upgrade-system-tables", 's', "Only upgrade the system tables "
144  "do not try to upgrade the data.",
145  &opt_systables_only, &opt_systables_only, 0,
146  GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
147  {"user", 'u', "User for login if not current user.", &opt_user,
148  &opt_user, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
149  {"verbose", 'v', "Display more output about the process.",
150  &opt_verbose, &opt_verbose, 0,
151  GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0},
152  {"write-binlog", OPT_WRITE_BINLOG,
153  "All commands including mysqlcheck are binlogged. Disabled by default; "
154  "use when commands should be sent to replication slaves.",
155  &opt_write_binlog, &opt_write_binlog, 0, GET_BOOL, NO_ARG,
156  0, 0, 0, 0, 0, 0},
157  {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
158 };
159 
160 
161 static void free_used_memory(void)
162 {
163  /* Free memory allocated by 'load_defaults' */
164  free_defaults(defaults_argv);
165 
166  dynstr_free(&ds_args);
167  dynstr_free(&conn_args);
168 }
169 
170 
171 static void die(const char *fmt, ...)
172 {
173  va_list args;
174  DBUG_ENTER("die");
175 
176  /* Print the error message */
177  va_start(args, fmt);
178  if (fmt)
179  {
180  fprintf(stderr, "FATAL ERROR: ");
181  vfprintf(stderr, fmt, args);
182  fprintf(stderr, "\n");
183  fflush(stderr);
184  }
185  va_end(args);
186 
187  DBUG_LEAVE;
188  free_used_memory();
189  my_end(my_end_arg);
190  exit(1);
191 }
192 
193 
194 static void verbose(const char *fmt, ...)
195 {
196  va_list args;
197 
198  if (!opt_verbose)
199  return;
200 
201  /* Print the verbose message */
202  va_start(args, fmt);
203  if (fmt)
204  {
205  vfprintf(stdout, fmt, args);
206  fprintf(stdout, "\n");
207  fflush(stdout);
208  }
209  va_end(args);
210 }
211 
212 
213 /*
214  Add one option - passed to mysql_upgrade on command line
215  or by defaults file(my.cnf) - to a dynamic string, in
216  this way we pass the same arguments on to mysql and mysql_check
217 */
218 
219 static void add_one_option(DYNAMIC_STRING* ds,
220  const struct my_option *opt,
221  const char* argument)
222 
223 {
224  const char* eq= NullS;
225  const char* arg= NullS;
226  if (opt->arg_type != NO_ARG)
227  {
228  eq= "=";
229  switch (opt->var_type & GET_TYPE_MASK) {
230  case GET_STR:
231  case GET_PASSWORD:
232  arg= argument;
233  break;
234  case GET_BOOL:
235  arg= (*(my_bool *)opt->value) ? "1" : "0";
236  break;
237  default:
238  die("internal error at %s: %d",__FILE__, __LINE__);
239  }
240  }
241  dynstr_append_os_quoted(ds, "--", opt->name, eq, arg, NullS);
242  dynstr_append(ds, " ");
243 }
244 
245 
246 static my_bool
247 get_one_option(int optid, const struct my_option *opt,
248  char *argument)
249 {
250  my_bool add_option= TRUE;
251 
252  switch (optid) {
253 
254  case '?':
255  printf("%s Ver %s Distrib %s, for %s (%s)\n",
256  my_progname, VER, MYSQL_SERVER_VERSION, SYSTEM_TYPE, MACHINE_TYPE);
257  puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2000"));
258  puts("MySQL utility for upgrading databases to new MySQL versions.\n");
259  my_print_help(my_long_options);
260  exit(0);
261  break;
262 
263  case '#':
264  DBUG_PUSH(argument ? argument : default_dbug_option);
265  add_option= FALSE;
266  debug_check_flag= 1;
267  break;
268 
269  case 'p':
270  if (argument == disabled_my_option)
271  argument= (char*) ""; /* Don't require password */
272  tty_password= 1;
273  add_option= FALSE;
274  if (argument)
275  {
276  /* Add password to ds_args before overwriting the arg with x's */
277  add_one_option(&ds_args, opt, argument);
278  while (*argument)
279  *argument++= 'x'; /* Destroy argument */
280  tty_password= 0;
281  }
282  break;
283 
284  case 't':
285  strnmov(opt_tmpdir, argument, sizeof(opt_tmpdir));
286  add_option= FALSE;
287  break;
288 
289  case 'b': /* --basedir */
290  case 'd': /* --datadir */
291  fprintf(stderr, "%s: the '--%s' option is always ignored\n",
292  my_progname, optid == 'b' ? "basedir" : "datadir");
293  /* FALLTHROUGH */
294 
295  case 'k': /* --version-check */
296  case 'v': /* --verbose */
297  case 'f': /* --force */
298  case 's': /* --upgrade-system-tables */
299  case OPT_WRITE_BINLOG: /* --write-binlog */
300  add_option= FALSE;
301  break;
302 
303  case 'h': /* --host */
304  case 'W': /* --pipe */
305  case 'P': /* --port */
306  case 'S': /* --socket */
307  case OPT_MYSQL_PROTOCOL: /* --protocol */
308  case OPT_SHARED_MEMORY_BASE_NAME: /* --shared-memory-base-name */
309  case OPT_PLUGIN_DIR: /* --plugin-dir */
310  case OPT_DEFAULT_AUTH: /* --default-auth */
311  add_one_option(&conn_args, opt, argument);
312  break;
313  }
314 
315  if (add_option)
316  {
317  /*
318  This is an option that is accpted by mysql_upgrade just so
319  it can be passed on to "mysql" and "mysqlcheck"
320  Save it in the ds_args string
321  */
322  add_one_option(&ds_args, opt, argument);
323  }
324  return 0;
325 }
326 
327 
332 static int run_command(char* cmd,
333  DYNAMIC_STRING *ds_res)
334 {
335  char buf[512]= {0};
336  FILE *res_file;
337  int error;
338 
339  if (! ds_res)
340  {
341  fflush(stdout);
342  fflush(stderr);
343  }
344  if (!(res_file= popen(cmd, "r")))
345  die("popen(\"%s\", \"r\") failed", cmd);
346 
347  while (fgets(buf, sizeof(buf), res_file))
348  {
349  DBUG_PRINT("info", ("buf: %s", buf));
350  if(ds_res)
351  {
352  /* Save the output of this command in the supplied string */
353  dynstr_append(ds_res, buf);
354  }
355  else
356  {
357  /* Print it directly on screen */
358  fprintf(stdout, "%s", buf);
359  }
360  }
361 
362  if (! ds_res)
363  {
364  fflush(stdout);
365  fflush(stderr);
366  }
367 
368  error= pclose(res_file);
369  return WEXITSTATUS(error);
370 }
371 
372 
373 static int run_tool(char *tool_path, DYNAMIC_STRING *ds_res, ...)
374 {
375  int ret;
376  const char* arg;
377  va_list args;
378  DYNAMIC_STRING ds_cmdline;
379 
380  DBUG_ENTER("run_tool");
381  DBUG_PRINT("enter", ("tool_path: %s", tool_path));
382 
383  if (init_dynamic_string(&ds_cmdline, IF_WIN("\"", ""), FN_REFLEN, FN_REFLEN))
384  die("Out of memory");
385 
386  dynstr_append_os_quoted(&ds_cmdline, tool_path, NullS);
387  dynstr_append(&ds_cmdline, " ");
388 
389  va_start(args, ds_res);
390 
391  while ((arg= va_arg(args, char *)))
392  {
393  /* Options should be os quoted */
394  if (strncmp(arg, "--", 2) == 0)
395  dynstr_append_os_quoted(&ds_cmdline, arg, NullS);
396  else
397  dynstr_append(&ds_cmdline, arg);
398  dynstr_append(&ds_cmdline, " ");
399  }
400 
401  va_end(args);
402 
403 #ifdef __WIN__
404  dynstr_append(&ds_cmdline, "\"");
405 #endif
406 
407  DBUG_PRINT("info", ("Running: %s", ds_cmdline.str));
408  ret= run_command(ds_cmdline.str, ds_res);
409  DBUG_PRINT("exit", ("ret: %d", ret));
410  dynstr_free(&ds_cmdline);
411  DBUG_RETURN(ret);
412 }
413 
414 
420 static void find_tool(char *tool_executable_name, const char *tool_name,
421  const char *self_name)
422 {
423  char *last_fn_libchar;
424  DYNAMIC_STRING ds_tmp;
425  DBUG_ENTER("find_tool");
426  DBUG_PRINT("enter", ("progname: %s", my_progname));
427 
428  if (init_dynamic_string(&ds_tmp, "", 32, 32))
429  die("Out of memory");
430 
431  last_fn_libchar= strrchr(self_name, FN_LIBCHAR);
432 
433  if (last_fn_libchar == NULL)
434  {
435  /*
436  mysql_upgrade was found by the shell searching the path. A sibling
437  next to us should be found the same way.
438  */
439  strncpy(tool_executable_name, tool_name, FN_REFLEN);
440  }
441  else
442  {
443  int len;
444 
445  /*
446  mysql_upgrade was run absolutely or relatively. We can find a sibling
447  by replacing our name after the LIBCHAR with the new tool name.
448  */
449 
450  /*
451  When running in a not yet installed build and using libtool,
452  the program(mysql_upgrade) will be in .libs/ and executed
453  through a libtool wrapper in order to use the dynamic libraries
454  from this build. The same must be done for the tools(mysql and
455  mysqlcheck). Thus if path ends in .libs/, step up one directory
456  and execute the tools from there
457  */
458  if (((last_fn_libchar - 6) >= self_name) &&
459  (strncmp(last_fn_libchar - 5, ".libs", 5) == 0) &&
460  (*(last_fn_libchar - 6) == FN_LIBCHAR))
461  {
462  DBUG_PRINT("info", ("Chopping off \".libs\" from end of path"));
463  last_fn_libchar -= 6;
464  }
465 
466  len= last_fn_libchar - self_name;
467 
468  my_snprintf(tool_executable_name, FN_REFLEN, "%.*s%c%s",
469  len, self_name, FN_LIBCHAR, tool_name);
470  }
471 
472  verbose("Looking for '%s' as: %s", tool_name, tool_executable_name);
473 
474  /*
475  Make sure it can be executed
476  */
477  if (run_tool(tool_executable_name,
478  &ds_tmp, /* Get output from command, discard*/
479  "--help",
480  "2>&1",
481  IF_WIN("> NUL", "> /dev/null"),
482  NULL))
483  die("Can't execute '%s'", tool_executable_name);
484 
485  dynstr_free(&ds_tmp);
486 
487  DBUG_VOID_RETURN;
488 }
489 
490 
491 /*
492  Run query using "mysql"
493 */
494 
495 static int run_query(const char *query, DYNAMIC_STRING *ds_res,
496  my_bool force)
497 {
498  int ret;
499  File fd;
500  char query_file_path[FN_REFLEN];
501  const uchar sql_log_bin[]= "SET SQL_LOG_BIN=0;";
502 
503  DBUG_ENTER("run_query");
504  DBUG_PRINT("enter", ("query: %s", query));
505  if ((fd= create_temp_file(query_file_path,
506  opt_tmpdir[0] ? opt_tmpdir : NULL,
507  "sql", O_CREAT | O_SHARE | O_RDWR,
508  MYF(MY_WME))) < 0)
509  die("Failed to create temporary file for defaults");
510 
511  /*
512  Master and slave should be upgraded separately. All statements executed
513  by mysql_upgrade will not be binlogged.
514  'SET SQL_LOG_BIN=0' is executed before any other statements.
515  */
516  if (!opt_write_binlog)
517  {
518  if (my_write(fd, sql_log_bin, sizeof(sql_log_bin)-1,
519  MYF(MY_FNABP | MY_WME)))
520  {
521  my_close(fd, MYF(0));
522  my_delete(query_file_path, MYF(0));
523  die("Failed to write to '%s'", query_file_path);
524  }
525  }
526 
527  if (my_write(fd, (uchar*) query, strlen(query),
528  MYF(MY_FNABP | MY_WME)))
529  {
530  my_close(fd, MYF(0));
531  my_delete(query_file_path, MYF(0));
532  die("Failed to write to '%s'", query_file_path);
533  }
534 
535  ret= run_tool(mysql_path,
536  ds_res,
537  "--no-defaults",
538  ds_args.str,
539  "--database=mysql",
540  "--batch", /* Turns off pager etc. */
541  force ? "--force": "--skip-force",
542  ds_res ? "--silent": "",
543  "<",
544  query_file_path,
545  "2>&1",
546  NULL);
547 
548  my_close(fd, MYF(0));
549  my_delete(query_file_path, MYF(0));
550 
551  DBUG_RETURN(ret);
552 }
553 
554 
555 /*
556  Extract the value returned from result of "show variable like ..."
557 */
558 
559 static int extract_variable_from_show(DYNAMIC_STRING* ds, char* value)
560 {
561  char *value_start, *value_end;
562  size_t len;
563 
564  /*
565  The query returns "datadir\t<datadir>\n", skip past
566  the tab
567  */
568  if ((value_start= strchr(ds->str, '\t')) == NULL)
569  return 1; /* Unexpected result */
570  value_start++;
571 
572  /* Don't copy the ending newline */
573  if ((value_end= strchr(value_start, '\n')) == NULL)
574  return 1; /* Unexpected result */
575 
576  len= (size_t) MY_MIN(FN_REFLEN, value_end-value_start);
577  strncpy(value, value_start, len);
578  value[len]= '\0';
579  return 0;
580 }
581 
582 
583 static int get_upgrade_info_file_name(char* name)
584 {
585  DYNAMIC_STRING ds_datadir;
586  DBUG_ENTER("get_upgrade_info_file_name");
587 
588  if (init_dynamic_string(&ds_datadir, NULL, 32, 32))
589  die("Out of memory");
590 
591  if (run_query("show variables like 'datadir'",
592  &ds_datadir, FALSE) ||
593  extract_variable_from_show(&ds_datadir, name))
594  {
595  dynstr_free(&ds_datadir);
596  DBUG_RETURN(1); /* Query failed */
597  }
598 
599  dynstr_free(&ds_datadir);
600 
601  fn_format(name, "mysql_upgrade_info", name, "", MYF(0));
602  DBUG_PRINT("exit", ("name: %s", name));
603  DBUG_RETURN(0);
604 }
605 
606 
607 /*
608  Read the content of mysql_upgrade_info file and
609  compare the version number form file against
610  version number wich mysql_upgrade was compiled for
611 
612  NOTE
613  This is an optimization to avoid running mysql_upgrade
614  when it's already been performed for the particular
615  version of MySQL.
616 
617  In case the MySQL server can't return the upgrade info
618  file it's always better to report that the upgrade hasn't
619  been performed.
620 
621 */
622 
623 static int upgrade_already_done(void)
624 {
625  FILE *in;
626  char upgrade_info_file[FN_REFLEN]= {0};
627  char buf[sizeof(MYSQL_SERVER_VERSION)+1];
628  char *res;
629 
630  if (get_upgrade_info_file_name(upgrade_info_file))
631  return 0; /* Could not get filename => not sure */
632 
633  if (!(in= my_fopen(upgrade_info_file, O_RDONLY, MYF(0))))
634  return 0; /* Could not open file => not sure */
635 
636  /*
637  Read from file, don't care if it fails since it
638  will be detected by the strncmp
639  */
640  memset(buf, 0, sizeof(buf));
641  res= fgets(buf, sizeof(buf), in);
642 
643  my_fclose(in, MYF(0));
644 
645  if (!res)
646  return 0; /* Could not read from file => not sure */
647 
648  return (strncmp(res, MYSQL_SERVER_VERSION,
649  sizeof(MYSQL_SERVER_VERSION)-1)==0);
650 }
651 
652 
653 /*
654  Write mysql_upgrade_info file in servers data dir indicating that
655  upgrade has been done for this version
656 
657  NOTE
658  This might very well fail but since it's just an optimization
659  to run mysql_upgrade only when necessary the error can be
660  ignored.
661 
662 */
663 
664 static void create_mysql_upgrade_info_file(void)
665 {
666  FILE *out;
667  char upgrade_info_file[FN_REFLEN]= {0};
668 
669  if (get_upgrade_info_file_name(upgrade_info_file))
670  return; /* Could not get filename => skip */
671 
672  if (!(out= my_fopen(upgrade_info_file, O_TRUNC | O_WRONLY, MYF(0))))
673  {
674  fprintf(stderr,
675  "Could not create the upgrade info file '%s' in "
676  "the MySQL Servers datadir, errno: %d\n",
677  upgrade_info_file, errno);
678  return;
679  }
680 
681  /* Write new version to file */
682  fputs(MYSQL_SERVER_VERSION, out);
683  my_fclose(out, MYF(0));
684 
685  /*
686  Check if the upgrad_info_file was properly created/updated
687  It's not a fatal error -> just print a message if it fails
688  */
689  if (!upgrade_already_done())
690  fprintf(stderr,
691  "Could not write to the upgrade info file '%s' in "
692  "the MySQL Servers datadir, errno: %d\n",
693  upgrade_info_file, errno);
694  return;
695 }
696 
697 
698 /*
699  Print connection-related arguments.
700 */
701 
702 static void print_conn_args(const char *tool_name)
703 {
704  if (conn_args.str[0])
705  verbose("Running '%s' with connection arguments: %s", tool_name,
706  conn_args.str);
707  else
708  verbose("Running '%s with default connection arguments", tool_name);
709 }
710 
711 
712 /*
713  Check and upgrade(if neccessary) all tables
714  in the server using "mysqlcheck --check-upgrade .."
715 */
716 
717 static int run_mysqlcheck_upgrade(void)
718 {
719  print_conn_args("mysqlcheck");
720  return run_tool(mysqlcheck_path,
721  NULL, /* Send output from mysqlcheck directly to screen */
722  "--no-defaults",
723  ds_args.str,
724  "--check-upgrade",
725  "--all-databases",
726  "--skip-database=mysql",
727  "--auto-repair",
728  opt_write_binlog ? "--write-binlog" : "--skip-write-binlog",
729  NULL);
730 }
731 
732 
733 static int run_mysqlcheck_fixnames(void)
734 {
735  print_conn_args("mysqlcheck");
736  return run_tool(mysqlcheck_path,
737  NULL, /* Send output from mysqlcheck directly to screen */
738  "--no-defaults",
739  ds_args.str,
740  "--all-databases",
741  "--skip-database=mysql",
742  "--fix-db-names",
743  "--fix-table-names",
744  opt_write_binlog ? "--write-binlog" : "--skip-write-binlog",
745  NULL);
746 }
747 
749 static int run_mysqlcheck_mysql_db_upgrade(void)
750 {
751  print_conn_args("mysqlcheck");
752  return run_tool(mysqlcheck_path,
753  NULL, /* Send output from mysqlcheck directly to screen */
754  "--no-defaults",
755  ds_args.str,
756  "--check-upgrade",
757  "--databases",
758  "--auto-repair",
759  opt_write_binlog ? "--write-binlog" : "--skip-write-binlog",
760  "mysql",
761  NULL);
762 }
763 
764 
766 static int run_mysqlcheck_mysql_db_fixnames(void)
767 {
768  print_conn_args("mysqlcheck");
769  return run_tool(mysqlcheck_path,
770  NULL, /* Send output from mysqlcheck directly to screen */
771  "--no-defaults",
772  ds_args.str,
773  "--databases",
774  "--fix-db-names",
775  "--fix-table-names",
776  opt_write_binlog ? "--write-binlog" : "--skip-write-binlog",
777  "mysql",
778  NULL);
779 }
780 static const char *expected_errors[]=
781 {
782  "ERROR 1060", /* Duplicate column name */
783  "ERROR 1061", /* Duplicate key name */
784  "ERROR 1054", /* Unknown column */
785  0
786 };
787 
788 
789 static my_bool is_expected_error(const char* line)
790 {
791  const char** error= expected_errors;
792  while (*error)
793  {
794  /*
795  Check if lines starting with ERROR
796  are in the list of expected errors
797  */
798  if (strncmp(line, "ERROR", 5) != 0 ||
799  strncmp(line, *error, strlen(*error)) == 0)
800  return 1; /* Found expected error */
801  error++;
802  }
803  return 0;
804 }
805 
806 
807 static char* get_line(char* line)
808 {
809  while (*line && *line != '\n')
810  line++;
811  if (*line)
812  line++;
813  return line;
814 }
815 
816 
817 /* Print the current line to stderr */
818 static void print_line(char* line)
819 {
820  while (*line && *line != '\n')
821  {
822  fputc(*line, stderr);
823  line++;
824  }
825  fputc('\n', stderr);
826 }
827 
828 
829 /*
830  Update all system tables in MySQL Server to current
831  version using "mysql" to execute all the SQL commands
832  compiled into the mysql_fix_privilege_tables array
833 */
834 
835 static int run_sql_fix_privilege_tables(void)
836 {
837  int found_real_errors= 0;
838  const char **query_ptr;
839  DYNAMIC_STRING ds_script;
840  DYNAMIC_STRING ds_result;
841  DBUG_ENTER("run_sql_fix_privilege_tables");
842 
843  if (init_dynamic_string(&ds_script, "", 65536, 1024))
844  die("Out of memory");
845 
846  if (init_dynamic_string(&ds_result, "", 512, 512))
847  die("Out of memory");
848 
849  verbose("Running 'mysql_fix_privilege_tables'...");
850 
851  /*
852  Individual queries can not be executed independently by invoking
853  a forked mysql client, because the script uses session variables
854  and prepared statements.
855  */
856  for ( query_ptr= &mysql_fix_privilege_tables[0];
857  *query_ptr != NULL;
858  query_ptr++
859  )
860  {
861  dynstr_append(&ds_script, *query_ptr);
862  }
863 
864  run_query(ds_script.str,
865  &ds_result, /* Collect result */
866  TRUE);
867  {
868  /*
869  Scan each line of the result for real errors
870  and ignore the expected one(s) like "Duplicate column name",
871  "Unknown column" and "Duplicate key name" since they just
872  indicate the system tables are already up to date
873  */
874  char *line= ds_result.str;
875  do
876  {
877  if (!is_expected_error(line))
878  {
879  /* Something unexpected failed, dump error line to screen */
880  found_real_errors++;
881  print_line(line);
882  }
883  else if ((strncmp(line, "WARNING", 7) == 0) ||
884  (strncmp(line, "Warning", 7) == 0))
885  {
886  print_line(line);
887  }
888  } while ((line= get_line(line)) && *line);
889  }
890 
891  dynstr_free(&ds_result);
892  dynstr_free(&ds_script);
893  DBUG_RETURN(found_real_errors);
894 }
895 
896 
897 static const char *load_default_groups[]=
898 {
899  "client", /* Read settings how to connect to server */
900  "mysql_upgrade", /* Read special settings for mysql_upgrade*/
901  0
902 };
903 
904 
905 /* Convert the specified version string into the numeric format. */
906 static ulong STDCALL calc_server_version(char *some_version)
907 {
908  uint major, minor, version;
909  char *point= some_version, *end_point;
910  major= (uint) strtoul(point, &end_point, 10); point=end_point+1;
911  minor= (uint) strtoul(point, &end_point, 10); point=end_point+1;
912  version= (uint) strtoul(point, &end_point, 10);
913  return (ulong) major * 10000L + (ulong)(minor * 100 + version);
914 }
915 
923 static int check_version_match(void)
924 {
925  DYNAMIC_STRING ds_version;
926  char version_str[NAME_CHAR_LEN + 1];
927 
928  if (init_dynamic_string(&ds_version, NULL, NAME_CHAR_LEN, NAME_CHAR_LEN))
929  die("Out of memory");
930 
931  if (run_query("show variables like 'version'",
932  &ds_version, FALSE) ||
933  extract_variable_from_show(&ds_version, version_str))
934  {
935  dynstr_free(&ds_version);
936  return 1; /* Query failed */
937  }
938 
939  dynstr_free(&ds_version);
940 
941  if (calc_server_version((char *) version_str) != MYSQL_VERSION_ID)
942  {
943  fprintf(stderr, "Error: Server version (%s) does not match with the "
944  "version of\nthe server (%s) with which this program was built/"
945  "distributed. You can\nuse --skip-version-check to skip this "
946  "check.\n", version_str, MYSQL_SERVER_VERSION);
947  return 1;
948  }
949  else
950  return 0;
951 }
952 
953 
954 int main(int argc, char **argv)
955 {
956  char self_name[FN_REFLEN];
957 
958  MY_INIT(argv[0]);
959 
960 #if __WIN__
961  if (GetModuleFileName(NULL, self_name, FN_REFLEN) == 0)
962 #endif
963  {
964  strncpy(self_name, argv[0], FN_REFLEN);
965  }
966 
967  if (init_dynamic_string(&ds_args, "", 512, 256) ||
968  init_dynamic_string(&conn_args, "", 512, 256))
969  die("Out of memory");
970 
971  my_getopt_use_args_separator= TRUE;
972  if (load_defaults("my", load_default_groups, &argc, &argv))
973  die(NULL);
974  my_getopt_use_args_separator= FALSE;
975  defaults_argv= argv; /* Must be freed by 'free_defaults' */
976 
977  if (handle_options(&argc, &argv, my_long_options, get_one_option))
978  die(NULL);
979  if (debug_info_flag)
980  my_end_arg= MY_CHECK_ERROR | MY_GIVE_INFO;
981  if (debug_check_flag)
982  my_end_arg= MY_CHECK_ERROR;
983 
984  if (tty_password)
985  {
986  opt_password= get_tty_password(NullS);
987  /* add password to defaults file */
988  dynstr_append_os_quoted(&ds_args, "--password=", opt_password, NullS);
989  dynstr_append(&ds_args, " ");
990  }
991  /* add user to defaults file */
992  dynstr_append_os_quoted(&ds_args, "--user=", opt_user, NullS);
993  dynstr_append(&ds_args, " ");
994 
995  /* Find mysql */
996  find_tool(mysql_path, IF_WIN("mysql.exe", "mysql"), self_name);
997 
998  if (!opt_systables_only)
999  {
1000  /* Find mysqlcheck */
1001  find_tool(mysqlcheck_path, IF_WIN("mysqlcheck.exe", "mysqlcheck"), self_name);
1002  }
1003  else
1004  {
1005  printf("The --upgrade-system-tables option was used, databases won't be touched.\n");
1006  }
1007 
1008  /*
1009  Read the mysql_upgrade_info file to check if mysql_upgrade
1010  already has been run for this installation of MySQL
1011  */
1012  if (!opt_force && upgrade_already_done())
1013  {
1014  printf("This installation of MySQL is already upgraded to %s, "
1015  "use --force if you still need to run mysql_upgrade\n",
1016  MYSQL_SERVER_VERSION);
1017  die(NULL);
1018  }
1019 
1020  if (opt_version_check && check_version_match())
1021  die("Upgrade failed");
1022 
1023  /*
1024  Run "mysqlcheck" and "mysql_fix_privilege_tables.sql"
1025  First run mysqlcheck on the system database.
1026  Then do the upgrade.
1027  And then run mysqlcheck on all tables.
1028  */
1029  if ((!opt_systables_only &&
1030  (run_mysqlcheck_mysql_db_fixnames() || run_mysqlcheck_mysql_db_upgrade())) ||
1031  run_sql_fix_privilege_tables() ||
1032  (!opt_systables_only &&
1033  (run_mysqlcheck_fixnames() || run_mysqlcheck_upgrade())))
1034  {
1035  /*
1036  The upgrade failed to complete in some way or another,
1037  significant error message should have been printed to the screen
1038  */
1039  die("Upgrade failed" );
1040  }
1041  verbose("OK");
1042 
1043  /* Create a file indicating upgrade has been performed */
1044  create_mysql_upgrade_info_file();
1045 
1046  free_used_memory();
1047  my_end(my_end_arg);
1048  exit(0);
1049 }
1050