1
/* Copyright (C) 2000 MySQL AB
3
This program is free software; you can redistribute it and/or modify
4
it under the terms of the GNU General Public License as published by
5
the Free Software Foundation; version 2 of the License.
7
This program is distributed in the hope that it will be useful,
8
but WITHOUT ANY WARRANTY; without even the implied warranty of
9
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
GNU General Public License for more details.
12
You should have received a copy of the GNU General Public License
13
along with this program; if not, write to the Free Software
14
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
16
#include "client_priv.h"
17
#include "../scripts/mysql_fix_privilege_tables_sql.c"
21
#ifdef HAVE_SYS_WAIT_H
27
# define WEXITSTATUS(stat_val) (stat_val)
29
# define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
33
static char mysql_path[FN_REFLEN];
34
static char mysqlcheck_path[FN_REFLEN];
36
static my_bool opt_force, opt_verbose, debug_info_flag, debug_check_flag;
37
static uint my_end_arg= 0;
38
static char *opt_user= (char*)"root";
40
static DYNAMIC_STRING ds_args;
42
static char *opt_password= 0;
43
static my_bool tty_password= 0;
46
static char *default_dbug_option= (char*) "d:t:O,/tmp/mysql_upgrade.trace";
49
static char **defaults_argv;
51
static my_bool not_used; /* Can't use GET_BOOL without a value pointer */
53
#include <help_start.h>
55
static struct my_option my_long_options[]=
57
{"help", '?', "Display this help message and exit.", 0, 0, 0, GET_NO_ARG,
58
NO_ARG, 0, 0, 0, 0, 0, 0},
59
{"basedir", 'b', "Not used by mysql_upgrade. Only for backward compatibilty",
60
0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
61
{"character-sets-dir", OPT_CHARSETS_DIR,
62
"Directory where character sets are.", 0,
63
0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
64
{"compress", OPT_COMPRESS, "Use compression in server/client protocol.",
65
(uchar**)¬_used, (uchar**)¬_used, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
67
"Not used by mysql_upgrade. Only for backward compatibilty",
68
0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
70
{"debug", '#', "This is a non-debug version. Catch this and exit",
71
0, 0, 0, GET_DISABLED, OPT_ARG, 0, 0, 0, 0, 0, 0},
73
{"debug", '#', "Output debug log", (uchar* *) & default_dbug_option,
74
(uchar* *) & default_dbug_option, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0},
76
{"debug-check", OPT_DEBUG_CHECK, "Check memory and open file usage at exit.",
77
(uchar**) &debug_check_flag, (uchar**) &debug_check_flag, 0,
78
GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
79
{"debug-info", 'T', "Print some debug info at exit.", (uchar**) &debug_info_flag,
80
(uchar**) &debug_info_flag, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
81
{"default-character-set", OPT_DEFAULT_CHARSET,
82
"Set the default character set.", 0,
83
0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
84
{"force", 'f', "Force execution of mysqlcheck even if mysql_upgrade "
85
"has already been executed for the current version of MySQL.",
86
(uchar**)&opt_force, (uchar**)&opt_force, 0,
87
GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
88
{"host",'h', "Connect to host.", 0,
89
0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
91
"Password to use when connecting to server. If password is not given"
92
" it's solicited on the tty.", (uchar**) &opt_password,(uchar**) &opt_password,
93
0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0},
95
{"pipe", 'W', "Use named pipes to connect to server.", 0, 0, 0,
96
GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
98
{"port", 'P', "Port number to use for connection or 0 for default to, in "
99
"order of preference, my.cnf, $MYSQL_TCP_PORT, "
100
#if MYSQL_PORT_DEFAULT == 0
103
"built-in default (" STRINGIFY_ARG(MYSQL_PORT) ").",
104
0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
105
{"protocol", OPT_MYSQL_PROTOCOL,
106
"The protocol of connection (tcp,socket,pipe,memory).",
107
0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
109
{"shared-memory-base-name", OPT_SHARED_MEMORY_BASE_NAME,
110
"Base name of shared memory.", 0,
111
0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
113
{"socket", 'S', "Socket file to use for connection.",
114
0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
115
{"user", 'u', "User for login if not current user.", (uchar**) &opt_user,
116
(uchar**) &opt_user, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
117
{"verbose", 'v', "Display more output about the process",
118
(uchar**) &opt_verbose, (uchar**) &opt_verbose, 0,
119
GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0},
120
{0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
123
#include <help_end.h>
126
static void free_used_memory(void)
128
/* Free memory allocated by 'load_defaults' */
129
free_defaults(defaults_argv);
131
dynstr_free(&ds_args);
135
static void die(const char *fmt, ...)
140
/* Print the error message */
144
fprintf(stderr, "FATAL ERROR: ");
145
vfprintf(stderr, fmt, args);
146
fprintf(stderr, "\n");
157
static void verbose(const char *fmt, ...)
164
/* Print the verbose message */
168
vfprintf(stdout, fmt, args);
169
fprintf(stdout, "\n");
177
Add one option - passed to mysql_upgrade on command line
178
or by defaults file(my.cnf) - to a dynamic string, in
179
this way we pass the same arguments on to mysql and mysql_check
182
static void add_one_option(DYNAMIC_STRING* ds,
183
const struct my_option *opt,
184
const char* argument)
187
const char* eq= NullS;
188
const char* arg= NullS;
189
if (opt->arg_type != NO_ARG)
192
switch (opt->var_type & GET_TYPE_MASK) {
197
die("internal error at %s: %d",__FILE__, __LINE__);
200
dynstr_append_os_quoted(ds, "--", opt->name, eq, arg, NullS);
201
dynstr_append(&ds_args, " ");
206
get_one_option(int optid, const struct my_option *opt,
209
my_bool add_option= TRUE;
214
printf("%s Ver %s Distrib %s, for %s (%s)\n",
215
my_progname, VER, MYSQL_SERVER_VERSION, SYSTEM_TYPE, MACHINE_TYPE);
216
puts("MySQL utility for upgrading databases to new MySQL versions\n");
217
my_print_help(my_long_options);
222
DBUG_PUSH(argument ? argument : default_dbug_option);
232
/* Add password to ds_args before overwriting the arg with x's */
233
add_one_option(&ds_args, opt, argument);
235
*argument++= 'x'; /* Destroy argument */
240
case 'b': /* --basedir */
241
case 'v': /* --verbose */
242
case 'd': /* --datadir */
243
case 'f': /* --force */
251
This is an option that is accpted by mysql_upgrade just so
252
it can be passed on to "mysql" and "mysqlcheck"
253
Save it in the ds_args string
255
add_one_option(&ds_args, opt, argument);
261
static int run_command(char* cmd,
262
DYNAMIC_STRING *ds_res)
268
if (!(res_file= popen(cmd, "r")))
269
die("popen(\"%s\", \"r\") failed", cmd);
271
while (fgets(buf, sizeof(buf), res_file))
273
DBUG_PRINT("info", ("buf: %s", buf));
276
/* Save the output of this command in the supplied string */
277
dynstr_append(ds_res, buf);
281
/* Print it directly on screen */
282
fprintf(stdout, "%s", buf);
286
error= pclose(res_file);
287
return WEXITSTATUS(error);
291
static int run_tool(char *tool_path, DYNAMIC_STRING *ds_res, ...)
296
DYNAMIC_STRING ds_cmdline;
298
DBUG_ENTER("run_tool");
299
DBUG_PRINT("enter", ("tool_path: %s", tool_path));
301
if (init_dynamic_string(&ds_cmdline, IF_WIN("\"", ""), FN_REFLEN, FN_REFLEN))
302
die("Out of memory");
304
dynstr_append_os_quoted(&ds_cmdline, tool_path, NullS);
305
dynstr_append(&ds_cmdline, " ");
307
va_start(args, ds_res);
309
while ((arg= va_arg(args, char *)))
311
/* Options should be os quoted */
312
if (strncmp(arg, "--", 2) == 0)
313
dynstr_append_os_quoted(&ds_cmdline, arg, NullS);
315
dynstr_append(&ds_cmdline, arg);
316
dynstr_append(&ds_cmdline, " ");
322
dynstr_append(&ds_cmdline, "\"");
325
DBUG_PRINT("info", ("Running: %s", ds_cmdline.str));
326
ret= run_command(ds_cmdline.str, ds_res);
327
DBUG_PRINT("exit", ("ret: %d", ret));
328
dynstr_free(&ds_cmdline);
334
Try to get the full path to this exceutable
336
Return 0 if path found
340
static my_bool get_full_path_to_executable(char* path)
343
DBUG_ENTER("get_full_path_to_executable");
345
ret= (GetModuleFileName(NULL, path, FN_REFLEN) == 0);
347
/* my_readlink returns 0 if a symlink was read */
348
ret= (my_readlink(path, "/proc/self/exe", MYF(0)) != 0);
349
/* Might also want to try with /proc/$$/exe if the above fails */
351
DBUG_PRINT("exit", ("path: %s", path));
357
Look for the tool in the same directory as mysql_upgrade.
360
static void find_tool(char *tool_path, const char *tool_name)
363
char path[FN_REFLEN];
364
DYNAMIC_STRING ds_tmp;
365
DBUG_ENTER("find_tool");
366
DBUG_PRINT("enter", ("progname: %s", my_progname));
368
if (init_dynamic_string(&ds_tmp, "", 32, 32))
369
die("Out of memory");
371
/* Initialize path with the full path to this program */
372
if (get_full_path_to_executable(path))
375
Easy way to get full executable path failed, try
378
if (my_progname[0] == FN_LIBCHAR)
380
/* 1. my_progname contains full path */
381
strmake(path, my_progname, FN_REFLEN);
383
else if (my_progname[0] == '.')
385
/* 2. my_progname contains relative path, prepend wd */
387
my_getwd(buf, FN_REFLEN, MYF(0));
388
my_snprintf(path, FN_REFLEN, "%s%s", buf, my_progname);
392
/* 3. Just go for it and hope tool is in path */
397
DBUG_PRINT("info", ("path: '%s'", path));
399
/* Chop off binary name (i.e mysql-upgrade) from path */
400
dirname_part(path, path, &path_len);
403
When running in a not yet installed build and using libtool,
404
the program(mysql_upgrade) will be in .libs/ and executed
405
through a libtool wrapper in order to use the dynamic libraries
406
from this build. The same must be done for the tools(mysql and
407
mysqlcheck). Thus if path ends in .libs/, step up one directory
408
and execute the tools from there
410
path[max(path_len-1, 0)]= 0; /* Chop off last / */
411
if (strncmp(path + dirname_length(path), ".libs", 5) == 0)
413
DBUG_PRINT("info", ("Chopping off .libs from '%s'", path));
416
dirname_part(path, path, &path_len);
420
DBUG_PRINT("info", ("path: '%s'", path));
422
/* Format name of the tool to search for */
423
fn_format(tool_path, tool_name,
424
path, "", MYF(MY_REPLACE_DIR));
426
verbose("Looking for '%s' in: %s", tool_name, tool_path);
428
/* Make sure the tool exists */
429
if (my_access(tool_path, F_OK) != 0)
430
die("Can't find '%s'", tool_path);
433
Make sure it can be executed
435
if (run_tool(tool_path,
436
&ds_tmp, /* Get output from command, discard*/
439
IF_WIN("> NUL", "> /dev/null"),
441
die("Can't execute '%s'", tool_path);
443
dynstr_free(&ds_tmp);
450
Run query using "mysql"
453
static int run_query(const char *query, DYNAMIC_STRING *ds_res,
458
char query_file_path[FN_REFLEN];
459
DBUG_ENTER("run_query");
460
DBUG_PRINT("enter", ("query: %s", query));
461
if ((fd= create_temp_file(query_file_path, NULL,
462
"sql", O_CREAT | O_SHARE | O_RDWR,
464
die("Failed to create temporary file for defaults");
466
if (my_write(fd, (uchar*) query, strlen(query),
467
MYF(MY_FNABP | MY_WME)))
469
my_close(fd, MYF(0));
470
my_delete(query_file_path, MYF(0));
471
die("Failed to write to '%s'", query_file_path);
474
ret= run_tool(mysql_path,
479
"--batch", /* Turns off pager etc. */
480
force ? "--force": "--skip-force",
481
ds_res ? "--silent": "",
487
my_close(fd, MYF(0));
488
my_delete(query_file_path, MYF(0));
495
Extract the value returned from result of "show variable like ..."
498
static int extract_variable_from_show(DYNAMIC_STRING* ds, char* value)
500
char *value_start, *value_end;
502
The query returns "datadir\t<datadir>\n", skip past
505
if ((value_start= strchr(ds->str, '\t')) == NULL)
506
return 1; /* Unexpected result */
509
/* Don't copy the ending newline */
510
if ((value_end= strchr(value_start, '\n')) == NULL)
511
return 1; /* Unexpected result */
513
strncpy(value, value_start, min(FN_REFLEN, value_end-value_start));
518
static int get_upgrade_info_file_name(char* name)
520
DYNAMIC_STRING ds_datadir;
521
DBUG_ENTER("get_upgrade_info_file_name");
523
if (init_dynamic_string(&ds_datadir, NULL, 32, 32))
524
die("Out of memory");
526
if (run_query("show variables like 'datadir'",
527
&ds_datadir, FALSE) ||
528
extract_variable_from_show(&ds_datadir, name))
530
dynstr_free(&ds_datadir);
531
DBUG_RETURN(1); /* Query failed */
534
dynstr_free(&ds_datadir);
536
fn_format(name, "mysql_upgrade_info", name, "", MYF(0));
537
DBUG_PRINT("exit", ("name: %s", name));
543
Read the content of mysql_upgrade_info file and
544
compare the version number form file against
545
version number wich mysql_upgrade was compiled for
548
This is an optimization to avoid running mysql_upgrade
549
when it's already been performed for the particular
552
In case the MySQL server can't return the upgrade info
553
file it's always better to report that the upgrade hasn't
558
static int upgrade_already_done(void)
561
char upgrade_info_file[FN_REFLEN]= {0};
562
char buf[sizeof(MYSQL_SERVER_VERSION)+1];
564
if (get_upgrade_info_file_name(upgrade_info_file))
565
return 0; /* Could not get filename => not sure */
567
if (!(in= my_fopen(upgrade_info_file, O_RDONLY, MYF(0))))
568
return 0; /* Could not open file => not sure */
571
Read from file, don't care if it fails since it
572
will be detected by the strncmp
574
bzero(buf, sizeof(buf));
575
fgets(buf, sizeof(buf), in);
577
my_fclose(in, MYF(0));
579
return (strncmp(buf, MYSQL_SERVER_VERSION,
580
sizeof(MYSQL_SERVER_VERSION)-1)==0);
585
Write mysql_upgrade_info file in servers data dir indicating that
586
upgrade has been done for this version
589
This might very well fail but since it's just an optimization
590
to run mysql_upgrade only when necessary the error can be
595
static void create_mysql_upgrade_info_file(void)
598
char upgrade_info_file[FN_REFLEN]= {0};
600
if (get_upgrade_info_file_name(upgrade_info_file))
601
return; /* Could not get filename => skip */
603
if (!(out= my_fopen(upgrade_info_file, O_TRUNC | O_WRONLY, MYF(0))))
606
"Could not create the upgrade info file '%s' in "
607
"the MySQL Servers datadir, errno: %d\n",
608
upgrade_info_file, errno);
612
/* Write new version to file */
613
fputs(MYSQL_SERVER_VERSION, out);
614
my_fclose(out, MYF(0));
617
Check if the upgrad_info_file was properly created/updated
618
It's not a fatal error -> just print a message if it fails
620
if (!upgrade_already_done())
622
"Could not write to the upgrade info file '%s' in "
623
"the MySQL Servers datadir, errno: %d\n",
624
upgrade_info_file, errno);
630
Check and upgrade(if neccessary) all tables
631
in the server using "mysqlcheck --check-upgrade .."
634
static int run_mysqlcheck_upgrade(void)
636
verbose("Running 'mysqlcheck'...");
637
return run_tool(mysqlcheck_path,
638
NULL, /* Send output from mysqlcheck directly to screen */
648
static const char *expected_errors[]=
650
"ERROR 1060", /* Duplicate column name */
651
"ERROR 1061", /* Duplicate key name */
652
"ERROR 1054", /* Unknown column */
657
static my_bool is_expected_error(const char* line)
659
const char** error= expected_errors;
663
Check if lines starting with ERROR
664
are in the list of expected errors
666
if (strncmp(line, "ERROR", 5) != 0 ||
667
strncmp(line, *error, strlen(*error)) == 0)
668
return 1; /* Found expected error */
675
static char* get_line(char* line)
677
while (*line && *line != '\n')
685
/* Print the current line to stderr */
686
static void print_line(char* line)
688
while (*line && *line != '\n')
690
fputc(*line, stderr);
698
Update all system tables in MySQL Server to current
699
version using "mysql" to execute all the SQL commands
700
compiled into the mysql_fix_privilege_tables array
703
static int run_sql_fix_privilege_tables(void)
705
int found_real_errors= 0;
706
DYNAMIC_STRING ds_result;
707
DBUG_ENTER("run_sql_fix_privilege_tables");
709
if (init_dynamic_string(&ds_result, "", 512, 512))
710
die("Out of memory");
712
verbose("Running 'mysql_fix_privilege_tables'...");
713
run_query(mysql_fix_privilege_tables,
714
&ds_result, /* Collect result */
719
Scan each line of the result for real errors
720
and ignore the expected one(s) like "Duplicate column name",
721
"Unknown column" and "Duplicate key name" since they just
722
indicate the system tables are already up to date
724
char *line= ds_result.str;
727
if (!is_expected_error(line))
729
/* Something unexpected failed, dump error line to screen */
733
} while ((line= get_line(line)) && *line);
736
dynstr_free(&ds_result);
737
return found_real_errors;
741
static const char *load_default_groups[]=
743
"client", /* Read settings how to connect to server */
744
"mysql_upgrade", /* Read special settings for mysql_upgrade*/
749
int main(int argc, char **argv)
753
setscreenmode(SCR_AUTOCLOSE_ON_EXIT);
756
if (init_dynamic_string(&ds_args, "", 512, 256))
757
die("Out of memory");
759
load_defaults("my", load_default_groups, &argc, &argv);
760
defaults_argv= argv; /* Must be freed by 'free_defaults' */
762
if (handle_options(&argc, &argv, my_long_options, get_one_option))
765
my_end_arg= MY_CHECK_ERROR | MY_GIVE_INFO;
766
if (debug_check_flag)
767
my_end_arg= MY_CHECK_ERROR;
771
opt_password= get_tty_password(NullS);
772
/* add password to defaults file */
773
dynstr_append_os_quoted(&ds_args, "--password=", opt_password, NullS);
774
dynstr_append(&ds_args, " ");
776
/* add user to defaults file */
777
dynstr_append_os_quoted(&ds_args, "--user=", opt_user, NullS);
778
dynstr_append(&ds_args, " ");
781
find_tool(mysql_path, IF_WIN("mysql.exe", "mysql"));
783
/* Find mysqlcheck */
784
find_tool(mysqlcheck_path, IF_WIN("mysqlcheck.exe", "mysqlcheck"));
787
Read the mysql_upgrade_info file to check if mysql_upgrade
788
already has been run for this installation of MySQL
790
if (!opt_force && upgrade_already_done())
792
printf("This installation of MySQL is already upgraded to %s, "
793
"use --force if you still need to run mysql_upgrade\n",
794
MYSQL_SERVER_VERSION);
799
Run "mysqlcheck" and "mysql_fix_privilege_tables.sql"
801
if (run_mysqlcheck_upgrade() ||
802
run_sql_fix_privilege_tables())
805
The upgrade failed to complete in some way or another,
806
significant error message should have been printed to the screen
808
die("Upgrade failed" );
812
/* Create a file indicating upgrade has been performed */
813
create_mysql_upgrade_info_file();