mysql_install_db.cc 16.7 KB
Newer Older
1
/* Copyright (C) 2010-2011 Monty Program Ab & Vladislav Vaintroub
2

3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

/*
  mysql_install_db creates a new database instance (optionally as service)
  on Windows.
*/
20 21 22 23 24 25 26 27 28 29 30 31
#define DONT_DEFINE_VOID
#include <my_global.h>
#include <my_getopt.h>
#include <my_sys.h>
#include <m_string.h>

#include <windows.h>
#include <assert.h>
#include <shellapi.h>
#include <accctrl.h>
#include <aclapi.h>

32
#define USAGETEXT \
33 34
"mysql_install_db.exe  Ver 1.00 for Windows\n" \
"Copyright (C) 2010-2011 Monty Program Ab & Vladislav Vaintroub\n" \
35
"This software comes with ABSOLUTELY NO WARRANTY. This is free software,\n" \
36 37 38
"and you are welcome to modify and redistribute it under the GPL v2 license\n" \
"Usage: mysql_install_db.exe [OPTIONS]\n" \
"OPTIONS:"
39 40 41

extern "C" const char mysql_bootstrap_sql[];

42
char default_os_user[]= "NT AUTHORITY\\NetworkService";
43
static int create_db_instance();
44
static uint opt_silent;
45 46 47 48 49 50 51 52 53 54 55 56
static char datadir_buffer[FN_REFLEN];
static char mysqld_path[FN_REFLEN];
static char *opt_datadir;
static char *opt_service;
static char *opt_password;
static int  opt_port;
static char *opt_socket;
static char *opt_os_user;
static char *opt_os_password;
static my_bool opt_default_user;
static my_bool opt_allow_remote_root_access;
static my_bool opt_skip_networking;
57
static my_bool verbose_errors;
58 59 60 61 62 63 64 65


static struct my_option my_long_options[]=
{
  {"help", '?', "Display this help message and exit.", 0, 0, 0, GET_NO_ARG,
   NO_ARG, 0, 0, 0, 0, 0, 0},
  {"datadir", 'd', "Data directory of the new database",
  &opt_datadir, &opt_datadir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
66
  {"service", 'S', "Name of the Windows service",
67 68 69 70 71
  &opt_service, &opt_service, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
  {"password", 'p', "Root password",
  &opt_password, &opt_password, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
  {"port", 'P', "mysql port",
  &opt_port, &opt_port, 0, GET_INT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
72
  {"socket", 'W', 
73 74 75 76 77 78 79 80 81 82 83
  "named pipe name (if missing, it will be set the same as service)",
  &opt_socket, &opt_socket, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
  {"default-user", 'D', "Create default user",
  &opt_default_user, &opt_default_user, 0 , GET_BOOL, OPT_ARG, 0, 0, 0, 0, 0, 0},
  {"allow-remote-root-access", 'R', 
  "Allows remote access from network for user root",
  &opt_allow_remote_root_access, &opt_allow_remote_root_access, 0 , GET_BOOL, 
  OPT_ARG, 0, 0, 0, 0, 0, 0},
  {"skip-networking", 'N', "Do not use TCP connections, use pipe instead",
  &opt_skip_networking, &opt_skip_networking, 0 , GET_BOOL, OPT_ARG, 0, 0, 0, 0,
  0, 0},
84 85
  {"silent", 's', "Print less information", &opt_silent,
   &opt_silent, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
86 87 88 89 90 91 92 93 94 95 96 97
  {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
};


static my_bool
get_one_option(int optid, 
   const struct my_option *opt __attribute__ ((unused)),
   char *argument __attribute__ ((unused)))
{
  DBUG_ENTER("get_one_option");
  switch (optid) {
  case '?':
98
    printf("%s\n", USAGETEXT);
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
    my_print_help(my_long_options);
    exit(0);
    break;
  }
  DBUG_RETURN(0);
}


static void die(const char *fmt, ...)
{
  va_list args;
  DBUG_ENTER("die");

  /* Print the error message */
  va_start(args, fmt);
114 115 116
  fprintf(stderr, "FATAL ERROR: ");
  vfprintf(stderr, fmt, args);
  fputc('\n', stderr);
117 118 119 120 121
  if (verbose_errors)
  {
   fprintf(stderr,
   "http://kb.askmonty.org/v/installation-issues-on-windows contains some help\n"
   "for solving the most common problems.  If this doesn't help you, please\n"
122 123
   "leave a comment in the Knowledgebase or file a bug report at\n"
   "http://mariadb.org/jira");
124
  }
125
  fflush(stderr);
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
  va_end(args);
  my_end(0);
  exit(1);
}


static void verbose(const char *fmt, ...)
{
  va_list args;

  if (opt_silent)
    return;

  /* Print the verbose message */
  va_start(args, fmt);
141 142 143
  vfprintf(stdout, fmt, args);
  fputc('\n', stdout);
  fflush(stdout);
144 145 146 147 148 149 150 151 152 153
  va_end(args);
}


int main(int argc, char **argv)
{
  int error;
  char self_name[FN_REFLEN];
  char *p;

154
  MY_INIT(argv[0]);
155 156
  GetModuleFileName(NULL, self_name, FN_REFLEN);
  strcpy(mysqld_path,self_name);
157 158
  p= strrchr(mysqld_path, FN_LIBCHAR);
  if (p)
159 160 161 162 163 164
  {
    strcpy(p, "\\mysqld.exe");
  }

  if ((error= handle_options(&argc, &argv, my_long_options, get_one_option)))
    exit(error);
165
  if (!opt_datadir)
166 167
  {
    my_print_help(my_long_options);
168
    die("parameter --datadir=# is mandatory");
169
  }
170 171 172 173 174

  /* Print some help on errors */
  verbose_errors= TRUE;

  if (!opt_os_user)
175 176
  {
    opt_os_user= default_os_user;
177
    opt_os_password= NULL;
178 179 180
  }
  /* Workaround WiX bug (strip possible quote character at the end of path) */
  size_t len= strlen(opt_datadir);
181
  if (len > 0)
182
  {
183
    if (opt_datadir[len-1] == '"')
184 185 186 187 188
    {
      opt_datadir[len-1]= 0;
    }
  }
  GetFullPathName(opt_datadir, FN_REFLEN, datadir_buffer, NULL);
189
  opt_datadir= datadir_buffer;
190 191 192 193 194 195 196 197 198 199 200 201 202

  if (create_db_instance())
  {
    die("database creation failed");
  }

  printf("Creation of the database was successfull");
  return 0;
}



/**
203
  Convert slashes in paths into MySQL-compatible form
204
*/
205

206 207
static void convert_slashes(char *s)
{
208 209
  for (; *s ; s++)
   if (*s == '\\')
210
     *s= '/';
211 212 213 214
}


/**
215
  Calculate basedir from mysqld.exe path.
216
  Basedir assumed to be is one level up from the mysqld.exe directory location.
217
  E.g basedir for C:\my\bin\mysqld.exe would be C:\my
218
*/
219

220 221 222 223
static void get_basedir(char *basedir, int size, const char *mysqld_path)
{
  strcpy_s(basedir, size,  mysqld_path);
  convert_slashes(basedir);
224 225
  char *p= strrchr(basedir,'/');
  if (p)
226 227
  {
    *p = 0;
228 229 230
    p= strrchr(basedir, '/');
    if (p)
      *p= 0;
231 232 233 234 235 236 237 238 239
  }
}


/**
  Allocate and initialize command line for mysqld --bootstrap.
 The resulting string is passed to popen, so it has a lot of quoting
 quoting around the full string plus quoting around parameters with spaces.
*/
240

241 242 243 244 245 246 247 248 249
static char *init_bootstrap_command_line(char *cmdline, size_t size)
{
  char basedir[MAX_PATH];
  get_basedir(basedir, sizeof(basedir), mysqld_path);

  my_snprintf(cmdline, size-1, 
    "\"\"%s\" --no-defaults --bootstrap"
    " \"--language=%s\\share\\english\""
    " --basedir=. --datadir=. --default-storage-engine=myisam"
250
    " --max_allowed_packet=9M --loose-skip-innodb"
251 252 253 254 255 256 257
    " --net-buffer-length=16k\"", mysqld_path, basedir);
  return cmdline;
}


/**
  Create my.ini in  current directory (this is assumed to be
258
  data directory as well).
259
*/
260

261 262 263 264 265 266 267 268 269
static int create_myini()
{
  my_bool enable_named_pipe= FALSE;
  printf("Creating my.ini file\n");

  char path_buf[MAX_PATH];
  GetCurrentDirectory(MAX_PATH, path_buf);

  /* Create ini file. */
270 271
  FILE *myini= fopen("my.ini","wt");
  if (!myini)
272 273 274
  {
    die("Cannot create my.ini in data directory");
  }
275 276

  /* Write out server settings. */
277 278 279 280 281 282
  fprintf(myini, "[mysqld]\n");
  convert_slashes(path_buf);
  fprintf(myini, "datadir=%s\n", path_buf);
  if (opt_skip_networking)
  {
    fprintf(myini,"skip-networking\n");
283
    if (!opt_socket)
284 285 286 287 288
      opt_socket= opt_service;
  }
  enable_named_pipe= (my_bool) 
    ((opt_socket && opt_socket[0]) || opt_skip_networking);

289
  if (enable_named_pipe)
290 291 292 293
  {
    fprintf(myini,"enable-named-pipe\n");
  }

294
  if (opt_socket && opt_socket[0])
295 296 297 298 299 300 301 302 303 304
  {
    fprintf(myini, "socket=%s\n", opt_socket);
  }
  if (opt_port)
  {
    fprintf(myini,"port=%d\n", opt_port);
  }

  /* Write out client settings. */
  fprintf(myini, "[client]\n");
305 306 307

  /* Used for named pipes */
  if (opt_socket && opt_socket[0])
308
    fprintf(myini,"socket=%s\n",opt_socket);
309
  if (opt_skip_networking)
310
    fprintf(myini,"protocol=pipe\n");
311
  else if (opt_port)
312 313 314 315 316 317 318
    fprintf(myini,"port=%d\n",opt_port);
  fclose(myini);
  return 0;
}


static const char update_root_passwd_part1[]=
319
  "UPDATE mysql.user SET Password = PASSWORD(";
320
static const char update_root_passwd_part2[]=
321
  ") where User='root';\n";
322 323 324
static const char remove_default_user_cmd[]= 
  "DELETE FROM mysql.user where User='';\n";
static const char allow_remote_root_access_cmd[]=
325
  "CREATE TEMPORARY TABLE tmp_user LIKE user;\n"
326 327 328 329 330 331 332 333
  "INSERT INTO tmp_user SELECT * from user where user='root' "
    " AND host='localhost';\n"
  "UPDATE tmp_user SET host='%';\n"
  "INSERT INTO user SELECT * FROM tmp_user;\n"
  "DROP TABLE tmp_user;\n";
static const char end_of_script[]="-- end.";

/* Register service. Assume my.ini is in datadir */
334

335 336 337 338 339 340
static int register_service()
{
  char buf[3*MAX_PATH +32]; /* path to mysqld.exe, to my.ini, service name */
  SC_HANDLE sc_manager, sc_service;

  size_t datadir_len= strlen(opt_datadir);
341
  const char *backslash_after_datadir= "\\";
342

343 344
  if (datadir_len && opt_datadir[datadir_len-1] == '\\')
    backslash_after_datadir= "";
345

346
  verbose("Registering service '%s'", opt_service);
347 348 349 350 351
  my_snprintf(buf, sizeof(buf)-1,
    "\"%s\" \"--defaults-file=%s%smy.ini\" \"%s\"" ,  mysqld_path, opt_datadir, 
    backslash_after_datadir, opt_service);

  /* Get a handle to the SCM database. */ 
352
  sc_manager= OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS);
353 354
  if (!sc_manager) 
  {
355
    die("OpenSCManager failed (%u)\n", GetLastError());
356 357 358
  }

  /* Create the service. */
359
  sc_service= CreateService(sc_manager, opt_service,  opt_service,
360 361 362 363 364 365
    SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, 
    SERVICE_ERROR_NORMAL, buf, NULL, NULL, NULL, opt_os_user, opt_os_password);

  if (!sc_service) 
  {
    CloseServiceHandle(sc_manager);
366
    die("CreateService failed (%u)", GetLastError());
367 368
  }

369
  SERVICE_DESCRIPTION sd= { "MariaDB database server" };
370 371 372 373 374 375 376 377 378 379
  ChangeServiceConfig2(sc_service, SERVICE_CONFIG_DESCRIPTION, &sd);
  CloseServiceHandle(sc_service); 
  CloseServiceHandle(sc_manager);
  return 0;
}


static void clean_directory(const char *dir)
{
  char dir2[MAX_PATH+2];
380
  *(strmake(dir2, dir, MAX_PATH+1)+1)= 0;
381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396

  SHFILEOPSTRUCT fileop;
  fileop.hwnd= NULL;    /* no status display */
  fileop.wFunc= FO_DELETE;  /* delete operation */
  fileop.pFrom= dir2;  /* source file name as double null terminated string */
  fileop.pTo= NULL;    /* no destination needed */
  fileop.fFlags= FOF_NOCONFIRMATION|FOF_SILENT;  /* do not prompt the user */


  fileop.fAnyOperationsAborted= FALSE;
  fileop.lpszProgressTitle= NULL;
  fileop.hNameMappings= NULL;

  SHFileOperation(&fileop);
}

397

398 399 400 401
/*
  Define directory permission to have inheritable all access for a user
  (defined as username or group string or as SID)
*/
402

403 404 405 406 407 408 409 410
static int set_directory_permissions(const char *dir, const char *os_user)
{

   struct{
        TOKEN_USER tokenUser;
        BYTE buffer[SECURITY_MAX_SID_SIZE];
   } tokenInfoBuffer;

411
  HANDLE hDir= CreateFile(dir,READ_CONTROL|WRITE_DAC,0,NULL,OPEN_EXISTING,
412
    FILE_FLAG_BACKUP_SEMANTICS,NULL);
413
  if (hDir == INVALID_HANDLE_VALUE) 
414 415
    return -1;  
  ACL* pOldDACL;
416
  SECURITY_DESCRIPTOR* pSD= NULL; 
417
  EXPLICIT_ACCESS ea={0};
418 419
  BOOL isWellKnownSID= FALSE;
  WELL_KNOWN_SID_TYPE wellKnownSidType = WinNullSid;
420
  PSID pSid= NULL;
421

422 423
  GetSecurityInfo(hDir, SE_FILE_OBJECT , DACL_SECURITY_INFORMATION,NULL, NULL,
    &pOldDACL, NULL, (void**)&pSD); 
424

425
  if (os_user)
426
  {
427 428 429 430
    /* Check for 3 predefined service users 
       They might have localized names in non-English Windows, thus they need
       to be handled using well-known SIDs.
    */
431
    if (stricmp(os_user, "NT AUTHORITY\\NetworkService") == 0)
432 433 434
    {
      wellKnownSidType= WinNetworkServiceSid;
    }
435
    else if (stricmp(os_user, "NT AUTHORITY\\LocalService") == 0)
436 437 438
    {
      wellKnownSidType= WinLocalServiceSid;
    }
439
    else if (stricmp(os_user, "NT AUTHORITY\\LocalSystem") == 0)
440 441 442 443
    {
      wellKnownSidType= WinLocalSystemSid;
    }

444
    if (wellKnownSidType != WinNullSid)
445
    {
446 447
      DWORD size= SECURITY_MAX_SID_SIZE;
      pSid= (PSID)tokenInfoBuffer.buffer;
448 449 450 451 452
      if (!CreateWellKnownSid(wellKnownSidType, NULL, pSid,
        &size))
      {
        return 1;
      }
453 454
      ea.Trustee.TrusteeForm= TRUSTEE_IS_SID;
      ea.Trustee.ptstrName= (LPTSTR)pSid;
455 456 457
    }
    else
    {
458 459
      ea.Trustee.TrusteeForm= TRUSTEE_IS_NAME;
      ea.Trustee.ptstrName= (LPSTR)os_user;
460
    }
461 462 463 464
  }
  else
  {
    HANDLE token;
465
    if (OpenProcessToken(GetCurrentProcess(),TOKEN_QUERY, &token))
466 467
    {

468
      DWORD length= (DWORD) sizeof(tokenInfoBuffer);
469 470 471 472 473 474
      if (GetTokenInformation(token, TokenUser, &tokenInfoBuffer, 
        length, &length))
      {
        pSid= tokenInfoBuffer.tokenUser.User.Sid;
      }
    }
475
    if (!pSid)
476
      return 0;
477 478
    ea.Trustee.TrusteeForm= TRUSTEE_IS_SID;
    ea.Trustee.ptstrName= (LPTSTR)pSid;
479
  }
480 481 482 483 484 485 486
  ea.grfAccessMode= GRANT_ACCESS;
  ea.grfAccessPermissions= GENERIC_ALL; 
  ea.grfInheritance= CONTAINER_INHERIT_ACE|OBJECT_INHERIT_ACE; 
  ea.Trustee.TrusteeType= TRUSTEE_IS_UNKNOWN; 
  ACL* pNewDACL= 0; 
  DWORD err= SetEntriesInAcl(1,&ea,pOldDACL,&pNewDACL); 
  if (pNewDACL)
487 488 489 490
  {
    SetSecurityInfo(hDir,SE_FILE_OBJECT,DACL_SECURITY_INFORMATION,NULL, NULL,
      pNewDACL, NULL);
  }
491
  if (pSD != NULL) 
492
    LocalFree((HLOCAL) pSD); 
493
  if (pNewDACL != NULL) 
494
    LocalFree((HLOCAL) pNewDACL);
495 496 497 498
  CloseHandle(hDir); 
  return 0;
}

499

500 501 502 503
/* 
  Give directory permissions for special service user NT SERVICE\servicename
  this user is available only on Win7 and later.
*/
504

505 506 507 508
void grant_directory_permissions_to_service()
{
  char service_user[MAX_PATH+ 12];
  OSVERSIONINFO info;
509
  info.dwOSVersionInfoSize= sizeof(info);
510 511 512 513 514 515 516 517 518 519 520
  GetVersionEx(&info);
  if (info.dwMajorVersion >6 || 
    (info.dwMajorVersion== 6 && info.dwMinorVersion > 0)
    && opt_service)
  {
    my_snprintf(service_user,sizeof(service_user), "NT SERVICE\\%s", 
      opt_service);
    set_directory_permissions(opt_datadir, service_user);
  }
}

521

522
/* Create database instance (including registering as service etc) .*/
523

524 525
static int create_db_instance()
{
526
  int ret= 0;
527 528 529 530 531 532 533 534 535 536
  char cwd[MAX_PATH];
  DWORD cwd_len= MAX_PATH;
  char cmdline[3*MAX_PATH];
  FILE *in;

  verbose("Running bootstrap");

  GetCurrentDirectory(cwd_len, cwd);
  CreateDirectory(opt_datadir, NULL); /*ignore error, it might already exist */

537
  if (!SetCurrentDirectory(opt_datadir))
538
  {
539
    die("Cannot set current directory to '%s'\n",opt_datadir);
540 541 542 543 544 545
    return -1;
  }

  CreateDirectory("mysql",NULL);
  CreateDirectory("test", NULL);

546 547 548 549
  /*
    Set data directory permissions for both current user and 
    default_os_user (the one who runs services).
  */
550 551 552
  set_directory_permissions(opt_datadir, NULL);
  set_directory_permissions(opt_datadir, default_os_user);

553
  /* Do mysqld --bootstrap. */
554 555 556 557
  init_bootstrap_command_line(cmdline, sizeof(cmdline));
  /* verbose("Executing %s", cmdline); */

  in= popen(cmdline, "wt");
558
  if (!in)
559 560 561 562 563
    goto end;

  if (fwrite("use mysql;\n",11,1, in) != 1)
  {
    verbose("ERROR: Cannot write to mysqld's stdin");
564
    ret= 1;
565 566 567 568 569 570 571 572 573 574 575 576
    goto end;
  }

  /* Write the bootstrap script to stdin. */
  if (fwrite(mysql_bootstrap_sql, strlen(mysql_bootstrap_sql), 1, in) != 1)
  {
    verbose("ERROR: Cannot write to mysqld's stdin");
    ret= 1;
    goto end;
  }

  /* Remove default user, if requested. */
577
  if (!opt_default_user)
578 579 580 581 582 583
  {
    verbose("Removing default user",remove_default_user_cmd);
    fputs(remove_default_user_cmd, in);
    fflush(in);
  }

584
  if (opt_allow_remote_root_access)
585 586 587 588 589 590 591
  {
     verbose("Allowing remote access for user root",remove_default_user_cmd);
     fputs(allow_remote_root_access_cmd,in);
     fflush(in);
  }

  /* Change root password if requested. */
592
  if (opt_password && opt_password[0])
593
  {
594
    verbose("Setting root password",remove_default_user_cmd);
595
    fputs(update_root_passwd_part1, in);
596 597 598 599 600 601 602 603 604

    /* Use hex encoding for password, to avoid escaping problems.*/
    fputc('0', in);
    fputc('x', in);
    for(int i= 0; opt_password[i]; i++)
    {
      fprintf(in,"%02x",opt_password[i]);
    }

605 606 607 608 609 610 611 612 613 614 615 616 617 618 619
    fputs(update_root_passwd_part2, in);
    fflush(in);
  }

  /*
    On some reason, bootstrap chokes if last command sent via stdin ends with 
    newline, so we supply a dummy comment, that does not end with newline.
  */
  fputs(end_of_script, in);
  fflush(in);

  /* Check if bootstrap has completed successfully. */
  ret= pclose(in);
  if (ret)
  {
620
    verbose("mysqld returned error %d in pclose",ret);
621 622 623 624 625
    goto end;
  }

  /* Create my.ini file in data directory.*/
  ret= create_myini();
626
  if (ret)
627 628 629
    goto end;

  /* Register service if requested. */
630
  if (opt_service && opt_service[0])
631 632 633
  {
    ret= register_service();
    grant_directory_permissions_to_service();
634
    if (ret)
635 636 637 638 639 640 641 642 643 644 645
      goto end;
  }

end:
  if (ret)
  {
    SetCurrentDirectory(cwd);
    clean_directory(opt_datadir);
  }
  return ret;
}