diff options
Diffstat (limited to 'sql/mysql_install_db.cc')
-rw-r--r-- | sql/mysql_install_db.cc | 983 |
1 files changed, 983 insertions, 0 deletions
diff --git a/sql/mysql_install_db.cc b/sql/mysql_install_db.cc new file mode 100644 index 00000000..5294b917 --- /dev/null +++ b/sql/mysql_install_db.cc @@ -0,0 +1,983 @@ +/* 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 <password.h> + +#include <windows.h> +#include <shellapi.h> +#include <accctrl.h> +#include <aclapi.h> +#include <ntsecapi.h> +#include <sddl.h> +struct IUnknown; +#include <shlwapi.h> +#include <winservice.h> + +#include <string> + +#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_datadir[MAX_PATH]; +static int create_db_instance(const char *datadir); +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 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; +static my_bool opt_large_pages; +static char *opt_config; + +#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}, + { "large-pages",'l', "Use large pages", &opt_large_pages, + &opt_large_pages, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"config",'c', "my.ini config template file", &opt_config, + &opt_config, 0, GET_STR, REQUIRED_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); + 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); +} + +static char full_config_path[MAX_PATH]; + +int main(int argc, char **argv) +{ + int error; + char self_name[MAX_PATH]; + char *p; + char *datadir = NULL; + MY_INIT(argv[0]); + GetModuleFileName(NULL, self_name, MAX_PATH); + 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_config != 0 && _access(opt_config, 04) != 0) + { + int err= errno; + switch(err) + { + case EACCES: + die("File %s can't be read", opt_config); + break; + case ENOENT: + die("File %s does not exist", opt_config); + break; + default: + die("Can't access file %s, errno %d",opt_config, err); + break; + } + } + if (opt_config) + { + DWORD dwret = GetFullPathName(opt_config, sizeof(full_config_path), full_config_path, NULL); + if (dwret == 0) + { + die("GetFullPathName failed, last error %u", GetLastError()); + } + else if (dwret > sizeof(full_config_path)) + { + die("Can't resolve the config file name, path too large"); + } + opt_config= full_config_path; + } + + if(opt_datadir) + datadir = opt_datadir; + + if (!datadir && opt_config) + { + for(auto section : {"server","mysqld"}) + { + auto ret = GetPrivateProfileStringA(section,"datadir", NULL, default_datadir, + sizeof(default_datadir)-1, opt_config); + if (ret) + { + datadir= default_datadir; + printf("Data directory (from config file) is %s\n",datadir); + break; + } + } + } + + if (!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"); + datadir= default_datadir; + printf("Default data directory is %s\n",datadir); + } + + DBUG_ASSERT(datadir); + + + /* Workaround WiX bug (strip possible quote character at the end of path) */ + size_t len= strlen(datadir); + if (len > 0) + { + if (datadir[len-1] == '"') + { + datadir[len-1]= 0; + } + if (datadir[0] == '"') + { + datadir++; + } + } + GetFullPathName(datadir, FN_REFLEN, datadir_buffer, NULL); + datadir= datadir_buffer; + + if (create_db_instance(datadir)) + { + 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, char replacement) +{ + for (; *s; s++) + if (*s == '\\' || *s == '/') + *s= replacement; +} + + +/** + 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, + char slash) +{ + 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, '/'); + safe_strcat(plugin_dir, sizeof(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) +{ + snprintf(cmdline, size - 1, + "\"\"%s\"" + " --defaults-file=my.ini" + " %s" + " --bootstrap" + " --datadir=." + " --loose-innodb-buffer-pool-size=20M" + "\"" + , mysqld_path, opt_verbose_bootstrap ? "--console" : ""); + return cmdline; +} + +static char my_ini_path[MAX_PATH]; + +static void write_myini_str(const char *key, const char* val, const char *section="mysqld") +{ + DBUG_ASSERT(my_ini_path[0]); + if (!WritePrivateProfileString(section, key, val, my_ini_path)) + { + die("Can't write to ini file key=%s, val=%s, section=%s, Windows error %u",key,val,section, + GetLastError()); + } +} + + +static void write_myini_int(const char* key, int val, const char* section = "mysqld") +{ + char buf[10]; + itoa(val, buf, 10); + write_myini_str(key, buf, section); +} + +/** + 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); + snprintf(my_ini_path,sizeof(my_ini_path), "%s\\my.ini", path_buf); + if (opt_config) + { + if (!CopyFile(opt_config, my_ini_path,TRUE)) + { + die("Can't copy %s to my.ini , last error %lu", opt_config, GetLastError()); + } + } + + /* Write out server settings. */ + convert_slashes(path_buf,'/'); + write_myini_str("datadir",path_buf); + + if (opt_skip_networking) + { + write_myini_str("skip-networking","ON"); + if (!opt_socket) + opt_socket= opt_service; + } + enable_named_pipe= (my_bool) + ((opt_socket && opt_socket[0]) || opt_skip_networking); + + if (enable_named_pipe) + { + write_myini_str("named-pipe","ON"); + } + + if (opt_socket && opt_socket[0]) + { + write_myini_str("socket", opt_socket); + } + if (opt_port) + { + write_myini_int("port", opt_port); + } + if (opt_innodb_page_size != DEFAULT_INNODB_PAGE_SIZE) + { + write_myini_int("innodb-page-size", opt_innodb_page_size); + } + if (opt_large_pages) + { + write_myini_str("large-pages","ON"); + } + + /* Write out client settings. */ + + /* Used for named pipes */ + if (opt_socket && opt_socket[0]) + write_myini_str("socket",opt_socket,"client"); + if (opt_skip_networking) + write_myini_str("protocol", "pipe", "client"); + else if (opt_port) + write_myini_int("port",opt_port,"client"); + + char *plugin_dir = get_plugindir(); + if (plugin_dir) + write_myini_str("plugin-dir", plugin_dir, "client"); + return 0; +} + + +static constexpr const char* update_root_passwd= + "UPDATE mysql.global_priv SET priv=json_set(priv," + "'$.password_last_changed', UNIX_TIMESTAMP()," + "'$.plugin','mysql_native_password'," + "'$.authentication_string','%s'," + "'$.auth_or', json_array(json_object(), json_object('plugin', 'gssapi','authentication_string','SID:BA'))" + ") where User= 'root';\n "; + +static constexpr char remove_default_user_cmd[]= + "DELETE FROM mysql.user where User='';\n"; +static constexpr 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."; + +/* +Add or remove privilege for a user +@param[in] account_name - user name, Windows style, e.g "NT SERVICE\mariadb", or ".\joe" +@param[in] privilege name - standard Windows privilege name, e.g "SeLockMemoryPrivilege" +@param[in] add - when true, add privilege, otherwise remove it + +In special case where privilege name is NULL, and add is false +all privileges for the user are removed. +*/ +static int handle_user_privileges(const char *account_name, const wchar_t *privilege_name, bool add) +{ + LSA_OBJECT_ATTRIBUTES attr{}; + LSA_HANDLE lsa_handle; + auto status= LsaOpenPolicy( + 0, &attr, POLICY_LOOKUP_NAMES | POLICY_CREATE_ACCOUNT, &lsa_handle); + if (status) + { + verbose("LsaOpenPolicy returned %lu", LsaNtStatusToWinError(status)); + return 1; + } + BYTE sidbuf[SECURITY_MAX_SID_SIZE]; + PSID sid= (PSID) sidbuf; + SID_NAME_USE name_use; + char domain_name[256]; + DWORD cbSid= sizeof(sidbuf); + DWORD cbDomain= sizeof(domain_name); + BOOL ok= LookupAccountNameA(0, account_name, sid, &cbSid, domain_name, + &cbDomain, &name_use); + if (!ok) + { + verbose("LsaOpenPolicy returned %lu", LsaNtStatusToWinError(status)); + return 1; + } + + if (privilege_name) + { + LSA_UNICODE_STRING priv{}; + priv.Buffer= (PWSTR) privilege_name; + priv.Length= (USHORT) wcslen(privilege_name) * sizeof(wchar_t); + priv.MaximumLength= priv.Length; + if (add) + { + status= LsaAddAccountRights(lsa_handle, sid, &priv, 1); + if (status) + { + verbose("LsaAddAccountRights returned %lu/%lu", status, + LsaNtStatusToWinError(status)); + return 1; + } + } + else + { + status= LsaRemoveAccountRights(lsa_handle, sid, FALSE, &priv, 1); + if (status) + { + verbose("LsaRemoveRights returned %lu/%lu", + LsaNtStatusToWinError(status)); + return 1; + } + } + } + else + { + DBUG_ASSERT(!add); + status= LsaRemoveAccountRights(lsa_handle, sid, TRUE, 0, 0); + } + LsaClose(lsa_handle); + return 0; +} + +/* Register service. Assume my.ini is in datadir */ + +static int register_service(const char *datadir, const char *user, const char *passwd) +{ + 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(datadir); + const char *backslash_after_datadir= "\\"; + + if (datadir_len && 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, 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, user, passwd); + + 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, + DWORD permission) +{ + + 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.Trustee.TrusteeType= TRUSTEE_IS_UNKNOWN; + ea.grfAccessMode= GRANT_ACCESS; + ea.grfAccessPermissions= permission; + ea.grfInheritance= CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE; + ACL *pNewDACL= 0; + + ACCESS_MASK access_mask; + if (GetEffectiveRightsFromAcl(pOldDACL, &ea.Trustee, &access_mask) != ERROR_SUCCESS + || (access_mask & permission) != permission) + { + 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; +} + +static void set_permissions(const char *datadir, const char *service_user) +{ + /* + Set data directory permissions for both current user and + the one who who runs services. + */ + set_directory_permissions(datadir, NULL, + FILE_GENERIC_READ | FILE_GENERIC_WRITE); + if (!service_user) + return; + + /* Datadir permission for the service. */ + set_directory_permissions(datadir, service_user, FILE_ALL_ACCESS); + char basedir[MAX_PATH]; + char path[MAX_PATH]; + + struct + { + const char *subdir; + DWORD perm; + } all_subdirs[]= { + {STR(INSTALL_PLUGINDIR), FILE_GENERIC_READ | FILE_GENERIC_EXECUTE}, + {STR(INSTALL_SHAREDIR), FILE_GENERIC_READ}, + }; + + + if (strncmp(service_user,"NT SERVICE\\",sizeof("NT SERVICE\\")-1) == 0) + { + /* + Read and execute permission for executables can/should be given + to any service account, rather than specific one. + */ + service_user="NT SERVICE\\ALL SERVICES"; + } + + get_basedir(basedir, sizeof(basedir), mysqld_path, '\\'); + for (int i= 0; i < array_elements(all_subdirs); i++) + { + auto subdir= + snprintf(path, sizeof(path), "%s\\%s", basedir, all_subdirs[i].subdir); + if (access(path, 0) == 0) + { + set_directory_permissions(path, service_user, all_subdirs[i].perm); + } + } + + /* Bindir, the directory where mysqld_path is located. */ + strcpy_s(path, mysqld_path); + char *end= strrchr(path, '/'); + if (!end) + end= strrchr(path, '\\'); + if (end) + *end= 0; + if (access(path, 0) == 0) + { + set_directory_permissions(path, service_user, + FILE_GENERIC_READ | FILE_GENERIC_EXECUTE); + } +} + +/* Create database instance (including registering as service etc) .*/ + +static int create_db_instance(const char *datadir) +{ + 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; + bool service_created= false; + std::string mysql_db_dir; + + verbose("Running bootstrap"); + + GetCurrentDirectory(cwd_len, cwd); + + /* Create datadir and datadir/mysql, if they do not already exist. */ + + if (CreateDirectory(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", + datadir); + break; + case ERROR_PATH_NOT_FOUND: + die("Can't create data directory '%s' " + "(one or more intermediate directories do not exist)\n", + datadir); + break; + default: + die("Can't create data directory '%s', last error %u\n", + datadir, last_error); + break; + } + } + + if (!SetCurrentDirectory(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", + datadir); + break; + default: + die("Can' set current directory to '%s', last error %u\n", + datadir, last_error); + break; + } + } + + if (!PathIsDirectoryEmpty(datadir)) + { + fprintf(stderr, "ERROR : Data directory %s is not empty." + " Only new or empty existing directories are accepted for --datadir\n", datadir); + exit(1); + } + + std::string service_user; + /* Register service if requested. */ + if (opt_service && opt_service[0]) + { + /* Run service under virtual account NT SERVICE\service_name.*/ + service_user.append("NT SERVICE\\").append(opt_service); + ret = register_service(datadir, service_user.c_str(), NULL); + if (ret) + goto end; + service_created = true; + } + + set_permissions(datadir, service_user.c_str()); + + if (opt_large_pages) + { + handle_user_privileges(service_user.c_str(), L"SeLockMemoryPrivilege", true); + } + + /* + Get security descriptor for the data directory. + It will be passed, as SDDL text, to the mysqld bootstrap subprocess, + to allow for correct subdirectory permissions. + */ + PSECURITY_DESCRIPTOR pSD; + if (GetNamedSecurityInfoA(datadir, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, + 0, 0, 0, 0, &pSD) == ERROR_SUCCESS) + { + char* string_sd = NULL; + if (ConvertSecurityDescriptorToStringSecurityDescriptor(pSD, SDDL_REVISION_1, + DACL_SECURITY_INFORMATION, &string_sd, 0)) + { + _putenv_s("MARIADB_NEW_DIRECTORY_SDDL", string_sd); + LocalFree(string_sd); + } + LocalFree(pSD); + } + + /* Create my.ini file in data directory.*/ + ret = create_myini(); + if (ret) + goto end; + + /* 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"); + } + static const char *pre_bootstrap_sql[] = { "create database mysql;\n","use mysql;\n"}; + for (auto cmd : pre_bootstrap_sql) + { + /* Write the bootstrap script to stdin. */ + if (fwrite(cmd, strlen(cmd), 1, in) != 1) + { + verbose("ERROR: Can't write to mysqld's stdin"); + ret= 1; + goto end; + } + } + + for (int i= 0; mysql_bootstrap_sql[i]; i++) + { + auto cmd = mysql_bootstrap_sql[i]; + /* Write the bootstrap script to stdin. */ + if (fwrite(cmd, strlen(cmd), 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"); + char buf[2 * MY_SHA1_HASH_SIZE + 2]; + my_make_scrambled_password(buf, opt_password, strlen(opt_password)); + fprintf(in, update_root_passwd, buf); + 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; + } + +end: + if (!ret) + return ret; + + /* Cleanup after error.*/ + if (created_datadir) + { + SetCurrentDirectory(cwd); + clean_directory(datadir); + } + + if (service_created) + { + auto sc_manager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); + if (sc_manager) + { + auto sc_handle= OpenService(sc_manager,opt_service, DELETE); + if (sc_handle) + { + DeleteService(sc_handle); + CloseServiceHandle(sc_handle); + } + CloseServiceHandle(sc_manager); + } + + /*Remove all service user privileges for the user.*/ + if(strncmp(service_user.c_str(), "NT SERVICE\\", + sizeof("NT SERVICE\\")-1)) + { + handle_user_privileges(service_user.c_str(), 0, false); + } + if (created_datadir) + RemoveDirectory(opt_datadir); + } + return ret; +} |