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 ¬_used, ¬_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