1/*
2 Copyright (c) 2006, 2013, Oracle and/or its affiliates.
3 Copyright (c) 2010, 2017, MariaDB
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; version 2 of the License.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17*/
18
19#include "client_priv.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.4"
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
39static int phase = 0;
40static const int phases_total = 7;
41static char mysql_path[FN_REFLEN];
42static char mysqlcheck_path[FN_REFLEN];
43
44static my_bool opt_force, opt_verbose, debug_info_flag, debug_check_flag,
45 opt_systables_only, opt_version_check;
46static my_bool opt_not_used, opt_silent;
47static uint my_end_arg= 0;
48static char *opt_user= (char*)"root";
49
50static my_bool upgrade_from_mysql;
51
52static DYNAMIC_STRING ds_args;
53static DYNAMIC_STRING conn_args;
54
55static char *opt_password= 0;
56static char *opt_plugin_dir= 0, *opt_default_auth= 0;
57
58static char *cnf_file_path= 0, defaults_file[FN_REFLEN + 32];
59
60static my_bool tty_password= 0;
61
62static char opt_tmpdir[FN_REFLEN] = "";
63
64#ifndef DBUG_OFF
65static char *default_dbug_option= (char*) "d:t:O,/tmp/mysql_upgrade.trace";
66#endif
67
68static char **defaults_argv;
69
70static my_bool not_used; /* Can't use GET_BOOL without a value pointer */
71
72char upgrade_from_version[sizeof("10.20.456-MariaDB")+1];
73
74static my_bool opt_write_binlog;
75
76#define OPT_SILENT OPT_MAX_CLIENT_OPTION
77
78static struct my_option my_long_options[]=
79{
80 {"help", '?', "Display this help message and exit.", 0, 0, 0, GET_NO_ARG,
81 NO_ARG, 0, 0, 0, 0, 0, 0},
82 {"basedir", 'b',
83 "Not used by mysql_upgrade. Only for backward compatibility.",
84 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
85 {"character-sets-dir", OPT_CHARSETS_DIR,
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 {"compress", OPT_COMPRESS,
89 "Not used by mysql_upgrade. Only for backward compatibility.",
90 &not_used, &not_used, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
91 {"datadir", 'd',
92 "Not used by mysql_upgrade. Only for backward compatibility.",
93 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
94#ifdef DBUG_OFF
95 {"debug", '#', "This is a non-debug version. Catch this and exit.",
96 0, 0, 0, GET_DISABLED, OPT_ARG, 0, 0, 0, 0, 0, 0},
97#else
98 {"debug", '#', "Output debug log.", &default_dbug_option,
99 &default_dbug_option, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0},
100#endif
101 {"debug-check", OPT_DEBUG_CHECK, "Check memory and open file usage at exit.",
102 &debug_check_flag, &debug_check_flag,
103 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
104 {"debug-info", 'T', "Print some debug info at exit.", &debug_info_flag,
105 &debug_info_flag, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
106 {"default-character-set", OPT_DEFAULT_CHARSET,
107 "Not used by mysql_upgrade. Only for backward compatibility.",
108 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
109 {"default_auth", OPT_DEFAULT_AUTH,
110 "Default authentication client-side plugin to use.",
111 &opt_default_auth, &opt_default_auth, 0,
112 GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
113 {"force", 'f', "Force execution of mysqlcheck even if mysql_upgrade "
114 "has already been executed for the current version of MySQL.",
115 &opt_force, &opt_force, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
116 {"host", 'h', "Connect to host.", 0,
117 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
118#define PASSWORD_OPT 12
119 {"password", 'p',
120 "Password to use when connecting to server. If password is not given,"
121 " it's solicited on the tty.", &opt_password,&opt_password,
122 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0},
123#ifdef __WIN__
124 {"pipe", 'W', "Use named pipes to connect to server.", 0, 0, 0,
125 GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
126#endif
127 {"plugin_dir", OPT_PLUGIN_DIR, "Directory for client-side plugins.",
128 &opt_plugin_dir, &opt_plugin_dir, 0,
129 GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
130 {"port", 'P', "Port number to use for connection or 0 for default to, in "
131 "order of preference, my.cnf, $MYSQL_TCP_PORT, "
132#if MYSQL_PORT_DEFAULT == 0
133 "/etc/services, "
134#endif
135 "built-in default (" STRINGIFY_ARG(MYSQL_PORT) ").",
136 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
137 {"protocol", OPT_MYSQL_PROTOCOL,
138 "The protocol to use for connection (tcp, socket, pipe, memory).",
139 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
140#ifdef HAVE_SMEM
141 {"shared-memory-base-name", OPT_SHARED_MEMORY_BASE_NAME,
142 "Base name of shared memory.", 0,
143 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
144#endif
145 {"silent", OPT_SILENT, "Print less information", &opt_silent,
146 &opt_silent, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
147 {"socket", 'S', "The socket file to use for connection.",
148 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
149#include <sslopt-longopts.h>
150 {"tmpdir", 't', "Directory for temporary files.",
151 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
152 {"upgrade-system-tables", 's', "Only upgrade the system tables in the mysql database. Tables in other databases are not checked or touched.",
153 &opt_systables_only, &opt_systables_only, 0,
154 GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
155#define USER_OPT (array_elements(my_long_options) - 6)
156 {"user", 'u', "User for login if not current user.", &opt_user,
157 &opt_user, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
158 {"verbose", 'v', "Display more output about the process; Using it twice will print connection argument; Using it 3 times will print out all CHECK, RENAME and ALTER TABLE during the check phase.",
159 &opt_not_used, &opt_not_used, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0},
160 {"version", 'V', "Output version information and exit.", 0, 0, 0,
161 GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
162 {"version-check", 'k',
163 "Run this program only if its \'server version\' "
164 "matches the version of the server to which it's connecting. "
165 "Note: the \'server version\' of the program is the version of the MariaDB "
166 "server with which it was built/distributed.",
167 &opt_version_check, &opt_version_check, 0,
168 GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0},
169 {"write-binlog", OPT_WRITE_BINLOG, "All commands including those "
170 "issued by mysqlcheck are written to the binary log.",
171 &opt_write_binlog, &opt_write_binlog, 0, GET_BOOL, NO_ARG,
172 0, 0, 0, 0, 0, 0},
173 {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
174};
175
176
177static const char *load_default_groups[]=
178{
179 "client", /* Read settings how to connect to server */
180 "mysql_upgrade", /* Read special settings for mysql_upgrade */
181 "client-server", /* Reads settings common between client & server */
182 "client-mariadb", /* Read mariadb unique client settings */
183 0
184};
185
186static void free_used_memory(void)
187{
188 /* Free memory allocated by 'load_defaults' */
189 if (defaults_argv)
190 free_defaults(defaults_argv);
191
192 dynstr_free(&ds_args);
193 dynstr_free(&conn_args);
194 if (cnf_file_path)
195 my_delete(cnf_file_path, MYF(MY_WME));
196}
197
198
199static void die(const char *fmt, ...)
200{
201 va_list args;
202 DBUG_ENTER("die");
203
204 /* Print the error message */
205 fflush(stdout);
206 va_start(args, fmt);
207 if (fmt)
208 {
209 fprintf(stderr, "FATAL ERROR: ");
210 vfprintf(stderr, fmt, args);
211 fprintf(stderr, "\n");
212 fflush(stderr);
213 }
214 va_end(args);
215
216 free_used_memory();
217 my_end(my_end_arg);
218 exit(1);
219}
220
221
222static void verbose(const char *fmt, ...)
223{
224 va_list args;
225
226 if (opt_silent)
227 return;
228
229 /* Print the verbose message */
230 va_start(args, fmt);
231 if (fmt)
232 {
233 vfprintf(stdout, fmt, args);
234 fprintf(stdout, "\n");
235 fflush(stdout);
236 }
237 va_end(args);
238}
239
240
241/*
242 Add one option - passed to mysql_upgrade on command line
243 or by defaults file(my.cnf) - to a dynamic string, in
244 this way we pass the same arguments on to mysql and mysql_check
245*/
246
247static void add_one_option_cmd_line(DYNAMIC_STRING *ds,
248 const struct my_option *opt,
249 const char* arg)
250{
251 dynstr_append(ds, "--");
252 dynstr_append(ds, opt->name);
253 if (arg)
254 {
255 dynstr_append(ds, "=");
256 dynstr_append_os_quoted(ds, arg, NullS);
257 }
258 dynstr_append(ds, " ");
259}
260
261static void add_one_option_cnf_file(DYNAMIC_STRING *ds,
262 const struct my_option *opt,
263 const char* arg)
264{
265 dynstr_append(ds, opt->name);
266 if (arg)
267 {
268 dynstr_append(ds, "=");
269 dynstr_append_os_quoted(ds, arg, NullS);
270 }
271 dynstr_append(ds, "\n");
272}
273
274static my_bool
275get_one_option(int optid, const struct my_option *opt,
276 char *argument)
277{
278 my_bool add_option= TRUE;
279
280 switch (optid) {
281
282 case '?':
283 printf("%s Ver %s Distrib %s, for %s (%s)\n",
284 my_progname, VER, MYSQL_SERVER_VERSION, SYSTEM_TYPE, MACHINE_TYPE);
285 puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2000"));
286 puts("MariaDB utility for upgrading databases to new MariaDB versions.");
287 print_defaults("my", load_default_groups);
288 puts("");
289 my_print_help(my_long_options);
290 my_print_variables(my_long_options);
291 die(0);
292 break;
293
294 case '#':
295 DBUG_PUSH(argument ? argument : default_dbug_option);
296 add_option= FALSE;
297 debug_check_flag= 1;
298 break;
299
300 case 'p':
301 if (argument == disabled_my_option)
302 argument= (char*) ""; /* Don't require password */
303 add_option= FALSE;
304 if (argument)
305 {
306 /* Add password to ds_args before overwriting the arg with x's */
307 add_one_option_cnf_file(&ds_args, opt, argument);
308 while (*argument)
309 *argument++= 'x'; /* Destroy argument */
310 tty_password= 0;
311 }
312 else
313 tty_password= 1;
314 break;
315
316 case 't':
317 strnmov(opt_tmpdir, argument, sizeof(opt_tmpdir));
318 add_option= FALSE;
319 break;
320
321 case 'b': /* --basedir */
322 case 'd': /* --datadir */
323 fprintf(stderr, "%s: the '--%s' option is always ignored\n",
324 my_progname, optid == 'b' ? "basedir" : "datadir");
325 /* FALLTHROUGH */
326
327 case 'k': /* --version-check */
328 case 'v': /* --verbose */
329 opt_verbose++;
330 if (argument == disabled_my_option)
331 {
332 opt_verbose= 0;
333 opt_silent= 1;
334 }
335 add_option= 0;
336 break;
337 case 'V':
338 printf("%s Ver %s Distrib %s, for %s (%s)\n",
339 my_progname, VER, MYSQL_SERVER_VERSION, SYSTEM_TYPE, MACHINE_TYPE);
340 die(0);
341 break;
342 case OPT_SILENT:
343 opt_verbose= 0;
344 add_option= 0;
345 break;
346 case 'f': /* --force */
347 case 's': /* --upgrade-system-tables */
348 case OPT_WRITE_BINLOG: /* --write-binlog */
349 add_option= FALSE;
350 break;
351
352 case 'h': /* --host */
353 case 'W': /* --pipe */
354 case 'P': /* --port */
355 case 'S': /* --socket */
356 case OPT_MYSQL_PROTOCOL: /* --protocol */
357 case OPT_SHARED_MEMORY_BASE_NAME: /* --shared-memory-base-name */
358 case OPT_PLUGIN_DIR: /* --plugin-dir */
359 case OPT_DEFAULT_AUTH: /* --default-auth */
360 add_one_option_cmd_line(&conn_args, opt, argument);
361 break;
362 }
363
364 if (add_option)
365 {
366 /*
367 This is an option that is accepted by mysql_upgrade just so
368 it can be passed on to "mysql" and "mysqlcheck"
369 Save it in the ds_args string
370 */
371 add_one_option_cnf_file(&ds_args, opt, argument);
372 }
373 return 0;
374}
375
376
377/**
378 Run a command using the shell, storing its output in the supplied dynamic
379 string.
380*/
381static int run_command(char* cmd,
382 DYNAMIC_STRING *ds_res)
383{
384 char buf[512]= {0};
385 FILE *res_file;
386 int error;
387
388 if (opt_verbose >= 4)
389 puts(cmd);
390
391 if (!(res_file= popen(cmd, "r")))
392 die("popen(\"%s\", \"r\") failed", cmd);
393
394 while (fgets(buf, sizeof(buf), res_file))
395 {
396 DBUG_PRINT("info", ("buf: %s", buf));
397 if(ds_res)
398 {
399 /* Save the output of this command in the supplied string */
400 dynstr_append(ds_res, buf);
401 }
402 else
403 {
404 /* Print it directly on screen */
405 fprintf(stdout, "%s", buf);
406 }
407 }
408
409 error= pclose(res_file);
410 return WEXITSTATUS(error);
411}
412
413
414static int run_tool(char *tool_path, DYNAMIC_STRING *ds_res, ...)
415{
416 int ret;
417 const char* arg;
418 va_list args;
419 DYNAMIC_STRING ds_cmdline;
420
421 DBUG_ENTER("run_tool");
422 DBUG_PRINT("enter", ("tool_path: %s", tool_path));
423
424 if (init_dynamic_string(&ds_cmdline, IF_WIN("\"", ""), FN_REFLEN, FN_REFLEN))
425 die("Out of memory");
426
427 dynstr_append_os_quoted(&ds_cmdline, tool_path, NullS);
428 dynstr_append(&ds_cmdline, " ");
429
430 va_start(args, ds_res);
431
432 while ((arg= va_arg(args, char *)))
433 {
434 /* Options should already be os quoted */
435 dynstr_append(&ds_cmdline, arg);
436 dynstr_append(&ds_cmdline, " ");
437 }
438
439 va_end(args);
440
441#ifdef __WIN__
442 dynstr_append(&ds_cmdline, "\"");
443#endif
444
445 DBUG_PRINT("info", ("Running: %s", ds_cmdline.str));
446 ret= run_command(ds_cmdline.str, ds_res);
447 DBUG_PRINT("exit", ("ret: %d", ret));
448 dynstr_free(&ds_cmdline);
449 DBUG_RETURN(ret);
450}
451
452
453/**
454 Look for the filename of given tool, with the presumption that it is in the
455 same directory as mysql_upgrade and that the same executable-searching
456 mechanism will be used when we run our sub-shells with popen() later.
457*/
458static void find_tool(char *tool_executable_name, const char *tool_name,
459 const char *self_name)
460{
461 char *last_fn_libchar;
462 DYNAMIC_STRING ds_tmp;
463 DBUG_ENTER("find_tool");
464 DBUG_PRINT("enter", ("progname: %s", my_progname));
465
466 if (init_dynamic_string(&ds_tmp, "", 32, 32))
467 die("Out of memory");
468
469 last_fn_libchar= strrchr(self_name, FN_LIBCHAR);
470
471 if (last_fn_libchar == NULL)
472 {
473 /*
474 mysql_upgrade was found by the shell searching the path. A sibling
475 next to us should be found the same way.
476 */
477 strncpy(tool_executable_name, tool_name, FN_REFLEN);
478 }
479 else
480 {
481 int len;
482
483 /*
484 mysql_upgrade was run absolutely or relatively. We can find a sibling
485 by replacing our name after the LIBCHAR with the new tool name.
486 */
487
488 /*
489 When running in a not yet installed build and using libtool,
490 the program(mysql_upgrade) will be in .libs/ and executed
491 through a libtool wrapper in order to use the dynamic libraries
492 from this build. The same must be done for the tools(mysql and
493 mysqlcheck). Thus if path ends in .libs/, step up one directory
494 and execute the tools from there
495 */
496 if (((last_fn_libchar - 6) >= self_name) &&
497 (strncmp(last_fn_libchar - 5, ".libs", 5) == 0) &&
498 (*(last_fn_libchar - 6) == FN_LIBCHAR))
499 {
500 DBUG_PRINT("info", ("Chopping off \".libs\" from end of path"));
501 last_fn_libchar -= 6;
502 }
503
504 len= (int)(last_fn_libchar - self_name);
505
506 my_snprintf(tool_executable_name, FN_REFLEN, "%.*s%c%s",
507 len, self_name, FN_LIBCHAR, tool_name);
508 }
509
510 if (opt_verbose)
511 verbose("Looking for '%s' as: %s", tool_name, tool_executable_name);
512
513 /*
514 Make sure it can be executed
515 */
516 if (run_tool(tool_executable_name,
517 &ds_tmp, /* Get output from command, discard*/
518 "--no-defaults",
519 "--help",
520 "2>&1",
521 IF_WIN("> NUL", "> /dev/null"),
522 NULL))
523 die("Can't execute '%s'", tool_executable_name);
524
525 dynstr_free(&ds_tmp);
526
527 DBUG_VOID_RETURN;
528}
529
530
531/*
532 Run query using "mysql"
533*/
534
535static int run_query(const char *query, DYNAMIC_STRING *ds_res,
536 my_bool force)
537{
538 int ret;
539 File fd;
540 char query_file_path[FN_REFLEN];
541#ifdef WITH_WSREP
542 /*
543 Strictly speaking, WITH_WSREP on the client only means that the
544 client was compiled with WSREP, it doesn't mean the server was,
545 so the server might not have WSREP_ON variable.
546
547 But mysql_upgrade is tightly bound to a specific server version
548 anyway - it was mysql_fix_privilege_tables_sql script embedded
549 into its binary - so even if it won't assume anything about server
550 wsrep-ness, it won't be any less server-dependent.
551 */
552 const uchar sql_log_bin[]= "SET SQL_LOG_BIN=0, WSREP_ON=OFF;";
553#else
554 const uchar sql_log_bin[]= "SET SQL_LOG_BIN=0;";
555#endif /* WITH_WSREP */
556
557 DBUG_ENTER("run_query");
558 DBUG_PRINT("enter", ("query: %s", query));
559 if ((fd= create_temp_file(query_file_path,
560 opt_tmpdir[0] ? opt_tmpdir : NULL,
561 "sql", O_SHARE, MYF(MY_WME))) < 0)
562 die("Failed to create temporary file for defaults");
563
564 /*
565 Master and slave should be upgraded separately. All statements executed
566 by mysql_upgrade will not be binlogged.
567 'SET SQL_LOG_BIN=0' is executed before any other statements.
568 */
569 if (!opt_write_binlog)
570 {
571 if (my_write(fd, sql_log_bin, sizeof(sql_log_bin)-1,
572 MYF(MY_FNABP | MY_WME)))
573 {
574 my_close(fd, MYF(0));
575 my_delete(query_file_path, MYF(0));
576 die("Failed to write to '%s'", query_file_path);
577 }
578 }
579
580 if (my_write(fd, (uchar*) query, strlen(query),
581 MYF(MY_FNABP | MY_WME)))
582 {
583 my_close(fd, MYF(0));
584 my_delete(query_file_path, MYF(0));
585 die("Failed to write to '%s'", query_file_path);
586 }
587
588 ret= run_tool(mysql_path,
589 ds_res,
590 defaults_file,
591 "--database=mysql",
592 "--batch", /* Turns off pager etc. */
593 force ? "--force": "--skip-force",
594 ds_res || opt_silent ? "--silent": "",
595 "<",
596 query_file_path,
597 "2>&1",
598 NULL);
599
600 my_close(fd, MYF(0));
601 my_delete(query_file_path, MYF(0));
602
603 DBUG_RETURN(ret);
604}
605
606
607/*
608 Extract the value returned from result of "show variable like ..."
609*/
610
611static 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
635static 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 which 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
675static int upgrade_already_done(myf flags)
676{
677 FILE *in;
678 char upgrade_info_file[FN_REFLEN]= {0};
679
680 if (get_upgrade_info_file_name(upgrade_info_file))
681 return 0; /* Could not get filename => not sure */
682
683 if (!(in= my_fopen(upgrade_info_file, O_RDONLY, flags)))
684 return 0; /* Could not open file => not sure */
685
686 bzero(upgrade_from_version, sizeof(upgrade_from_version));
687 if (!fgets(upgrade_from_version, sizeof(upgrade_from_version), in))
688 {
689 /* Preserve errno for caller */
690 int save_errno= errno;
691 (void) my_fclose(in, flags);
692 errno= save_errno;
693 return 0;
694 }
695
696 if (my_fclose(in, flags))
697 return 0;
698
699 errno= 0;
700 return (strncmp(upgrade_from_version, 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
716static 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 MariaDB Servers datadir, errno: %d\n",
729 upgrade_info_file, errno);
730 return;
731 }
732
733 /* Write new version to file */
734 my_fwrite(out, (uchar*) MYSQL_SERVER_VERSION,
735 sizeof(MYSQL_SERVER_VERSION), MY_WME);
736 my_fclose(out, MYF(MY_WME));
737
738 /*
739 Check if the upgrad_info_file was properly created/updated
740 It's not a fatal error -> just print a message if it fails
741 */
742 if (!upgrade_already_done(MY_WME))
743 fprintf(stderr,
744 "Upgrade file '%s' was not properly created. "
745 "Got error errno while checking file content: %d\n",
746 upgrade_info_file, errno);
747 return;
748}
749
750
751/*
752 Print connection-related arguments.
753*/
754
755static void print_conn_args(const char *tool_name)
756{
757 if (opt_verbose < 2)
758 return;
759 if (conn_args.str[0])
760 verbose("Running '%s' with connection arguments: %s", tool_name,
761 conn_args.str);
762 else
763 verbose("Running '%s with default connection arguments", tool_name);
764}
765
766/*
767 Check and upgrade(if necessary) all tables
768 in the server using "mysqlcheck --check-upgrade .."
769*/
770
771static int run_mysqlcheck_upgrade(my_bool mysql_db_only)
772{
773 const char *what= mysql_db_only ? "mysql database" : "tables";
774 const char *arg1= mysql_db_only ? "--databases" : "--all-databases";
775 const char *arg2= mysql_db_only ? "mysql" : "--skip-database=mysql";
776 int retch;
777 if (opt_systables_only && !mysql_db_only)
778 {
779 verbose("Phase %d/%d: Checking and upgrading %s... Skipped",
780 ++phase, phases_total, what);
781 return 0;
782 }
783 verbose("Phase %d/%d: Checking and upgrading %s", ++phase, phases_total, what);
784 print_conn_args("mysqlcheck");
785 retch= run_tool(mysqlcheck_path,
786 NULL, /* Send output from mysqlcheck directly to screen */
787 defaults_file,
788 "--check-upgrade",
789 "--auto-repair",
790 !opt_silent || opt_verbose >= 1 ? "--verbose" : "",
791 opt_verbose >= 2 ? "--verbose" : "",
792 opt_verbose >= 3 ? "--verbose" : "",
793 opt_silent ? "--silent": "",
794 opt_write_binlog ? "--write-binlog" : "--skip-write-binlog",
795 arg1, arg2,
796 "2>&1",
797 NULL);
798 return retch;
799}
800
801#define EVENTS_STRUCT_LEN 7000
802
803static my_bool is_mysql()
804{
805 my_bool ret= TRUE;
806 DYNAMIC_STRING ds_events_struct;
807
808 if (init_dynamic_string(&ds_events_struct, NULL,
809 EVENTS_STRUCT_LEN, EVENTS_STRUCT_LEN))
810 die("Out of memory");
811
812 if (run_query("show create table mysql.event",
813 &ds_events_struct, FALSE) ||
814 strstr(ds_events_struct.str, "IGNORE_BAD_TABLE_OPTIONS") != NULL)
815 ret= FALSE;
816 else
817 verbose("MySQL upgrade detected");
818
819 dynstr_free(&ds_events_struct);
820 return(ret);
821}
822
823static int run_mysqlcheck_views(void)
824{
825 const char *upgrade_views="--process-views=YES";
826 if (upgrade_from_mysql)
827 {
828 /*
829 this has to ignore opt_systables_only, because upgrade_from_mysql
830 is determined by analyzing systables. if we honor opt_systables_only
831 here, views won't be fixed by subsequent mysql_upgrade runs
832 */
833 upgrade_views="--process-views=UPGRADE_FROM_MYSQL";
834 verbose("Phase %d/%d: Fixing views from mysql", ++phase, phases_total);
835 }
836 else if (opt_systables_only)
837 {
838 verbose("Phase %d/%d: Fixing views... Skipped", ++phase, phases_total);
839 return 0;
840 }
841 else
842 verbose("Phase %d/%d: Fixing views", ++phase, phases_total);
843
844 print_conn_args("mysqlcheck");
845 return run_tool(mysqlcheck_path,
846 NULL, /* Send output from mysqlcheck directly to screen */
847 defaults_file,
848 "--all-databases", "--repair",
849 upgrade_views,
850 "--skip-process-tables",
851 opt_verbose ? "--verbose": "",
852 opt_silent ? "--silent": "",
853 opt_write_binlog ? "--write-binlog" : "--skip-write-binlog",
854 "2>&1",
855 NULL);
856}
857
858static int run_mysqlcheck_fixnames(void)
859{
860 if (opt_systables_only)
861 {
862 verbose("Phase %d/%d: Fixing table and database names ... Skipped",
863 ++phase, phases_total);
864 return 0;
865 }
866 verbose("Phase %d/%d: Fixing table and database names",
867 ++phase, phases_total);
868 print_conn_args("mysqlcheck");
869 return run_tool(mysqlcheck_path,
870 NULL, /* Send output from mysqlcheck directly to screen */
871 defaults_file,
872 "--all-databases",
873 "--fix-db-names",
874 "--fix-table-names",
875 opt_verbose >= 1 ? "--verbose" : "",
876 opt_verbose >= 2 ? "--verbose" : "",
877 opt_verbose >= 3 ? "--verbose" : "",
878 opt_silent ? "--silent": "",
879 opt_write_binlog ? "--write-binlog" : "--skip-write-binlog",
880 "2>&1",
881 NULL);
882}
883
884
885static const char *expected_errors[]=
886{
887 "ERROR 1060", /* Duplicate column name */
888 "ERROR 1061", /* Duplicate key name */
889 "ERROR 1054", /* Unknown column */
890 "ERROR 1290", /* RR_OPTION_PREVENTS_STATEMENT */
891 0
892};
893
894
895static my_bool is_expected_error(const char* line)
896{
897 const char** error= expected_errors;
898 while (*error)
899 {
900 /*
901 Check if lines starting with ERROR
902 are in the list of expected errors
903 */
904 if (strncmp(line, "ERROR", 5) != 0 ||
905 strncmp(line, *error, strlen(*error)) == 0)
906 return 1; /* Found expected error */
907 error++;
908 }
909 return 0;
910}
911
912
913static char* get_line(char* line)
914{
915 while (*line && *line != '\n')
916 line++;
917 if (*line)
918 line++;
919 return line;
920}
921
922
923/* Print the current line to stderr */
924static void print_line(char* line)
925{
926 while (*line && *line != '\n')
927 {
928 fputc(*line, stderr);
929 line++;
930 }
931 fputc('\n', stderr);
932}
933
934static my_bool from_before_10_1()
935{
936 my_bool ret= TRUE;
937 DYNAMIC_STRING ds_events_struct;
938
939 if (upgrade_from_version[0])
940 {
941 return upgrade_from_version[1] == '.' ||
942 strncmp(upgrade_from_version, "10.1.", 5) < 0;
943 }
944
945 if (init_dynamic_string(&ds_events_struct, NULL, 2048, 2048))
946 die("Out of memory");
947
948 if (run_query("show create table mysql.user", &ds_events_struct, FALSE) ||
949 strstr(ds_events_struct.str, "default_role") != NULL)
950 ret= FALSE;
951 else
952 verbose("Upgrading from a version before MariaDB-10.1");
953
954 dynstr_free(&ds_events_struct);
955 return ret;
956}
957
958
959/*
960 Check for entries with "Unknown storage engine" in I_S.TABLES,
961 try to load plugins for these tables if available (MDEV-11942)
962*/
963static int install_used_engines(void)
964{
965 char buf[512];
966 DYNAMIC_STRING ds_result;
967 const char *query = "SELECT DISTINCT LOWER(engine) AS c1 FROM information_schema.tables"
968 " WHERE table_comment LIKE 'Unknown storage engine%'"
969 " ORDER BY c1";
970
971 if (opt_systables_only || !from_before_10_1())
972 {
973 verbose("Phase %d/%d: Installing used storage engines... Skipped", ++phase, phases_total);
974 return 0;
975 }
976 verbose("Phase %d/%d: Installing used storage engines", ++phase, phases_total);
977
978 if (init_dynamic_string(&ds_result, "", 512, 512))
979 die("Out of memory");
980
981 verbose("Checking for tables with unknown storage engine");
982
983 run_query(query, &ds_result, TRUE);
984
985 if (ds_result.length)
986 {
987 char *line= ds_result.str, *next=get_line(line);
988 do
989 {
990 if (next[-1] == '\n')
991 next[-1]=0;
992
993 verbose("installing plugin for '%s' storage engine", line);
994
995 // we simply assume soname=ha_enginename
996 strxnmov(buf, sizeof(buf)-1, "install soname 'ha_", line, "'", NULL);
997
998
999 if (run_query(buf, NULL, TRUE))
1000 fprintf(stderr, "... can't %s\n", buf);
1001 line=next;
1002 next=get_line(line);
1003 } while (*line);
1004 }
1005 dynstr_free(&ds_result);
1006 return 0;
1007}
1008
1009
1010/*
1011 Update all system tables in MySQL Server to current
1012 version using "mysql" to execute all the SQL commands
1013 compiled into the mysql_fix_privilege_tables array
1014*/
1015
1016static int run_sql_fix_privilege_tables(void)
1017{
1018 int found_real_errors= 0;
1019 const char **query_ptr;
1020 DYNAMIC_STRING ds_script;
1021 DYNAMIC_STRING ds_result;
1022 DBUG_ENTER("run_sql_fix_privilege_tables");
1023
1024 if (init_dynamic_string(&ds_script, "", 65536, 1024))
1025 die("Out of memory");
1026
1027 if (init_dynamic_string(&ds_result, "", 512, 512))
1028 die("Out of memory");
1029
1030 verbose("Phase %d/%d: Running 'mysql_fix_privilege_tables'",
1031 ++phase, phases_total);
1032
1033 /*
1034 Individual queries can not be executed independently by invoking
1035 a forked mysql client, because the script uses session variables
1036 and prepared statements.
1037 */
1038 for ( query_ptr= &mysql_fix_privilege_tables[0];
1039 *query_ptr != NULL;
1040 query_ptr++
1041 )
1042 {
1043 if (strcasecmp(*query_ptr, "flush privileges;\n"))
1044 dynstr_append(&ds_script, *query_ptr);
1045 }
1046
1047 run_query(ds_script.str,
1048 &ds_result, /* Collect result */
1049 TRUE);
1050
1051 {
1052 /*
1053 Scan each line of the result for real errors
1054 and ignore the expected one(s) like "Duplicate column name",
1055 "Unknown column" and "Duplicate key name" since they just
1056 indicate the system tables are already up to date
1057 */
1058 char *line= ds_result.str;
1059 do
1060 {
1061 if (!is_expected_error(line))
1062 {
1063 /* Something unexpected failed, dump error line to screen */
1064 found_real_errors++;
1065 print_line(line);
1066 }
1067 else if (strncmp(line, "WARNING", 7) == 0)
1068 {
1069 print_line(line);
1070 }
1071 } while ((line= get_line(line)) && *line);
1072 }
1073
1074 dynstr_free(&ds_result);
1075 dynstr_free(&ds_script);
1076 DBUG_RETURN(found_real_errors);
1077}
1078
1079
1080static void print_error(const char *error_msg, DYNAMIC_STRING *output)
1081{
1082 fprintf(stderr, "%s\n", error_msg);
1083 fprintf(stderr, "%s", output->str);
1084}
1085
1086
1087/* Convert the specified version string into the numeric format. */
1088static ulong STDCALL calc_server_version(char *some_version)
1089{
1090 uint major, minor, version;
1091 char *point= some_version, *end_point;
1092 major= (uint) strtoul(point, &end_point, 10); point=end_point+1;
1093 minor= (uint) strtoul(point, &end_point, 10); point=end_point+1;
1094 version= (uint) strtoul(point, &end_point, 10);
1095 return (ulong) major * 10000L + (ulong)(minor * 100 + version);
1096}
1097
1098/**
1099 Check if the server version matches with the server version mysql_upgrade
1100 was compiled with.
1101
1102 @return 0 match successful
1103 1 failed
1104*/
1105static int check_version_match(void)
1106{
1107 DYNAMIC_STRING ds_version;
1108 char version_str[NAME_CHAR_LEN + 1];
1109
1110 if (init_dynamic_string(&ds_version, NULL, NAME_CHAR_LEN, NAME_CHAR_LEN))
1111 die("Out of memory");
1112
1113 if (run_query("show variables like 'version'",
1114 &ds_version, FALSE) ||
1115 extract_variable_from_show(&ds_version, version_str))
1116 {
1117 print_error("Version check failed. Got the following error when calling "
1118 "the 'mysql' command line client", &ds_version);
1119 dynstr_free(&ds_version);
1120 return 1; /* Query failed */
1121 }
1122
1123 dynstr_free(&ds_version);
1124
1125 if (calc_server_version((char *) version_str) != MYSQL_VERSION_ID)
1126 {
1127 fprintf(stderr, "Error: Server version (%s) does not match with the "
1128 "version of\nthe server (%s) with which this program was built/"
1129 "distributed. You can\nuse --skip-version-check to skip this "
1130 "check.\n", version_str, MYSQL_SERVER_VERSION);
1131 return 1;
1132 }
1133 else
1134 return 0;
1135}
1136
1137
1138int main(int argc, char **argv)
1139{
1140 char self_name[FN_REFLEN + 1];
1141
1142 MY_INIT(argv[0]);
1143 load_defaults_or_exit("my", load_default_groups, &argc, &argv);
1144 defaults_argv= argv; /* Must be freed by 'free_defaults' */
1145
1146#if defined(__WIN__)
1147 if (GetModuleFileName(NULL, self_name, FN_REFLEN) == 0)
1148#endif
1149 {
1150 strmake_buf(self_name, argv[0]);
1151 }
1152
1153 if (init_dynamic_string(&ds_args, "", 512, 256) ||
1154 init_dynamic_string(&conn_args, "", 512, 256))
1155 die("Out of memory");
1156
1157 if (handle_options(&argc, &argv, my_long_options, get_one_option))
1158 die(NULL);
1159 if (debug_info_flag)
1160 my_end_arg= MY_CHECK_ERROR | MY_GIVE_INFO;
1161 if (debug_check_flag)
1162 my_end_arg= MY_CHECK_ERROR;
1163
1164 if (tty_password)
1165 {
1166 opt_password= get_tty_password(NullS);
1167 /* add password to defaults file */
1168 add_one_option_cnf_file(&ds_args, &my_long_options[PASSWORD_OPT], opt_password);
1169 DBUG_ASSERT(strcmp(my_long_options[PASSWORD_OPT].name, "password") == 0);
1170 }
1171 /* add user to defaults file */
1172 add_one_option_cnf_file(&ds_args, &my_long_options[USER_OPT], opt_user);
1173 DBUG_ASSERT(strcmp(my_long_options[USER_OPT].name, "user") == 0);
1174
1175 cnf_file_path= strmov(defaults_file, "--defaults-file=");
1176 {
1177 int fd= create_temp_file(cnf_file_path, opt_tmpdir[0] ? opt_tmpdir : NULL,
1178 "mysql_upgrade-", 0, MYF(MY_FAE));
1179 if (fd < 0)
1180 die(NULL);
1181 my_write(fd, USTRING_WITH_LEN( "[client]\n"), MYF(MY_FAE));
1182 my_write(fd, (uchar*)ds_args.str, ds_args.length, MYF(MY_FAE));
1183 my_close(fd, MYF(0));
1184 }
1185
1186 /* Find mysql */
1187 find_tool(mysql_path, IF_WIN("mysql.exe", "mysql"), self_name);
1188
1189 /* Find mysqlcheck */
1190 find_tool(mysqlcheck_path, IF_WIN("mysqlcheck.exe", "mysqlcheck"), self_name);
1191
1192 if (opt_systables_only && !opt_silent)
1193 printf("The --upgrade-system-tables option was used, user tables won't be touched.\n");
1194
1195 /*
1196 Read the mysql_upgrade_info file to check if mysql_upgrade
1197 already has been run for this installation of MySQL
1198 */
1199 if (!opt_force && upgrade_already_done(0))
1200 {
1201 printf("This installation of MySQL is already upgraded to %s, "
1202 "use --force if you still need to run mysql_upgrade\n",
1203 MYSQL_SERVER_VERSION);
1204 goto end;
1205 }
1206
1207 if (opt_version_check && check_version_match())
1208 die("Upgrade failed");
1209
1210 upgrade_from_mysql= is_mysql();
1211
1212 /*
1213 Run "mysqlcheck" and "mysql_fix_privilege_tables.sql"
1214 */
1215 if (run_mysqlcheck_upgrade(TRUE) ||
1216 install_used_engines() ||
1217 run_mysqlcheck_views() ||
1218 run_sql_fix_privilege_tables() ||
1219 run_mysqlcheck_fixnames() ||
1220 run_mysqlcheck_upgrade(FALSE))
1221 die("Upgrade failed" );
1222
1223 verbose("Phase %d/%d: Running 'FLUSH PRIVILEGES'", ++phase, phases_total);
1224 if (run_query("FLUSH PRIVILEGES", NULL, TRUE))
1225 die("Upgrade failed" );
1226
1227 verbose("OK");
1228
1229 /* Create a file indicating upgrade has been performed */
1230 create_mysql_upgrade_info_file();
1231
1232 DBUG_ASSERT(phase == phases_total);
1233
1234end:
1235 free_used_memory();
1236 my_end(my_end_arg);
1237 exit(0);
1238}
1239