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