From 220dc1fd59e61a44718a38ea59de5dc1da5aab8f Mon Sep 17 00:00:00 2001 From: Vladislav Vaintroub <wlad@mariadb.com> Date: Thu, 18 Nov 2021 17:19:52 +0100 Subject: [PATCH] xxx --- mysys/my_getopt.c | 51 +++++++- sql/mysql_upgrade_service.cc | 40 +++--- sql/mysqld.cc | 1 + sql/upgrade_conf_file.cc | 211 +++++++++++++++++++++++++----- sql/winservice.c | 32 +++-- sql/winservice.h | 26 ++-- win/upgrade_wizard/CMakeLists.txt | 1 + 7 files changed, 283 insertions(+), 79 deletions(-) diff --git a/mysys/my_getopt.c b/mysys/my_getopt.c index 3fe025ba808..96f147c26c3 100644 --- a/mysys/my_getopt.c +++ b/mysys/my_getopt.c @@ -38,7 +38,7 @@ static double getopt_double(char *arg, const struct my_option *optp, int *err); static void init_variables(const struct my_option *, init_func_p); static void init_one_value(const struct my_option *, void *, longlong); static void fini_one_value(const struct my_option *, void *, longlong); -static int setval(const struct my_option *, void *, char *, my_bool); +static int setval(const struct my_option *, void *, char *, my_bool, const char *); static char *check_struct_option(char *cur_arg, char *key_name); /* @@ -133,6 +133,48 @@ double getopt_ulonglong2double(ulonglong v) return u.dbl; } +#ifdef _WIN32 +/** + + On Windows, if program is running in UTF8 mode, but some arguments are not UTF8. + + This will mostly likely be a sign of old "ANSI" my.ini, and it is likely that + something will go wrong, e.g file access error. +*/ +static void validate_value(const char *key, const char *value, + const char *filename) +{ + MY_STRCOPY_STATUS status; + const struct charset_info_st *cs= &my_charset_utf8mb4_bin; + size_t len; + if (GetACP() != CP_UTF8) + return; + len= strlen(value); + if (!len) + return; + cs->cset->well_formed_char_length(cs, value, value + len, len, &status); + if (!status.m_well_formed_error_pos) + return; + + if (filename && *filename) + { + my_getopt_error_reporter(WARNING_LEVEL, + "%s: invalid (non-UTF8) characters found for option '%s'" + " in file '%s'", + my_progname, key, filename); + } + else + { + DBUG_ASSERT(0); + my_getopt_error_reporter( + WARNING_LEVEL, "%s: invalid (non-UTF8) characters for option %s", + my_progname, key); + } +} +#else +#define validate_value(key, value, filename) (void)filename +#endif + /** Handle command line options. Sort options. @@ -564,7 +606,7 @@ int handle_options(int *argc, char ***argv, const struct my_option *longopts, } } if ((error= setval(optp, optp->value, argument, - set_maximum_value))) + set_maximum_value,filename))) DBUG_RETURN(error); if (get_one_option(optp, argument, filename)) DBUG_RETURN(EXIT_UNSPECIFIED_ERROR); @@ -610,7 +652,7 @@ int handle_options(int *argc, char ***argv, const struct my_option *longopts, continue; } if ((!option_is_autoset) && - ((error= setval(optp, value, argument, set_maximum_value))) && + ((error= setval(optp, value, argument, set_maximum_value,filename))) && !option_is_loose) DBUG_RETURN(error); if (get_one_option(optp, argument, filename)) @@ -711,7 +753,7 @@ static my_bool get_bool_argument(const struct my_option *opts, */ static int setval(const struct my_option *opts, void *value, char *argument, - my_bool set_maximum_value) + my_bool set_maximum_value, const char *option_file) { int err= 0, res= 0; DBUG_ENTER("setval"); @@ -858,6 +900,7 @@ static int setval(const struct my_option *opts, void *value, char *argument, goto ret; }; } + validate_value(opts->name, argument, option_file); DBUG_RETURN(0); ret: diff --git a/sql/mysql_upgrade_service.cc b/sql/mysql_upgrade_service.cc index 61f9f2542e9..3850816010d 100644 --- a/sql/mysql_upgrade_service.cc +++ b/sql/mysql_upgrade_service.cc @@ -366,7 +366,6 @@ static void get_service_config() */ static void change_service_config() { - char defaults_file[MAX_PATH]; char default_character_set[64]; char buf[MAX_PATH]; char commandline[3 * MAX_PATH + 19]; @@ -376,13 +375,17 @@ static void change_service_config() Write datadir to my.ini, after converting backslashes to unix style slashes. */ - strcpy_s(buf, MAX_PATH, service_properties.datadir); - for(i= 0; buf[i]; i++) + if (service_properties.datadir[0]) { - if (buf[i] == '\\') - buf[i]= '/'; + strcpy_s(buf, MAX_PATH, service_properties.datadir); + for (i= 0; buf[i]; i++) + { + if (buf[i] == '\\') + buf[i]= '/'; + } + WritePrivateProfileString("mysqld", "datadir", buf, + service_properties.inifile); } - WritePrivateProfileString("mysqld", "datadir",buf, service_properties.inifile); /* Remove basedir from defaults file, otherwise the service wont come up in @@ -397,19 +400,19 @@ static void change_service_config() */ default_character_set[0]= 0; GetPrivateProfileString("mysqld", "default-character-set", NULL, - default_character_set, sizeof(default_character_set), defaults_file); + default_character_set, sizeof(default_character_set), service_properties.inifile); if (default_character_set[0]) { WritePrivateProfileString("mysqld", "default-character-set", NULL, - defaults_file); + service_properties.inifile); WritePrivateProfileString("mysqld", "character-set-server", - default_character_set, defaults_file); + default_character_set, service_properties.inifile); } sprintf(defaults_file_param,"--defaults-file=%s", service_properties.inifile); sprintf_s(commandline, "\"%s\" \"%s\" \"%s\"", mysqld_path, defaults_file_param, opt_service); - if (!ChangeServiceConfig(service, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, + if (!my_ChangeServiceConfig(service, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, commandline, NULL, NULL, NULL, NULL, NULL, NULL)) { die("ChangeServiceConfig failed with %u", GetLastError()); @@ -483,13 +486,8 @@ int main(int argc, char **argv) } } - old_mysqld_exe_exists = (GetFileAttributes(service_properties.mysqld_exe) != INVALID_FILE_ATTRIBUTES); - log("Phase %d/%d: Fixing server config file%s", ++phase, max_phases, my_ini_exists ? "" : "(skipped)"); - - snprintf(my_ini_bck, sizeof(my_ini_bck), "%s.BCK", service_properties.inifile); - CopyFile(service_properties.inifile, my_ini_bck, FALSE); - upgrade_config_file(service_properties.inifile); - + old_mysqld_exe_exists= (GetFileAttributes(service_properties.mysqld_exe) != + INVALID_FILE_ATTRIBUTES); bool do_start_stop_server = old_mysqld_exe_exists && initial_service_state != SERVICE_RUNNING; log("Phase %d/%d: Start and stop server in the old version, to avoid crash recovery %s", ++phase, max_phases, @@ -544,6 +542,14 @@ int main(int argc, char **argv) start_duration_ms += 500; } } + + log("Phase %d/%d: Fixing server config file%s", ++phase, max_phases, + my_ini_exists ? "" : "(skipped)"); + snprintf(my_ini_bck, sizeof(my_ini_bck), "%s.BCK", + service_properties.inifile); + CopyFile(service_properties.inifile, my_ini_bck, FALSE); + upgrade_config_file(service_properties.inifile); + /* Start mysqld.exe as non-service skipping privileges (so we do not care about the password). But disable networking and enable pipe diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 93a548b476e..c946f7fba55 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -127,6 +127,7 @@ #ifdef _WIN32 #include <handle_connections_win.h> #include <sddl.h> +#include <winservice.h> /* SERVICE_STOPPED, SERVICE_RUNNING etc */ #endif #include <my_service_manager.h> diff --git a/sql/upgrade_conf_file.cc b/sql/upgrade_conf_file.cc index 543df7b9bdf..c255026fd9c 100644 --- a/sql/upgrade_conf_file.cc +++ b/sql/upgrade_conf_file.cc @@ -158,51 +158,196 @@ static int cmp_strings(const void* a, const void *b) return strcmp((const char *)a, *(const char **)b); } -/** - Convert file from a previous version, by removing -*/ -int upgrade_config_file(const char *myini_path) +static bool is_utf8_str(const char *s) { -#define MY_INI_SECTION_SIZE 32*1024 +3 - static char section_data[MY_INI_SECTION_SIZE]; - for (const char *section_name : { "mysqld","server","mariadb" }) + const unsigned char *bytes= (const unsigned char *) s; + int num; + + while (*bytes) { - DWORD size = GetPrivateProfileSection(section_name, section_data, MY_INI_SECTION_SIZE, myini_path); - if (size == MY_INI_SECTION_SIZE - 2) + if ((*bytes & 0x80) == 0x00) + num= 1; + else if ((*bytes & 0xE0) == 0xC0) + num= 2; + else if ((*bytes & 0xF0) == 0xE0) + num= 3; + else if ((*bytes & 0xF8) == 0xF0) + num= 4; + else + return false; + + bytes++; + for (int i= 1; i < num; i++) { - return -1; + if ((*bytes & 0xC0) != 0x80) + return false; + bytes++; } + } + return true; +} - for (char *keyval = section_data; *keyval; keyval += strlen(keyval) + 1) + +static UINT get_system_acp() +{ + static DWORD system_acp; + if (system_acp) + return system_acp; + + char str_cp[10]; + int cch= GetLocaleInfo(GetSystemDefaultLCID(), LOCALE_IDEFAULTANSICODEPAGE, + str_cp, sizeof(str_cp)); + + system_acp= cch > 0 ? atoi(str_cp) : 1252; + + return system_acp; +} + +#define MY_INI_SECTION_SIZE 32 * 1024 + 3 + +static char *ansi_to_utf8(const char *s) +{ +#define MAX_STR_LEN MY_INI_SECTION_SIZE + static wchar_t utf16_buf[MAX_STR_LEN]; + static char utf8_buf[MAX_STR_LEN]; + if (MultiByteToWideChar(get_system_acp(), 0, s, -1, utf16_buf, MAX_STR_LEN)) + { + if (WideCharToMultiByte(CP_UTF8, 0, utf16_buf, -1, utf8_buf, MAX_STR_LEN, + 0, 0)) + return utf8_buf; + } + return 0; +} + + +int fix_section(const char *myini_path, const char *section_name, + bool is_server) +{ + if (!is_server && GetACP() != CP_UTF8) + return 0; + + static char section_data[MY_INI_SECTION_SIZE]; + DWORD size= GetPrivateProfileSection(section_name, section_data, + MY_INI_SECTION_SIZE, myini_path); + if (size == MY_INI_SECTION_SIZE - 2) + { + return -1; + } + + for (char *keyval= section_data; *keyval; keyval += strlen(keyval)+1) + { + char varname[256]; + char *value; + + char *key_end= strchr(keyval, '='); + if (!key_end) + key_end= keyval + strlen(keyval); + + if (key_end - keyval > sizeof(varname)) + continue; + + value= key_end + 1; + if (GetACP() == CP_UTF8 && !is_utf8_str(value)) { - char varname[256]; - char *key_end = strchr(keyval, '='); - if (!key_end) - key_end = keyval+ strlen(keyval); - - if (key_end - keyval > sizeof(varname)) - continue; - // copy and normalize (convert dash to underscore) to variable names - for (char *p = keyval, *q = varname;; p++,q++) + char *new_val= ansi_to_utf8(value); + if (new_val) { - if (p == key_end) - { - *q = 0; - break; - } - *q = (*p == '-') ? '_' : *p; + *key_end= 0; + fprintf(stdout, "Fixing variable '%s' charset, value=%s\n", keyval, + new_val); + WritePrivateProfileString(section_name, keyval, new_val, myini_path); + *key_end= '='; } - const char *v = (const char *)bsearch(varname, removed_variables, sizeof(removed_variables) / sizeof(removed_variables[0]), - sizeof(char *), cmp_strings); + } + if (!is_server) + continue; - if (v) + // Check if variable should be removed from config. + // First, copy and normalize (convert dash to underscore) to variable + // names + for (char *p= keyval, *q= varname;; p++, q++) + { + if (p == key_end) { - fprintf(stdout, "Removing variable '%s' from config file\n", varname); - // delete variable - *key_end = 0; - WritePrivateProfileString(section_name, keyval, 0, myini_path); + *q= 0; + break; } + *q= (*p == '-') ? '_' : *p; + } + const char *v= (const char *) bsearch(varname, removed_variables, sizeof(removed_variables) / sizeof(removed_variables[0]), + sizeof(char *), cmp_strings); + + if (v) + { + fprintf(stdout, "Removing variable '%s' from config file\n", varname); + // delete variable + *key_end= 0; + WritePrivateProfileString(section_name, keyval, 0, myini_path); } } return 0; } + +static bool is_mariadb_section(const char *name, bool *is_server) +{ + if (strncmp(name, "mysql", 5) + && strncmp(name, "mariadb", 7) + && strcmp(name, "client") + && strcmp(name, "client-server") + && strcmp(name, "server")) + { + return false; + } + + for (const char *section_name : {"mysqld", "server", "mariadb"}) + if (*is_server= !strcmp(section_name, name)) + break; + + return *is_server; +} + + +/** + Convert file from a previous version, by removing obsolete variables + Also, fix values to be UTF8, if MariaDB is running in utf8 mode +*/ +int upgrade_config_file(const char *myini_path) +{ + static char all_sections[MY_INI_SECTION_SIZE]; + int sz= GetPrivateProfileSectionNamesA(all_sections, MY_INI_SECTION_SIZE, + myini_path); + if (!sz) + return 0; + if (sz > MY_INI_SECTION_SIZE - 2) + { + fprintf(stderr, "Too many sections in config file\n"); + return -1; + } + for (char *section= all_sections; *section; section+= strlen(section) + 1) + { + bool is_server_section; + if (is_mariadb_section(section, &is_server_section)) + fix_section(myini_path, section, is_server_section); + } + return 0; +} + +#ifdef MAIN +int main(int argc, char **argv) +{ + if (argc != 2) + { + fprintf(stderr, "Usage : %s <config_file>\n", argv[0]); + return 1; + } + int rc= upgrade_config_file(argv[1]); + if (rc) + { + fprintf(stderr, "upgrade_config_file(\"%s\") returned an error\n", + argv[1]); + return 1; + } + return 0; +} +#endif + diff --git a/sql/winservice.c b/sql/winservice.c index a11087e5cd5..193d914957f 100644 --- a/sql/winservice.c +++ b/sql/winservice.c @@ -134,6 +134,20 @@ static void get_datadir_from_ini(const char *ini, char *service_name, char *data } +static int fix_and_check_datadir(mysqld_service_properties *props) +{ + normalize_path(props->datadir, MAX_PATH); + /* Check if datadir really exists */ + if (GetFileAttributes(props->datadir) != INVALID_FILE_ATTRIBUTES) + return 0; + /* + It is possible, that datadir contains some unconvertable character. + We just pretend not to know what's the data directory + */ + props->datadir[0]= 0; + return 0; +} + /* Retrieve some properties from windows mysqld service binary path. We're interested in ini file location and datadir, and also in version of @@ -183,7 +197,7 @@ int get_mysql_service_properties(const wchar_t *bin_path, } /* Last parameter is the service name*/ - wcstombs(service_name, args[numargs-1], MAX_PATH); + WideCharToMultiByte(CP_ACP, 0, args[numargs - 1], -1, service_name, MAX_PATH, NULL, NULL); if(have_inifile && wcsncmp(args[1], L"--defaults-file=", 16) != 0) goto end; @@ -202,7 +216,7 @@ int get_mysql_service_properties(const wchar_t *bin_path, goto end; } - wcstombs(props->mysqld_exe, mysqld_path, MAX_PATH); + WideCharToMultiByte(CP_ACP, 0, mysqld_path, -1, props->mysqld_exe, MAX_PATH, NULL, NULL); /* If mysqld.exe exists, try to get its version from executable */ if (GetFileAttributes(props->mysqld_exe) != INVALID_FILE_ATTRIBUTES) { @@ -213,7 +227,8 @@ int get_mysql_service_properties(const wchar_t *bin_path, if (have_inifile) { /* We have --defaults-file in service definition. */ - wcstombs(props->inifile, args[1]+16, MAX_PATH); + WideCharToMultiByte(CP_ACP, 0, args[1] + 16, -1, props->inifile, + MAX_PATH, NULL, NULL); normalize_path(props->inifile, MAX_PATH); if (GetFileAttributes(props->inifile) != INVALID_FILE_ATTRIBUTES) { @@ -284,16 +299,9 @@ int get_mysql_service_properties(const wchar_t *bin_path, } } - if (props->datadir[0]) + if (props->datadir[0] == 0 || fix_and_check_datadir(props)) { - normalize_path(props->datadir, MAX_PATH); - /* Check if datadir really exists */ - if (GetFileAttributes(props->datadir) == INVALID_FILE_ATTRIBUTES) - goto end; - } - else - { - /* There is no datadir in ini file, bail out.*/ + /* There is no datadir in ini file, or non-existing dir, bail out.*/ goto end; } diff --git a/sql/winservice.h b/sql/winservice.h index d24ce52c8d9..3ac746d44ba 100644 --- a/sql/winservice.h +++ b/sql/winservice.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2011, 2012, Monty Program Ab + Copyright (c) 2011, 2021 Monty Program Ab 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 @@ -37,6 +37,7 @@ typedef struct mysqld_service_properties_st extern int get_mysql_service_properties(const wchar_t *bin_path, mysqld_service_properties *props); + #if !defined(UNICODE) #include <malloc.h> /* @@ -92,15 +93,13 @@ end: return ret; } -static inline SC_HANDLE my_CreateService(SC_HANDLE hSCManager, LPCSTR lpServiceName, LPCSTR lpDisplayName, +static inline SC_HANDLE my_CreateService(SC_HANDLE hSCManager, + LPCSTR lpServiceName, LPCSTR lpDisplayName, DWORD dwDesiredAccess, DWORD dwServiceType, DWORD dwStartType, DWORD dwErrorControl, - LPCSTR lpBinaryPathName, - LPCSTR lpLoadOrderGroup, - LPDWORD lpdwTagId, - LPCSTR lpDependencies, - LPCSTR lpServiceStartName, - LPCSTR lpPassword) + LPCSTR lpBinaryPathName, LPCSTR lpLoadOrderGroup, + LPDWORD lpdwTagId, LPCSTR lpDependencies, + LPCSTR lpServiceStartName, LPCSTR lpPassword) { wchar_t *w_ServiceName= NULL; wchar_t *w_DisplayName= NULL; @@ -146,11 +145,11 @@ end: } static inline BOOL my_ChangeServiceConfig(SC_HANDLE hService, DWORD dwServiceType, - DWORD dwStartType, DWORD dwErrorControl, - LPCSTR lpBinaryPathName, LPCSTR lpLoadOrderGroup, - LPDWORD lpdwTagId, LPCSTR lpDependencies, - LPCSTR lpServiceStartName, LPCSTR lpPassword, - LPCSTR lpDisplayName) + DWORD dwStartType, DWORD dwErrorControl, + LPCSTR lpBinaryPathName, LPCSTR lpLoadOrderGroup, + LPDWORD lpdwTagId, LPCSTR lpDependencies, + LPCSTR lpServiceStartName, LPCSTR lpPassword, + LPCSTR lpDisplayName) { wchar_t *w_DisplayName= NULL; wchar_t *w_BinaryPathName= NULL; @@ -190,6 +189,7 @@ end: SetLastError(last_error); return ret; } +#undef AWSTRDUP #undef OpenService #define OpenService my_OpenService diff --git a/win/upgrade_wizard/CMakeLists.txt b/win/upgrade_wizard/CMakeLists.txt index 20a06a41215..fd3560e1ee6 100644 --- a/win/upgrade_wizard/CMakeLists.txt +++ b/win/upgrade_wizard/CMakeLists.txt @@ -5,6 +5,7 @@ ENDIF() # We need MFC # /permissive- flag does not play well with MFC, disable it. STRING(REPLACE "/permissive-" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") +REMOVE_DEFINITIONS(-DNOSERVICE) # fixes "already defined" warning in an AFX header FIND_PACKAGE(MFC) IF(NOT MFC_FOUND)