mariadb/sql/mysql_install_db.cc
Monty 5d6ad2ad66 Added 'const' to arguments in get_one_option and find_typeset()
One should not change the program arguments!
This change also reduces warnings from the icc compiler.

Almost all changes are just syntax changes (adding const to
'get_one_option function' declarations).

Other changes:
- Added a few cast of 'argument' from 'const char*' to 'char *'. This
  was mainly in calls to 'external' functions we don't have control of.
- Ensure that all reset of 'password command line argument' are similar.
  (In almost all cases it was just adding a comment and a cast)
- In mysqlbinlog.cc and mysqld.cc there was a few cases that changed
  the command line argument. These places where changed to instead allocate
  the option in a MEM_ROOT to avoid changing the argument. Some of this
  code was changed to ensure that different programs did parsing the
  same way. Added a test case for the changes in mysqlbinlog.cc
- Changed a few variables that took their value from command line options
  from 'char *' to 'const char *'.
2021-02-08 12:16:29 +02:00

749 lines
20 KiB
C++

/* Copyright (C) 2010-2011 Monty Program Ab & Vladislav Vaintroub
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */
/*
mysql_install_db creates a new database instance (optionally as service)
on Windows.
*/
#define DONT_DEFINE_VOID
#include "mariadb.h"
#include <my_getopt.h>
#include <m_string.h>
#include <windows.h>
#include <shellapi.h>
#include <accctrl.h>
#include <aclapi.h>
struct IUnknown;
#include <shlwapi.h>
#define USAGETEXT \
"mysql_install_db.exe Ver 1.00 for Windows\n" \
"Copyright (C) 2010-2011 Monty Program Ab & Vladislav Vaintroub\n" \
"This software comes with ABSOLUTELY NO WARRANTY. This is free software,\n" \
"and you are welcome to modify and redistribute it under the GPL v2 license\n" \
"Usage: mysql_install_db.exe [OPTIONS]\n" \
"OPTIONS:"
extern "C" const char* mysql_bootstrap_sql[];
static char default_os_user[]= "NT AUTHORITY\\NetworkService";
static char default_datadir[MAX_PATH];
static int create_db_instance();
static uint opt_silent;
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 int opt_innodb_page_size;
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;
static my_bool opt_verbose_bootstrap;
static my_bool verbose_errors;
#define DEFAULT_INNODB_PAGE_SIZE 16*1024
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},
{"service", 'S', "Name of the Windows service",
&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},
{"socket", 'W',
"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},
{ "innodb-page-size", 'i', "Page size for innodb",
&opt_innodb_page_size, &opt_innodb_page_size, 0, GET_INT, REQUIRED_ARG, DEFAULT_INNODB_PAGE_SIZE, 1*1024, 64*1024, 0, 0, 0 },
{"silent", 's', "Print less information", &opt_silent,
&opt_silent, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
{"verbose-bootstrap", 'o', "Include mysqld bootstrap output",&opt_verbose_bootstrap,
&opt_verbose_bootstrap, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
};
static my_bool
get_one_option(const struct my_option *opt, const char *, const char *)
{
DBUG_ENTER("get_one_option");
switch (opt->id) {
case '?':
printf("%s\n", USAGETEXT);
my_print_help(my_long_options);
exit(0);
break;
}
DBUG_RETURN(0);
}
ATTRIBUTE_NORETURN static void die(const char *fmt, ...)
{
va_list args;
DBUG_ENTER("die");
/* Print the error message */
va_start(args, fmt);
fprintf(stderr, "FATAL ERROR: ");
vfprintf(stderr, fmt, args);
fputc('\n', stderr);
if (verbose_errors)
{
fprintf(stderr,
"https://mariadb.com/kb/en/installation-issues-on-windows contains some help\n"
"for solving the most common problems. If this doesn't help you, please\n"
"leave a comment in the Knowledge Base or file a bug report at\n"
"https://jira.mariadb.org");
}
fflush(stderr);
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);
vfprintf(stdout, fmt, args);
fputc('\n', stdout);
fflush(stdout);
va_end(args);
}
int main(int argc, char **argv)
{
int error;
char self_name[FN_REFLEN];
char *p;
MY_INIT(argv[0]);
GetModuleFileName(NULL, self_name, FN_REFLEN);
strcpy(mysqld_path,self_name);
p= strrchr(mysqld_path, FN_LIBCHAR);
if (p)
{
strcpy(p, "\\mysqld.exe");
}
if ((error= handle_options(&argc, &argv, my_long_options, get_one_option)))
exit(error);
if (!opt_datadir)
{
/*
Figure out default data directory. It "data" directory, next to "bin" directory, where
mysql_install_db.exe resides.
*/
strcpy(default_datadir, self_name);
p = strrchr(default_datadir, FN_LIBCHAR);
if (p)
{
*p= 0;
p= strrchr(default_datadir, FN_LIBCHAR);
if (p)
*p= 0;
}
if (!p)
{
die("--datadir option not provided, and default datadir not found");
my_print_help(my_long_options);
}
strcat_s(default_datadir, "\\data");
opt_datadir= default_datadir;
printf("Default data directory is %s\n",opt_datadir);
}
/* Print some help on errors */
verbose_errors= TRUE;
if (!opt_os_user)
{
opt_os_user= default_os_user;
opt_os_password= NULL;
}
/* Workaround WiX bug (strip possible quote character at the end of path) */
size_t len= strlen(opt_datadir);
if (len > 0)
{
if (opt_datadir[len-1] == '"')
{
opt_datadir[len-1]= 0;
}
}
GetFullPathName(opt_datadir, FN_REFLEN, datadir_buffer, NULL);
opt_datadir= datadir_buffer;
if (create_db_instance())
{
die("database creation failed");
}
printf("Creation of the database was successful\n");
return 0;
}
/**
Convert slashes in paths into MySQL-compatible form
*/
static void convert_slashes(char *s)
{
for (; *s ; s++)
if (*s == '\\')
*s= '/';
}
/**
Calculate basedir from mysqld.exe path.
Basedir assumed to be is one level up from the mysqld.exe directory location.
E.g basedir for C:\my\bin\mysqld.exe would be C:\my
*/
static void get_basedir(char *basedir, int size, const char *mysqld_path)
{
strcpy_s(basedir, size, mysqld_path);
convert_slashes(basedir);
char *p= strrchr(basedir,'/');
if (p)
{
*p = 0;
p= strrchr(basedir, '/');
if (p)
*p= 0;
}
}
#define STR(s) _STR(s)
#define _STR(s) #s
static char *get_plugindir()
{
static char plugin_dir[2*MAX_PATH];
get_basedir(plugin_dir, sizeof(plugin_dir), mysqld_path);
strcat(plugin_dir, "/" STR(INSTALL_PLUGINDIR));
if (access(plugin_dir, 0) == 0)
return plugin_dir;
return NULL;
}
/**
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.
*/
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 %s --innodb-page-size=%d --bootstrap"
" \"--lc-messages-dir=%s/share\""
" --basedir=. --datadir=. --default-storage-engine=myisam"
" --max_allowed_packet=9M "
" --net-buffer-length=16k\"", mysqld_path,
opt_verbose_bootstrap ? "--console" : "", opt_innodb_page_size, basedir);
return cmdline;
}
/**
Create my.ini in current directory (this is assumed to be
data directory as well).
*/
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. */
FILE *myini= fopen("my.ini","wt");
if (!myini)
{
die("Can't create my.ini in data directory");
}
/* Write out server settings. */
fprintf(myini, "[mysqld]\n");
convert_slashes(path_buf);
fprintf(myini, "datadir=%s\n", path_buf);
if (opt_skip_networking)
{
fprintf(myini,"skip-networking\n");
if (!opt_socket)
opt_socket= opt_service;
}
enable_named_pipe= (my_bool)
((opt_socket && opt_socket[0]) || opt_skip_networking);
if (enable_named_pipe)
{
fprintf(myini,"named-pipe=ON\n");
}
if (opt_socket && opt_socket[0])
{
fprintf(myini, "socket=%s\n", opt_socket);
}
if (opt_port)
{
fprintf(myini,"port=%d\n", opt_port);
}
if (opt_innodb_page_size != DEFAULT_INNODB_PAGE_SIZE)
{
fprintf(myini, "innodb-page-size=%d\n", opt_innodb_page_size);
}
/* Write out client settings. */
fprintf(myini, "[client]\n");
/* Used for named pipes */
if (opt_socket && opt_socket[0])
fprintf(myini,"socket=%s\n",opt_socket);
if (opt_skip_networking)
fprintf(myini,"protocol=pipe\n");
else if (opt_port)
fprintf(myini,"port=%d\n",opt_port);
char *plugin_dir = get_plugindir();
if (plugin_dir)
fprintf(myini, "plugin-dir=%s\n", plugin_dir);
fclose(myini);
return 0;
}
static const char update_root_passwd_part1[]=
"UPDATE mysql.global_priv SET priv=json_set(priv,"
"'$.password_last_changed', UNIX_TIMESTAMP(),"
"'$.plugin','mysql_native_password',"
"'$.authentication_string',PASSWORD(";
static const char update_root_passwd_part2[]=
")) where User='root';\n";
static const char remove_default_user_cmd[]=
"DELETE FROM mysql.user where User='';\n";
static const char allow_remote_root_access_cmd[]=
"CREATE TEMPORARY TABLE tmp_user LIKE global_priv;\n"
"INSERT INTO tmp_user SELECT * from global_priv where user='root' "
" AND host='localhost';\n"
"UPDATE tmp_user SET host='%';\n"
"INSERT INTO global_priv 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 */
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);
const char *backslash_after_datadir= "\\";
if (datadir_len && opt_datadir[datadir_len-1] == '\\')
backslash_after_datadir= "";
verbose("Registering service '%s'", opt_service);
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. */
sc_manager= OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (!sc_manager)
{
die("OpenSCManager failed (%u)\n", GetLastError());
}
/* Create the service. */
sc_service= CreateService(sc_manager, opt_service, opt_service,
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);
die("CreateService failed (%u)", GetLastError());
}
char description[] = "MariaDB database server";
SERVICE_DESCRIPTION sd= { description };
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 + 4]= {};
snprintf(dir2, MAX_PATH+2, "%s\\*", dir);
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);
}
/*
Define directory permission to have inheritable all access for a user
(defined as username or group string or as SID)
*/
static int set_directory_permissions(const char *dir, const char *os_user)
{
struct{
TOKEN_USER tokenUser;
BYTE buffer[SECURITY_MAX_SID_SIZE];
} tokenInfoBuffer;
HANDLE hDir= CreateFile(dir,READ_CONTROL|WRITE_DAC,0,NULL,OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,NULL);
if (hDir == INVALID_HANDLE_VALUE)
return -1;
ACL* pOldDACL;
SECURITY_DESCRIPTOR* pSD= NULL;
EXPLICIT_ACCESS ea={0};
WELL_KNOWN_SID_TYPE wellKnownSidType = WinNullSid;
PSID pSid= NULL;
GetSecurityInfo(hDir, SE_FILE_OBJECT , DACL_SECURITY_INFORMATION,NULL, NULL,
&pOldDACL, NULL, (void**)&pSD);
if (os_user)
{
/* 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.
*/
if (stricmp(os_user, "NT AUTHORITY\\NetworkService") == 0)
{
wellKnownSidType= WinNetworkServiceSid;
}
else if (stricmp(os_user, "NT AUTHORITY\\LocalService") == 0)
{
wellKnownSidType= WinLocalServiceSid;
}
else if (stricmp(os_user, "NT AUTHORITY\\LocalSystem") == 0)
{
wellKnownSidType= WinLocalSystemSid;
}
if (wellKnownSidType != WinNullSid)
{
DWORD size= SECURITY_MAX_SID_SIZE;
pSid= (PSID)tokenInfoBuffer.buffer;
if (!CreateWellKnownSid(wellKnownSidType, NULL, pSid,
&size))
{
return 1;
}
ea.Trustee.TrusteeForm= TRUSTEE_IS_SID;
ea.Trustee.ptstrName= (LPTSTR)pSid;
}
else
{
ea.Trustee.TrusteeForm= TRUSTEE_IS_NAME;
ea.Trustee.ptstrName= (LPSTR)os_user;
}
}
else
{
HANDLE token;
if (OpenProcessToken(GetCurrentProcess(),TOKEN_QUERY, &token))
{
DWORD length= (DWORD) sizeof(tokenInfoBuffer);
if (GetTokenInformation(token, TokenUser, &tokenInfoBuffer,
length, &length))
{
pSid= tokenInfoBuffer.tokenUser.User.Sid;
}
}
if (!pSid)
return 0;
ea.Trustee.TrusteeForm= TRUSTEE_IS_SID;
ea.Trustee.ptstrName= (LPTSTR)pSid;
}
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;
SetEntriesInAcl(1,&ea,pOldDACL,&pNewDACL);
if (pNewDACL)
{
SetSecurityInfo(hDir,SE_FILE_OBJECT,DACL_SECURITY_INFORMATION,NULL, NULL,
pNewDACL, NULL);
}
if (pSD != NULL)
LocalFree((HLOCAL) pSD);
if (pNewDACL != NULL)
LocalFree((HLOCAL) pNewDACL);
CloseHandle(hDir);
return 0;
}
/* Create database instance (including registering as service etc) .*/
static int create_db_instance()
{
int ret= 0;
char cwd[MAX_PATH];
DWORD cwd_len= MAX_PATH;
char cmdline[3*MAX_PATH];
FILE *in;
bool created_datadir= false;
DWORD last_error;
verbose("Running bootstrap");
GetCurrentDirectory(cwd_len, cwd);
/* Create datadir and datadir/mysql, if they do not already exist. */
if (CreateDirectory(opt_datadir, NULL))
{
created_datadir= true;
}
else if (GetLastError() != ERROR_ALREADY_EXISTS)
{
last_error = GetLastError();
switch(last_error)
{
case ERROR_ACCESS_DENIED:
die("Can't create data directory '%s' (access denied)\n",
opt_datadir);
break;
case ERROR_PATH_NOT_FOUND:
die("Can't create data directory '%s' "
"(one or more intermediate directories do not exist)\n",
opt_datadir);
break;
default:
die("Can't create data directory '%s', last error %u\n",
opt_datadir, last_error);
break;
}
}
if (!SetCurrentDirectory(opt_datadir))
{
last_error = GetLastError();
switch (last_error)
{
case ERROR_DIRECTORY:
die("Can't set current directory to '%s', the path is not a valid directory \n",
opt_datadir);
break;
default:
die("Can' set current directory to '%s', last error %u\n",
opt_datadir, last_error);
break;
}
}
if (!PathIsDirectoryEmpty(opt_datadir))
{
fprintf(stderr,"ERROR : Data directory %s is not empty."
" Only new or empty existing directories are accepted for --datadir\n",opt_datadir);
exit(1);
}
if (!CreateDirectory("mysql",NULL))
{
last_error = GetLastError();
DWORD attributes;
switch(last_error)
{
case ERROR_ACCESS_DENIED:
die("Can't create subdirectory 'mysql' in '%s' (access denied)\n",opt_datadir);
break;
case ERROR_ALREADY_EXISTS:
attributes = GetFileAttributes("mysql");
if (attributes == INVALID_FILE_ATTRIBUTES)
die("GetFileAttributes() failed for existing file '%s\\mysql', last error %u",
opt_datadir, GetLastError());
else if (!(attributes & FILE_ATTRIBUTE_DIRECTORY))
die("File '%s\\mysql' exists, but it is not a directory", opt_datadir);
break;
}
}
/*
Set data directory permissions for both current user and
default_os_user (the one who runs services).
*/
set_directory_permissions(opt_datadir, NULL);
set_directory_permissions(opt_datadir, default_os_user);
/* Do mysqld --bootstrap. */
init_bootstrap_command_line(cmdline, sizeof(cmdline));
if(opt_verbose_bootstrap)
printf("Executing %s\n", cmdline);
in= popen(cmdline, "wt");
if (!in)
goto end;
if (setvbuf(in, NULL, _IONBF, 0))
{
verbose("WARNING: Can't disable buffering on mysqld's stdin");
}
if (fwrite("use mysql;\n",11,1, in) != 1)
{
verbose("ERROR: Can't write to mysqld's stdin");
ret= 1;
goto end;
}
int i;
for (i=0; mysql_bootstrap_sql[i]; i++)
{
/* Write the bootstrap script to stdin. */
if (fwrite(mysql_bootstrap_sql[i], strlen(mysql_bootstrap_sql[i]), 1, in) != 1)
{
verbose("ERROR: Can't write to mysqld's stdin");
ret= 1;
goto end;
}
}
/* Remove default user, if requested. */
if (!opt_default_user)
{
verbose("Removing default user",remove_default_user_cmd);
fputs(remove_default_user_cmd, in);
fflush(in);
}
if (opt_allow_remote_root_access)
{
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. */
if (opt_password && opt_password[0])
{
verbose("Setting root password",remove_default_user_cmd);
fputs(update_root_passwd_part1, in);
/* 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]);
}
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)
{
verbose("mysqld returned error %d in pclose",ret);
goto end;
}
/* Create my.ini file in data directory.*/
ret= create_myini();
if (ret)
goto end;
/* Register service if requested. */
if (opt_service && opt_service[0])
{
ret= register_service();
if (ret)
goto end;
}
end:
if (ret)
{
SetCurrentDirectory(cwd);
clean_directory(opt_datadir);
if (created_datadir)
RemoveDirectory(opt_datadir);
}
return ret;
}