~ubuntu-branches/ubuntu/trusty/mariadb-5.5/trusty-proposed

« back to all changes in this revision

Viewing changes to sql/mysql_upgrade_service.cc

  • Committer: Package Import Robot
  • Author(s): Otto Kekäläinen
  • Date: 2013-12-22 10:27:05 UTC
  • Revision ID: package-import@ubuntu.com-20131222102705-mndw7s12mz0szrcn
Tags: upstream-5.5.32
Import upstream version 5.5.32

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* Copyright (C) 2010-2011 Monty Program Ab & Vladislav Vaintroub
 
2
 
 
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.
 
6
 
 
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.
 
11
 
 
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 */
 
15
 
 
16
/*
 
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
 
20
*/
 
21
 
 
22
#define DONT_DEFINE_VOID
 
23
#include <process.h>
 
24
#include <my_global.h>
 
25
#include <my_getopt.h>
 
26
#include <my_sys.h>
 
27
#include <m_string.h>
 
28
#include <mysql_version.h>
 
29
#include <winservice.h>
 
30
 
 
31
#include <windows.h>
 
32
 
 
33
/* We're using version APIs */
 
34
#pragma comment(lib, "version")
 
35
 
 
36
#define USAGETEXT \
 
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" \
 
42
"OPTIONS:"
 
43
 
 
44
static char mysqld_path[MAX_PATH];
 
45
static char mysqladmin_path[MAX_PATH];
 
46
static char mysqlupgrade_path[MAX_PATH];
 
47
 
 
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;
 
52
static SC_HANDLE scm;
 
53
HANDLE mysqld_process; // mysqld.exe started for upgrade
 
54
DWORD initial_service_state= -1; // initial state of the service
 
55
HANDLE logfile_handle;
 
56
 
 
57
/*
 
58
  Startup and shutdown timeouts, in seconds. 
 
59
  Maybe,they can be made parameters
 
60
*/
 
61
static unsigned int startup_timeout= 60;
 
62
static unsigned int shutdown_timeout= 60;
 
63
 
 
64
static struct my_option my_long_options[]=
 
65
{
 
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}
 
71
};
 
72
 
 
73
 
 
74
 
 
75
static my_bool
 
76
get_one_option(int optid, 
 
77
   const struct my_option *opt __attribute__ ((unused)),
 
78
   char *argument __attribute__ ((unused)))
 
79
{
 
80
  DBUG_ENTER("get_one_option");
 
81
  switch (optid) {
 
82
  case '?':
 
83
    printf("%s\n", USAGETEXT);
 
84
    my_print_help(my_long_options);
 
85
    exit(0);
 
86
    break;
 
87
  }
 
88
  DBUG_RETURN(0);
 
89
}
 
90
 
 
91
 
 
92
 
 
93
static void log(const char *fmt, ...)
 
94
{
 
95
  va_list args;
 
96
  /* Print the error message */
 
97
  va_start(args, fmt);
 
98
  vfprintf(stdout,fmt, args);
 
99
  va_end(args);
 
100
  fputc('\n', stdout);
 
101
  fflush(stdout);
 
102
}
 
103
 
 
104
 
 
105
static void die(const char *fmt, ...)
 
106
{
 
107
  va_list args;
 
108
  DBUG_ENTER("die");
 
109
 
 
110
  /* Print the error message */
 
111
  va_start(args, fmt);
 
112
 
 
113
  fprintf(stderr, "FATAL ERROR: ");
 
114
  vfprintf(stderr, fmt, args);
 
115
  if (logfile_path[0])
 
116
  {
 
117
    fprintf(stderr, "Additional information can be found in the log file %s",
 
118
      logfile_path);
 
119
  }
 
120
  va_end(args);
 
121
  fputc('\n', stderr);
 
122
  fflush(stdout);
 
123
  /* Cleanup */
 
124
 
 
125
  /*
 
126
    Stop service that we started, if it was not initally running at
 
127
    program start.
 
128
  */
 
129
  if (initial_service_state != -1 && initial_service_state != SERVICE_RUNNING)
 
130
  {
 
131
    SERVICE_STATUS service_status;
 
132
    ControlService(service, SERVICE_CONTROL_STOP, &service_status);
 
133
  }
 
134
 
 
135
  if (scm)
 
136
    CloseServiceHandle(scm);
 
137
  if (service)
 
138
    CloseServiceHandle(service);
 
139
  /* Stop mysqld.exe, if it was started for upgrade */
 
140
  if (mysqld_process)
 
141
    TerminateProcess(mysqld_process, 3);
 
142
  if (logfile_handle)
 
143
    CloseHandle(logfile_handle);
 
144
  my_end(0);
 
145
 
 
146
  exit(1);
 
147
}
 
148
 
 
149
 
 
150
/*
 
151
  spawn-like function to run subprocesses. 
 
152
  We also redirect the full output to the log file.
 
153
 
 
154
  Typical usage could be something like
 
155
  run_tool(P_NOWAIT, "cmd.exe", "/c" , "echo", "foo", NULL)
 
156
  
 
157
  @param    wait_flag (P_WAIT or P_NOWAIT)
 
158
  @program  program to run
 
159
 
 
160
  Rest of the parameters is NULL terminated strings building command line.
 
161
 
 
162
  @return intptr containing either process handle, if P_NOWAIT is used
 
163
  or return code of the process (if P_WAIT is used)
 
164
*/
 
165
 
 
166
static intptr_t run_tool(int wait_flag, const char *program,...)
 
167
{
 
168
  static char cmdline[32*1024];
 
169
  char *end;
 
170
  va_list args;
 
171
  va_start(args, program);
 
172
  if (!program)
 
173
    die("Invalid call to run_tool");
 
174
  end= strxmov(cmdline, "\"", program, "\"", NullS);
 
175
 
 
176
  for(;;) 
 
177
  {
 
178
    char *param= va_arg(args,char *);
 
179
    if(!param)
 
180
      break;
 
181
    end= strxmov(end, " \"", param, "\"", NullS);
 
182
  }
 
183
  va_end(args);
 
184
  
 
185
  /* Create output file if not alredy done */
 
186
  if (!logfile_handle)
 
187
  {
 
188
    char tmpdir[FN_REFLEN];
 
189
    GetTempPath(FN_REFLEN, tmpdir);
 
190
    sprintf_s(logfile_path, "%s\\mysql_upgrade_service.%s.log", tmpdir, 
 
191
      opt_service);
 
192
    logfile_handle= CreateFile(logfile_path, GENERIC_WRITE,  FILE_SHARE_READ, 
 
193
      NULL, TRUNCATE_EXISTING, 0, NULL);
 
194
    if (!logfile_handle)
 
195
    {
 
196
      die("Cannot open log file %s, windows error %u", 
 
197
        logfile_path, GetLastError());
 
198
    }
 
199
  }
 
200
 
 
201
  /* Start child process */
 
202
  STARTUPINFO si= {0};
 
203
  si.cb= sizeof(si);
 
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))
 
211
  {
 
212
    die("CreateProcess failed (commandline %s)", cmdline);
 
213
  }
 
214
  CloseHandle(pi.hThread);
 
215
 
 
216
  if (wait_flag == P_NOWAIT)
 
217
  {
 
218
    /* Do not wait for process to complete, return handle. */
 
219
    return (intptr_t)pi.hProcess;
 
220
  }
 
221
 
 
222
  /* Wait for process to complete. */
 
223
  if (WaitForSingleObject(pi.hProcess, INFINITE) != WAIT_OBJECT_0)
 
224
  {
 
225
    die("WaitForSingleObject() failed");
 
226
  }
 
227
  DWORD exit_code;
 
228
  if (!GetExitCodeProcess(pi.hProcess, &exit_code))
 
229
  {
 
230
    die("GetExitCodeProcess() failed");
 
231
  }
 
232
  return (intptr_t)exit_code;
 
233
}
 
234
 
 
235
 
 
236
void stop_mysqld_service()
 
237
{
 
238
  DWORD needed;
 
239
  SERVICE_STATUS_PROCESS ssp;
 
240
  int timeout= shutdown_timeout*1000; 
 
241
  for(;;)
 
242
  {
 
243
    if (!QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO,
 
244
          (LPBYTE)&ssp, 
 
245
          sizeof(SERVICE_STATUS_PROCESS),
 
246
          &needed))
 
247
    {
 
248
      die("QueryServiceStatusEx failed (%u)\n", GetLastError()); 
 
249
    }
 
250
 
 
251
    /*
 
252
      Remeber initial state of the service, we will restore it on
 
253
      exit.
 
254
    */
 
255
    if(initial_service_state == -1)
 
256
      initial_service_state= ssp.dwCurrentState;
 
257
 
 
258
    switch(ssp.dwCurrentState)
 
259
    {
 
260
      case SERVICE_STOPPED:
 
261
        return;
 
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:
 
268
        if(timeout < 0)
 
269
          die("Service does not stop after %d seconds timeout",shutdown_timeout);
 
270
        Sleep(100);
 
271
        timeout -= 100;
 
272
        break;
 
273
      default:
 
274
        die("Unexpected service state %d",ssp.dwCurrentState);
 
275
    }
 
276
  }
 
277
}
 
278
 
 
279
 
 
280
/* 
 
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.
 
284
*/
 
285
void initiate_mysqld_shutdown()
 
286
{
 
287
  char event_name[32];
 
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);
 
291
  if(!shutdown_handle)
 
292
  {
 
293
    die("OpenEvent() failed for shutdown event");
 
294
  }
 
295
 
 
296
  if(!SetEvent(shutdown_handle))
 
297
  {
 
298
    die("SetEvent() failed");
 
299
  }
 
300
}
 
301
 
 
302
 
 
303
/*
 
304
  Change service configuration (binPath) to point to mysqld from 
 
305
  this installation.
 
306
*/
 
307
static void change_service_config()
 
308
{
 
309
 
 
310
  char defaults_file[MAX_PATH];
 
311
  char default_character_set[64];
 
312
  char buf[MAX_PATH];
 
313
  char commandline[3*MAX_PATH + 19];
 
314
  int i;
 
315
 
 
316
  scm= OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
 
317
  if(!scm)
 
318
    die("OpenSCManager failed with %u", GetLastError());
 
319
  service= OpenService(scm, opt_service, SERVICE_ALL_ACCESS);
 
320
  if (!service)
 
321
    die("OpenService failed with %u", GetLastError());
 
322
 
 
323
  BYTE config_buffer[8*1024];
 
324
  LPQUERY_SERVICE_CONFIGW config= (LPQUERY_SERVICE_CONFIGW)config_buffer;
 
325
  DWORD size= sizeof(config_buffer);
 
326
  DWORD needed;
 
327
  if (!QueryServiceConfigW(service, config, size, &needed))
 
328
    die("QueryServiceConfig failed with %u", GetLastError());
 
329
 
 
330
  mysqld_service_properties props;
 
331
  if (get_mysql_service_properties(config->lpBinaryPathName, &props))
 
332
  {
 
333
    die("Not a valid MySQL service");
 
334
  }
 
335
 
 
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;
 
339
 
 
340
  if(my_major < props.version_major || 
 
341
    (my_major == props.version_major && my_minor < props.version_minor))
 
342
  {
 
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);
 
346
  }
 
347
 
 
348
  if(props.inifile[0] == 0)
 
349
  {
 
350
    /*
 
351
      Weird case, no --defaults-file in service definition, need to create one.
 
352
    */
 
353
    sprintf_s(props.inifile, MAX_PATH, "%s\\my.ini", props.datadir);
 
354
  }
 
355
 
 
356
  /*
 
357
    Write datadir to my.ini, after converting  backslashes to 
 
358
    unix style slashes.
 
359
  */
 
360
  strcpy_s(buf, MAX_PATH, props.datadir);
 
361
  for(i= 0; buf[i]; i++)
 
362
  {
 
363
    if (buf[i] == '\\')
 
364
      buf[i]= '/';
 
365
  }
 
366
  WritePrivateProfileString("mysqld", "datadir",buf, props.inifile);
 
367
 
 
368
  /*
 
369
    Remove basedir from defaults file, otherwise the service wont come up in 
 
370
    the new version, and will complain about mismatched message file.
 
371
  */
 
372
  WritePrivateProfileString("mysqld", "basedir",NULL, props.inifile);
 
373
 
 
374
  /* 
 
375
    Replace default-character-set  with character-set-server, to avoid 
 
376
    "default-character-set is deprecated and will be replaced ..."
 
377
    message.
 
378
  */
 
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])
 
383
  {
 
384
    WritePrivateProfileString("mysqld", "default-character-set", NULL, 
 
385
      defaults_file);
 
386
    WritePrivateProfileString("mysqld", "character-set-server",
 
387
      default_character_set, defaults_file);
 
388
  }
 
389
 
 
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))
 
395
  {
 
396
    die("ChangeServiceConfig failed with %u", GetLastError());
 
397
  }
 
398
 
 
399
}
 
400
 
 
401
 
 
402
int main(int argc, char **argv)
 
403
{
 
404
  int error;
 
405
  MY_INIT(argv[0]);
 
406
  char bindir[FN_REFLEN];
 
407
  char *p;
 
408
 
 
409
  /* Parse options */
 
410
  if ((error= handle_options(&argc, &argv, my_long_options, get_one_option)))
 
411
    die("");
 
412
  if (!opt_service)
 
413
    die("--service=# parameter is mandatory");
 
414
 
 
415
 /*
 
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.
 
419
  */
 
420
  GetModuleFileName(NULL, bindir, FN_REFLEN);
 
421
  p= strrchr(bindir, FN_LIBCHAR);
 
422
  if(p)
 
423
  {
 
424
    *p= 0;
 
425
  }
 
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);
 
429
 
 
430
  char *paths[]= {mysqld_path, mysqladmin_path, mysqlupgrade_path};
 
431
  for(int i= 0; i< 3;i++)
 
432
  {
 
433
    if(GetFileAttributes(paths[i]) == INVALID_FILE_ATTRIBUTES)
 
434
      die("File %s does not exist", paths[i]);
 
435
  }
 
436
 
 
437
  /*
 
438
    Messages written on stdout should not be buffered,  GUI upgrade program 
 
439
    reads them from pipe and uses as progress indicator.
 
440
  */
 
441
  setvbuf(stdout, NULL, _IONBF, 0);
 
442
 
 
443
  log("Phase 1/8: Changing service configuration");
 
444
  change_service_config();
 
445
 
 
446
  log("Phase 2/8: Stopping service");
 
447
  stop_mysqld_service();
 
448
 
 
449
  /* 
 
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.
 
453
  */
 
454
  char socket_param[FN_REFLEN];
 
455
  sprintf_s(socket_param,"--socket=mysql_upgrade_service_%d", 
 
456
    GetCurrentProcessId());
 
457
 
 
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);
 
462
 
 
463
  if (mysqld_process == INVALID_HANDLE_VALUE)
 
464
  {
 
465
    die("Cannot start mysqld.exe process, errno=%d", errno);
 
466
  }
 
467
 
 
468
  log("Phase 4/8: Waiting for startup to complete");
 
469
  DWORD start_duration_ms= 0;
 
470
  for(;;)
 
471
  {
 
472
    if (WaitForSingleObject(mysqld_process, 0) != WAIT_TIMEOUT)
 
473
      die("mysqld.exe did not start");
 
474
 
 
475
    if (run_tool(P_WAIT, mysqladmin_path, "--protocol=pipe",
 
476
      socket_param, "ping",  NULL) == 0)
 
477
    {
 
478
      break;
 
479
    }
 
480
    if (start_duration_ms > startup_timeout*1000)
 
481
      die("Server did not come up in %d seconds",startup_timeout);
 
482
    Sleep(500);
 
483
    start_duration_ms+= 500;
 
484
  }
 
485
 
 
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,
 
489
    NULL);
 
490
 
 
491
  if (upgrade_err)
 
492
    die("mysql_upgrade failed with error code %d\n", upgrade_err);
 
493
 
 
494
  log("Phase 6/8: Initiating server shutdown");
 
495
  initiate_mysqld_shutdown();
 
496
 
 
497
  log("Phase 7/8: Waiting for shutdown to complete");
 
498
  if (WaitForSingleObject(mysqld_process, shutdown_timeout*1000)
 
499
      != WAIT_OBJECT_0)
 
500
  {
 
501
    /* Shutdown takes too long */
 
502
    die("mysqld does not shutdown.");
 
503
  }
 
504
  CloseHandle(mysqld_process);
 
505
  mysqld_process= NULL;
 
506
 
 
507
  log("Phase 8/8: Starting service%s",
 
508
    (initial_service_state == SERVICE_RUNNING)?"":" (skipped)");
 
509
  if (initial_service_state == SERVICE_RUNNING)
 
510
  {
 
511
    StartService(service, NULL, NULL);
 
512
  }
 
513
 
 
514
  log("Service '%s' successfully upgraded.\nLog file is written to %s",
 
515
    opt_service, logfile_path);
 
516
  CloseServiceHandle(service);
 
517
  CloseServiceHandle(scm);
 
518
  if (logfile_handle)
 
519
    CloseHandle(logfile_handle);
 
520
  my_end(0);
 
521
  exit(0);
 
522
}
 
 
b'\\ No newline at end of file'