1
/* Copyright (C) 2010-2011 Monty Program Ab & Vladislav Vaintroub
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 */
17
mysql_upgrade_service upgrades mysql service on Windows.
18
It changes service definition to point to the new mysqld.exe, restarts the
19
server and runs mysql_upgrade
22
#define DONT_DEFINE_VOID
24
#include <my_global.h>
25
#include <my_getopt.h>
28
#include <mysql_version.h>
29
#include <winservice.h>
33
/* We're using version APIs */
34
#pragma comment(lib, "version")
37
"mysql_upgrade_service.exe Ver 1.00 for Windows\n" \
38
"Copyright (C) 2010-2011 Monty Program Ab & Vladislav Vaintroub" \
39
"This software comes with ABSOLUTELY NO WARRANTY. This is free software,\n" \
40
"and you are welcome to modify and redistribute it under the GPL v2 license\n" \
41
"Usage: mysql_upgrade_service.exe [OPTIONS]\n" \
44
static char mysqld_path[MAX_PATH];
45
static char mysqladmin_path[MAX_PATH];
46
static char mysqlupgrade_path[MAX_PATH];
48
static char defaults_file_param[MAX_PATH + 16]; /*--defaults-file=<path> */
49
static char logfile_path[MAX_PATH];
50
static char *opt_service;
51
static SC_HANDLE service;
53
HANDLE mysqld_process; // mysqld.exe started for upgrade
54
DWORD initial_service_state= -1; // initial state of the service
55
HANDLE logfile_handle;
58
Startup and shutdown timeouts, in seconds.
59
Maybe,they can be made parameters
61
static unsigned int startup_timeout= 60;
62
static unsigned int shutdown_timeout= 60;
64
static struct my_option my_long_options[]=
66
{"help", '?', "Display this help message and exit.", 0, 0, 0, GET_NO_ARG,
67
NO_ARG, 0, 0, 0, 0, 0, 0},
68
{"service", 'S', "Name of the existing Windows service",
69
&opt_service, &opt_service, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
70
{0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
76
get_one_option(int optid,
77
const struct my_option *opt __attribute__ ((unused)),
78
char *argument __attribute__ ((unused)))
80
DBUG_ENTER("get_one_option");
83
printf("%s\n", USAGETEXT);
84
my_print_help(my_long_options);
93
static void log(const char *fmt, ...)
96
/* Print the error message */
98
vfprintf(stdout,fmt, args);
105
static void die(const char *fmt, ...)
110
/* Print the error message */
113
fprintf(stderr, "FATAL ERROR: ");
114
vfprintf(stderr, fmt, args);
117
fprintf(stderr, "Additional information can be found in the log file %s",
126
Stop service that we started, if it was not initally running at
129
if (initial_service_state != -1 && initial_service_state != SERVICE_RUNNING)
131
SERVICE_STATUS service_status;
132
ControlService(service, SERVICE_CONTROL_STOP, &service_status);
136
CloseServiceHandle(scm);
138
CloseServiceHandle(service);
139
/* Stop mysqld.exe, if it was started for upgrade */
141
TerminateProcess(mysqld_process, 3);
143
CloseHandle(logfile_handle);
151
spawn-like function to run subprocesses.
152
We also redirect the full output to the log file.
154
Typical usage could be something like
155
run_tool(P_NOWAIT, "cmd.exe", "/c" , "echo", "foo", NULL)
157
@param wait_flag (P_WAIT or P_NOWAIT)
158
@program program to run
160
Rest of the parameters is NULL terminated strings building command line.
162
@return intptr containing either process handle, if P_NOWAIT is used
163
or return code of the process (if P_WAIT is used)
166
static intptr_t run_tool(int wait_flag, const char *program,...)
168
static char cmdline[32*1024];
171
va_start(args, program);
173
die("Invalid call to run_tool");
174
end= strxmov(cmdline, "\"", program, "\"", NullS);
178
char *param= va_arg(args,char *);
181
end= strxmov(end, " \"", param, "\"", NullS);
185
/* Create output file if not alredy done */
188
char tmpdir[FN_REFLEN];
189
GetTempPath(FN_REFLEN, tmpdir);
190
sprintf_s(logfile_path, "%s\\mysql_upgrade_service.%s.log", tmpdir,
192
logfile_handle= CreateFile(logfile_path, GENERIC_WRITE, FILE_SHARE_READ,
193
NULL, TRUNCATE_EXISTING, 0, NULL);
196
die("Cannot open log file %s, windows error %u",
197
logfile_path, GetLastError());
201
/* Start child process */
204
si.hStdInput= GetStdHandle(STD_INPUT_HANDLE);
205
si.hStdError= logfile_handle;
206
si.hStdOutput= logfile_handle;
207
si.dwFlags= STARTF_USESTDHANDLES;
208
PROCESS_INFORMATION pi;
209
if (!CreateProcess(NULL, cmdline, NULL,
210
NULL, TRUE, NULL, NULL, NULL, &si, &pi))
212
die("CreateProcess failed (commandline %s)", cmdline);
214
CloseHandle(pi.hThread);
216
if (wait_flag == P_NOWAIT)
218
/* Do not wait for process to complete, return handle. */
219
return (intptr_t)pi.hProcess;
222
/* Wait for process to complete. */
223
if (WaitForSingleObject(pi.hProcess, INFINITE) != WAIT_OBJECT_0)
225
die("WaitForSingleObject() failed");
228
if (!GetExitCodeProcess(pi.hProcess, &exit_code))
230
die("GetExitCodeProcess() failed");
232
return (intptr_t)exit_code;
236
void stop_mysqld_service()
239
SERVICE_STATUS_PROCESS ssp;
240
int timeout= shutdown_timeout*1000;
243
if (!QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO,
245
sizeof(SERVICE_STATUS_PROCESS),
248
die("QueryServiceStatusEx failed (%u)\n", GetLastError());
252
Remeber initial state of the service, we will restore it on
255
if(initial_service_state == -1)
256
initial_service_state= ssp.dwCurrentState;
258
switch(ssp.dwCurrentState)
260
case SERVICE_STOPPED:
262
case SERVICE_RUNNING:
263
if(!ControlService(service, SERVICE_CONTROL_STOP,
264
(SERVICE_STATUS *)&ssp))
265
die("ControlService failed, error %u\n", GetLastError());
266
case SERVICE_START_PENDING:
267
case SERVICE_STOP_PENDING:
269
die("Service does not stop after %d seconds timeout",shutdown_timeout);
274
die("Unexpected service state %d",ssp.dwCurrentState);
281
Shutdown mysql server. Not using mysqladmin, since
282
our --skip-grant-tables do not work anymore after mysql_upgrade
283
that does "flush privileges". Instead, the shutdown event is set.
285
void initiate_mysqld_shutdown()
288
DWORD pid= GetProcessId(mysqld_process);
289
sprintf_s(event_name, "MySQLShutdown%d", pid);
290
HANDLE shutdown_handle= OpenEvent(EVENT_MODIFY_STATE, FALSE, event_name);
293
die("OpenEvent() failed for shutdown event");
296
if(!SetEvent(shutdown_handle))
298
die("SetEvent() failed");
304
Change service configuration (binPath) to point to mysqld from
307
static void change_service_config()
310
char defaults_file[MAX_PATH];
311
char default_character_set[64];
313
char commandline[3*MAX_PATH + 19];
316
scm= OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
318
die("OpenSCManager failed with %u", GetLastError());
319
service= OpenService(scm, opt_service, SERVICE_ALL_ACCESS);
321
die("OpenService failed with %u", GetLastError());
323
BYTE config_buffer[8*1024];
324
LPQUERY_SERVICE_CONFIGW config= (LPQUERY_SERVICE_CONFIGW)config_buffer;
325
DWORD size= sizeof(config_buffer);
327
if (!QueryServiceConfigW(service, config, size, &needed))
328
die("QueryServiceConfig failed with %u", GetLastError());
330
mysqld_service_properties props;
331
if (get_mysql_service_properties(config->lpBinaryPathName, &props))
333
die("Not a valid MySQL service");
336
int my_major= MYSQL_VERSION_ID/10000;
337
int my_minor= (MYSQL_VERSION_ID %10000)/100;
338
int my_patch= MYSQL_VERSION_ID%100;
340
if(my_major < props.version_major ||
341
(my_major == props.version_major && my_minor < props.version_minor))
343
die("Can not downgrade, the service is currently running as version %d.%d.%d"
344
", my version is %d.%d.%d", props.version_major, props.version_minor,
345
props.version_patch, my_major, my_minor, my_patch);
348
if(props.inifile[0] == 0)
351
Weird case, no --defaults-file in service definition, need to create one.
353
sprintf_s(props.inifile, MAX_PATH, "%s\\my.ini", props.datadir);
357
Write datadir to my.ini, after converting backslashes to
360
strcpy_s(buf, MAX_PATH, props.datadir);
361
for(i= 0; buf[i]; i++)
366
WritePrivateProfileString("mysqld", "datadir",buf, props.inifile);
369
Remove basedir from defaults file, otherwise the service wont come up in
370
the new version, and will complain about mismatched message file.
372
WritePrivateProfileString("mysqld", "basedir",NULL, props.inifile);
375
Replace default-character-set with character-set-server, to avoid
376
"default-character-set is deprecated and will be replaced ..."
379
default_character_set[0]= 0;
380
GetPrivateProfileString("mysqld", "default-character-set", NULL,
381
default_character_set, sizeof(default_character_set), defaults_file);
382
if (default_character_set[0])
384
WritePrivateProfileString("mysqld", "default-character-set", NULL,
386
WritePrivateProfileString("mysqld", "character-set-server",
387
default_character_set, defaults_file);
390
sprintf(defaults_file_param,"--defaults-file=%s", props.inifile);
391
sprintf_s(commandline, "\"%s\" \"%s\" \"%s\"", mysqld_path,
392
defaults_file_param, opt_service);
393
if (!ChangeServiceConfig(service, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE,
394
SERVICE_NO_CHANGE, commandline, NULL, NULL, NULL, NULL, NULL, NULL))
396
die("ChangeServiceConfig failed with %u", GetLastError());
402
int main(int argc, char **argv)
406
char bindir[FN_REFLEN];
410
if ((error= handle_options(&argc, &argv, my_long_options, get_one_option)))
413
die("--service=# parameter is mandatory");
416
Get full path to mysqld, we need it when changing service configuration.
417
Assume installation layout, i.e mysqld.exe, mysqladmin.exe, mysqlupgrade.exe
418
and mysql_upgrade_service.exe are in the same directory.
420
GetModuleFileName(NULL, bindir, FN_REFLEN);
421
p= strrchr(bindir, FN_LIBCHAR);
426
sprintf_s(mysqld_path, "%s\\mysqld.exe", bindir);
427
sprintf_s(mysqladmin_path, "%s\\mysqladmin.exe", bindir);
428
sprintf_s(mysqlupgrade_path, "%s\\mysql_upgrade.exe", bindir);
430
char *paths[]= {mysqld_path, mysqladmin_path, mysqlupgrade_path};
431
for(int i= 0; i< 3;i++)
433
if(GetFileAttributes(paths[i]) == INVALID_FILE_ATTRIBUTES)
434
die("File %s does not exist", paths[i]);
438
Messages written on stdout should not be buffered, GUI upgrade program
439
reads them from pipe and uses as progress indicator.
441
setvbuf(stdout, NULL, _IONBF, 0);
443
log("Phase 1/8: Changing service configuration");
444
change_service_config();
446
log("Phase 2/8: Stopping service");
447
stop_mysqld_service();
450
Start mysqld.exe as non-service skipping privileges (so we do not
451
care about the password). But disable networking and enable pipe
452
for communication, for security reasons.
454
char socket_param[FN_REFLEN];
455
sprintf_s(socket_param,"--socket=mysql_upgrade_service_%d",
456
GetCurrentProcessId());
458
log("Phase 3/8: Starting mysqld for upgrade");
459
mysqld_process= (HANDLE)run_tool(P_NOWAIT, mysqld_path,
460
defaults_file_param, "--skip-networking", "--skip-grant-tables",
461
"--enable-named-pipe", socket_param, NULL);
463
if (mysqld_process == INVALID_HANDLE_VALUE)
465
die("Cannot start mysqld.exe process, errno=%d", errno);
468
log("Phase 4/8: Waiting for startup to complete");
469
DWORD start_duration_ms= 0;
472
if (WaitForSingleObject(mysqld_process, 0) != WAIT_TIMEOUT)
473
die("mysqld.exe did not start");
475
if (run_tool(P_WAIT, mysqladmin_path, "--protocol=pipe",
476
socket_param, "ping", NULL) == 0)
480
if (start_duration_ms > startup_timeout*1000)
481
die("Server did not come up in %d seconds",startup_timeout);
483
start_duration_ms+= 500;
486
log("Phase 5/8: Running mysql_upgrade");
487
int upgrade_err= (int) run_tool(P_WAIT, mysqlupgrade_path,
488
"--protocol=pipe", "--force", socket_param,
492
die("mysql_upgrade failed with error code %d\n", upgrade_err);
494
log("Phase 6/8: Initiating server shutdown");
495
initiate_mysqld_shutdown();
497
log("Phase 7/8: Waiting for shutdown to complete");
498
if (WaitForSingleObject(mysqld_process, shutdown_timeout*1000)
501
/* Shutdown takes too long */
502
die("mysqld does not shutdown.");
504
CloseHandle(mysqld_process);
505
mysqld_process= NULL;
507
log("Phase 8/8: Starting service%s",
508
(initial_service_state == SERVICE_RUNNING)?"":" (skipped)");
509
if (initial_service_state == SERVICE_RUNNING)
511
StartService(service, NULL, NULL);
514
log("Service '%s' successfully upgraded.\nLog file is written to %s",
515
opt_service, logfile_path);
516
CloseServiceHandle(service);
517
CloseServiceHandle(scm);
519
CloseHandle(logfile_handle);
b'\\ No newline at end of file'