diff options
author | Vladislav Vaintroub <wlad@mariadb.com> | 2022-01-18 21:37:52 +0100 |
---|---|---|
committer | Vladislav Vaintroub <wlad@mariadb.com> | 2022-01-18 21:37:52 +0100 |
commit | e222e44d1bfc995870430bb90d8ac97e91f66cb4 (patch) | |
tree | e720df7a0fba13fa0a66bb53d910d007567083ad | |
parent | d9f7a6b3316cdaa16acff2c8621e775f49153b20 (diff) | |
parent | 2e48fbe3f5e84ebb8a2de4f4fc0448d648d25c0c (diff) | |
download | mariadb-git-bb-10.8-wlad.tar.gz |
Merge branch 'preview-10.8-MDEV-26713-Windows-i18-support' into 10.8bb-10.8-wlad
43 files changed, 1107 insertions, 401 deletions
diff --git a/client/mysql.cc b/client/mysql.cc index 37f506a99cd..6612b273d17 100644 --- a/client/mysql.cc +++ b/client/mysql.cc @@ -88,9 +88,7 @@ extern "C" { #endif /* defined(HAVE_CURSES_H) && defined(HAVE_TERM_H) */ #undef bcmp // Fix problem with new readline -#if defined(_WIN32) -#include <conio.h> -#else +#if !defined(_WIN32) # ifdef __APPLE__ # include <editline/readline.h> # else @@ -104,6 +102,98 @@ extern "C" { #define USE_POPEN } +static CHARSET_INFO *charset_info= &my_charset_latin1; + +#if defined(_WIN32) +/* + Set console mode for the whole duration of the client session. + + We need for input + - line input (i.e read lines from console) + - echo typed characters + - "cooked" mode, i.e we do not want to handle all keystrokes, + like DEL etc ourselves, yet. We might want handle keystrokes + in the future, to implement tab completion, and better + (multiline) history. + + Disable VT escapes for the output.We do not know what kind of escapes SELECT would return. +*/ +struct Console_mode +{ + HANDLE in= GetStdHandle(STD_INPUT_HANDLE); + HANDLE out= GetStdHandle(STD_OUTPUT_HANDLE); + DWORD mode_in=0; + DWORD mode_out=0; + + enum {STDIN_CHANGED = 1, STDOUT_CHANGED = 2}; + int changes=0; + + Console_mode() + { + if (in && in != INVALID_HANDLE_VALUE && GetConsoleMode(in, &mode_in)) + { + SetConsoleMode(in, ENABLE_ECHO_INPUT|ENABLE_LINE_INPUT|ENABLE_PROCESSED_INPUT); + changes |= STDIN_CHANGED; + } + + if (out && out != INVALID_HANDLE_VALUE && GetConsoleMode(out, &mode_out)) + { +#ifdef ENABLE_VIRTUAL_TERMINAL_INPUT + SetConsoleMode(out, mode_out & ~ENABLE_VIRTUAL_TERMINAL_INPUT); + changes |= STDOUT_CHANGED; +#endif + } + } + + ~Console_mode() + { + if (changes & STDIN_CHANGED) + SetConsoleMode(in, mode_in); + + if(changes & STDOUT_CHANGED) + SetConsoleMode(out, mode_out); + } +}; + +static Console_mode my_conmode; + +#define MAX_CGETS_LINE_LEN 65535 +/** Read line from console, chomp EOL*/ +static char *win_readline() +{ + static wchar_t wstrbuf[MAX_CGETS_LINE_LEN]; + static char strbuf[MAX_CGETS_LINE_LEN * 4]; + + DWORD nchars= 0; + uint len= 0; + SetLastError(0); + if (!ReadConsoleW(GetStdHandle(STD_INPUT_HANDLE), wstrbuf, MAX_CGETS_LINE_LEN-1, + &nchars, NULL)) + goto err; + if (nchars == 0 && GetLastError() == ERROR_OPERATION_ABORTED) + goto err; + + for (;nchars > 0; nchars--) + { + if (wstrbuf[nchars - 1] != '\n' && wstrbuf[nchars - 1] != '\r') + break; + } + + if (nchars > 0) + { + uint errors; + len= my_convert(strbuf, sizeof(strbuf), charset_info, + (const char *) wstrbuf, nchars * sizeof(wchar_t), + &my_charset_utf16le_bin, &errors); + } + strbuf[len]= 0; + return strbuf; +err: + return NULL; +} +#endif + + #ifdef HAVE_VIDATTR static int have_curses= 0; static void my_vidattr(chtype attrs) @@ -208,7 +298,6 @@ unsigned short terminal_width= 80; static uint opt_protocol=0; static const char *opt_protocol_type= ""; -static CHARSET_INFO *charset_info= &my_charset_latin1; static uint protocol_to_force= MYSQL_PROTOCOL_DEFAULT; @@ -1353,6 +1442,46 @@ sig_handler mysql_end(int sig) exit(status.exit_status); } +#ifdef _WIN32 +#define CNV_BUFSIZE 1024 + +/** + Convert user,database,and password to requested charset. + + This is done in the single case when user connects with non-UTF8 + default-character-set, on UTF8 capable Windows. + + User, password, and database are UTF8 encoded, prior to the function, + this needs to be fixed, in case they contain non-ASCIIs. + + Mostly a workaround, to allow existng users with non-ASCII password + to survive upgrade without losing connectivity. +*/ +static void maybe_convert_charset(const char **user, const char **password, + const char **database, const char *csname) +{ + if (GetACP() != CP_UTF8 || !strncmp(csname, "utf8", 4)) + return; + static char bufs[3][CNV_BUFSIZE]; + const char **from[]= {user, password, database}; + CHARSET_INFO *cs= get_charset_by_csname(csname, MY_CS_PRIMARY, + MYF(MY_UTF8_IS_UTF8MB3 | MY_WME)); + if (!cs) + return; + for (int i= 0; i < 3; i++) + { + const char *str= *from[i]; + if (!str) + continue; + uint errors; + uint len= my_convert(bufs[i], CNV_BUFSIZE, cs, str, (uint32) strlen(str), + &my_charset_utf8mb4_bin, &errors); + bufs[i][len]= 0; + *from[i]= bufs[i]; + } +} +#endif + /* set connection-specific options and call mysql_real_connect */ @@ -1384,6 +1513,10 @@ static bool do_connect(MYSQL *mysql, const char *host, const char *user, mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_RESET, 0); mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "program_name", "mysql"); +#ifdef _WIN32 + maybe_convert_charset(&user, &password, &database,default_charset); +#endif + return mysql_real_connect(mysql, host, user, password, database, opt_mysql_port, opt_mysql_unix_port, flags); } @@ -2033,11 +2166,6 @@ static int get_options(int argc, char **argv) static int read_and_execute(bool interactive) { -#if defined(_WIN32) - String tmpbuf; - String buffer; -#endif - char *line= NULL; char in_string=0; ulong line_number=0; @@ -2115,26 +2243,7 @@ static int read_and_execute(bool interactive) #if defined(_WIN32) tee_fputs(prompt, stdout); - if (!tmpbuf.is_alloced()) - tmpbuf.alloc(65535); - tmpbuf.length(0); - buffer.length(0); - size_t clen; - do - { - line= my_cgets((char*)tmpbuf.ptr(), tmpbuf.alloced_length()-1, &clen); - buffer.append(line, clen); - /* - if we got buffer fully filled than there is a chance that - something else is still in console input buffer - */ - } while (tmpbuf.alloced_length() <= clen); - /* - An empty line is returned from my_cgets when there's error reading : - Ctrl-c for example - */ - if (line) - line= buffer.c_ptr(); + line= win_readline(); #else if (opt_outfile) fputs(prompt, OUTFILE); @@ -2201,10 +2310,7 @@ static int read_and_execute(bool interactive) } } -#if defined(_WIN32) - buffer.free(); - tmpbuf.free(); -#else +#if !defined(_WIN32) if (interactive) /* free the last entered line. @@ -3242,6 +3348,21 @@ com_clear(String *buffer,char *line __attribute__((unused))) return 0; } +static void adjust_console_codepage(const char *name __attribute__((unused))) +{ +#ifdef _WIN32 + if (my_set_console_cp(name) < 0) + { + char buf[128]; + snprintf(buf, sizeof(buf), + "WARNING: Could not determine Windows codepage for charset '%s'," + "continue using codepage %u", name, GetConsoleOutputCP()); + put_info(buf, INFO_INFO); + } +#endif +} + + /* ARGSUSED */ static int com_charset(String *buffer __attribute__((unused)), char *line) @@ -3263,6 +3384,7 @@ com_charset(String *buffer __attribute__((unused)), char *line) mysql_set_character_set(&mysql, charset_info->cs_name.str); default_charset= (char *)charset_info->cs_name.str; put_info("Charset changed", INFO_INFO); + adjust_console_codepage(charset_info->cs_name.str); } else put_info("Charset is not found", INFO_INFO); return 0; @@ -4806,6 +4928,7 @@ sql_real_connect(char *host,char *database,char *user,char *password, put_info(buff, INFO_ERROR); return 1; } + adjust_console_codepage(charset_info->cs_name.str); connected=1; #ifndef EMBEDDED_LIBRARY mysql_options(&mysql, MYSQL_OPT_RECONNECT, &debug_info_flag); diff --git a/client/mysqladmin.cc b/client/mysqladmin.cc index 6fa5d6c73d0..a7159d2bb6a 100644 --- a/client/mysqladmin.cc +++ b/client/mysqladmin.cc @@ -438,6 +438,7 @@ int main(int argc,char *argv[]) mysql_options(&mysql,MYSQL_OPT_PROTOCOL,(char*)&opt_protocol); if (!strcmp(default_charset,MYSQL_AUTODETECT_CHARSET_NAME)) default_charset= (char *)my_default_csname(); + my_set_console_cp(default_charset); mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, default_charset); error_flags= (myf)(opt_nobeep ? 0 : ME_BELL); diff --git a/client/mysqlcheck.c b/client/mysqlcheck.c index 480308aa015..eb063765a37 100644 --- a/client/mysqlcheck.c +++ b/client/mysqlcheck.c @@ -503,6 +503,7 @@ static int get_options(int *argc, char ***argv) printf("Unsupported character set: %s\n", default_charset); DBUG_RETURN(1); } + my_set_console_cp(default_charset); if (*argc > 0 && opt_alldbs) { printf("You should give only options, no arguments at all, with option\n"); diff --git a/client/mysqlimport.c b/client/mysqlimport.c index 8723641c74b..48f11667cd1 100644 --- a/client/mysqlimport.c +++ b/client/mysqlimport.c @@ -525,6 +525,7 @@ static MYSQL *db_connect(char *host, char *database, mysql_options(mysql, MYSQL_DEFAULT_AUTH, opt_default_auth); if (!strcmp(default_charset,MYSQL_AUTODETECT_CHARSET_NAME)) default_charset= (char *)my_default_csname(); + my_set_console_cp(default_charset); mysql_options(mysql, MYSQL_SET_CHARSET_NAME, my_default_csname()); mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_RESET, 0); mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, diff --git a/client/mysqlshow.c b/client/mysqlshow.c index 9b31d87225c..cbac1817c3c 100644 --- a/client/mysqlshow.c +++ b/client/mysqlshow.c @@ -147,6 +147,7 @@ int main(int argc, char **argv) if (!strcmp(default_charset,MYSQL_AUTODETECT_CHARSET_NAME)) default_charset= (char *)my_default_csname(); + my_set_console_cp(default_charset); mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, default_charset); if (opt_plugin_dir && *opt_plugin_dir) diff --git a/client/mysqltest.cc b/client/mysqltest.cc index ceca762bdf9..3d64552922a 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -3258,6 +3258,47 @@ static int replace(DYNAMIC_STRING *ds_str, return 0; } +#ifdef _WIN32 +/** + Check if background execution of command was requested. + Like in Unix shell, we assume background execution of the last + character in command is a ampersand (we do not tokenize though) +*/ +static bool is_background_command(const DYNAMIC_STRING *ds) +{ + for (size_t i= ds->length - 1; i > 1; i--) + { + char c= ds->str[i]; + if (!isspace(c)) + return (c == '&'); + } + return false; +} + +/** + Execute OS command in background. We assume that the last character + is ampersand, i.e is_background_command() returned +*/ +#include <string> +static int execute_in_background(char *cmd) +{ + STARTUPINFO s{}; + PROCESS_INFORMATION pi{}; + char *end= strrchr(cmd, '&'); + DBUG_ASSERT(end); + *end =0; + std::string scmd("cmd /c "); + scmd.append(cmd); + BOOL ok= + CreateProcess(0, (char *)scmd.c_str(), 0, 0, 0, CREATE_NO_WINDOW, 0, 0, &s, &pi); + *end= '&'; + if (!ok) + return (int) GetLastError(); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return 0; +} +#endif /* Execute given command. @@ -3332,6 +3373,14 @@ void do_exec(struct st_command *command) DBUG_PRINT("info", ("Executing '%s' as '%s'", command->first_argument, ds_cmd.str)); +#ifdef _WIN32 + if (is_background_command(&ds_cmd)) + { + error= execute_in_background(ds_cmd.str); + goto end; + } +#endif + if (!(res_file= my_popen(ds_cmd.str, "r"))) { dynstr_free(&ds_cmd); @@ -3358,7 +3407,9 @@ void do_exec(struct st_command *command) dynstr_append_sorted(&ds_res, &ds_sorted, 0); dynstr_free(&ds_sorted); } - +#ifdef _WIN32 +end: +#endif if (error) { uint status= WEXITSTATUS(error); diff --git a/cmake/os/Windows.cmake b/cmake/os/Windows.cmake index d9a0dfba46a..da75c73d585 100644 --- a/cmake/os/Windows.cmake +++ b/cmake/os/Windows.cmake @@ -60,8 +60,8 @@ ENDIF() ADD_DEFINITIONS(-D_CRT_SECURE_NO_DEPRECATE) ADD_DEFINITIONS(-D_WIN32_WINNT=0x0A00) -# We do not want the windows.h macros min/max -ADD_DEFINITIONS(-DNOMINMAX) +# We do not want the windows.h , or winsvc.h macros min/max +ADD_DEFINITIONS(-DNOMINMAX -DNOSERVICE) # Speed up build process excluding unused header files ADD_DEFINITIONS(-DWIN32_LEAN_AND_MEAN) diff --git a/cmake/win_compatibility.manifest b/cmake/win_compatibility.manifest index 2e4b27a6dc4..0e7ce667d68 100644 --- a/cmake/win_compatibility.manifest +++ b/cmake/win_compatibility.manifest @@ -19,4 +19,9 @@ </application> </compatibility> + <application> + <windowsSettings> + <activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage> + </windowsSettings> + </application> </asmv1:assembly> diff --git a/include/my_global.h b/include/my_global.h index 224909116dd..ed213e69f85 100644 --- a/include/my_global.h +++ b/include/my_global.h @@ -29,6 +29,14 @@ #pragma GCC poison __WIN__ #endif +#if defined(_MSC_VER) +/* + Following functions have bugs, when used with UTF-8 active codepage. + #include <winservice.h> will use the non-buggy wrappers +*/ +#pragma deprecated("CreateServiceA", "OpenServiceA", "ChangeServiceConfigA") +#endif + /* InnoDB depends on some MySQL internals which other plugins should not need. This is because of InnoDB's foreign key support, "safe" binlog diff --git a/include/my_sys.h b/include/my_sys.h index 3d2a7280e53..58d036c6fc3 100644 --- a/include/my_sys.h +++ b/include/my_sys.h @@ -1087,6 +1087,9 @@ extern char *get_tty_password(const char *opt_message); #define BACKSLASH_MBTAIL /* File system character set */ extern CHARSET_INFO *fs_character_set(void); +extern int my_set_console_cp(const char *name); +#else +#define my_set_console_cp(A) do {} while (0) #endif extern const char *my_default_csname(void); extern size_t escape_quotes_for_mysql(CHARSET_INFO *charset_info, @@ -1098,13 +1101,6 @@ extern void thd_increment_bytes_sent(void *thd, size_t length); extern void thd_increment_bytes_received(void *thd, size_t length); extern void thd_increment_net_big_packet_count(void *thd, size_t length); -#ifdef _WIN32 - -/* implemented in my_conio.c */ -char* my_cgets(char *string, size_t clen, size_t* plen); - -#endif - #include <mysql/psi/psi.h> #ifdef HAVE_PSI_INTERFACE diff --git a/mysql-test/include/check_utf8_cli.inc b/mysql-test/include/check_utf8_cli.inc new file mode 100644 index 00000000000..a1fac216446 --- /dev/null +++ b/mysql-test/include/check_utf8_cli.inc @@ -0,0 +1,3 @@ +# Check if utf8 can be used on the command line for --exec +# The real check is done in the suite.pm +# diff --git a/mysql-test/include/check_windows_admin.inc b/mysql-test/include/check_windows_admin.inc new file mode 100644 index 00000000000..44a8b71a19f --- /dev/null +++ b/mysql-test/include/check_windows_admin.inc @@ -0,0 +1,3 @@ +# Check if current user is Windows admin +# Used for testing services with mysql_install_db.exe +# Actual value is set by suite.pm diff --git a/mysql-test/include/no_utf8_cli.inc b/mysql-test/include/no_utf8_cli.inc new file mode 100644 index 00000000000..19f9aa6df42 --- /dev/null +++ b/mysql-test/include/no_utf8_cli.inc @@ -0,0 +1,3 @@ +# Check if utf8 can't be used on the command line for --exec +# The real check is done in the suite.pm +# diff --git a/mysql-test/main/charset_client_win.test b/mysql-test/main/charset_client_win.test index b4a21d4f0a4..c3f649cb7d4 100644 --- a/mysql-test/main/charset_client_win.test +++ b/mysql-test/main/charset_client_win.test @@ -1,2 +1,3 @@ --source include/windows.inc +--source include/no_utf8_cli.inc --exec chcp 1257 > NUL && $MYSQL --default-character-set=auto -e "select @@character_set_client" diff --git a/mysql-test/main/charset_client_win_utf8mb4.result b/mysql-test/main/charset_client_win_utf8mb4.result new file mode 100644 index 00000000000..9bbf751e45c --- /dev/null +++ b/mysql-test/main/charset_client_win_utf8mb4.result @@ -0,0 +1,6 @@ +@@character_set_client +utf8mb4 +ERROR 1045 (28000): Access denied for user 'u'@'localhost' (using password: YES) +2 +2 +DROP user u; diff --git a/mysql-test/main/charset_client_win_utf8mb4.test b/mysql-test/main/charset_client_win_utf8mb4.test new file mode 100644 index 00000000000..e08afa250bd --- /dev/null +++ b/mysql-test/main/charset_client_win_utf8mb4.test @@ -0,0 +1,22 @@ +--source include/windows.inc +--source include/check_utf8_cli.inc +--exec $MYSQL --default-character-set=auto -e "select @@character_set_client" + +# Test that a user with old, non-UTF8 password can still connect +# by setting setting non-auto --default-character-set +# This is important for backward compatibility + +# Emulate creating password in an interactive client session, with older clients +# which communicates with the server using with something like cp850 + +exec chcp 850 > NUL && echo CREATE USER 'u' IDENTIFIED by 'ü' | $MYSQL --default-character-set=cp850; + + +# Can't connect with UTF8 +--error 1 +exec $MYSQL --default-character-set=auto --user=u --password=ü -e "select 1" 2>&1; + +# Can connect with tweaked --default-character-set +exec $MYSQL --default-character-set=cp850 --user=u --password=ü -e "select 2"; +DROP user u; + diff --git a/mysql-test/main/grant_not_windows.result b/mysql-test/main/grant_utf8_cli.result index fedfaf984b2..fedfaf984b2 100644 --- a/mysql-test/main/grant_not_windows.result +++ b/mysql-test/main/grant_utf8_cli.result diff --git a/mysql-test/main/grant_not_windows.test b/mysql-test/main/grant_utf8_cli.test index 55b09232edc..bc811d5298e 100644 --- a/mysql-test/main/grant_not_windows.test +++ b/mysql-test/main/grant_utf8_cli.test @@ -1,6 +1,5 @@ - # UTF8 parameters to mysql client do not work on Windows ---source include/not_windows.inc --source include/not_embedded.inc +--source include/check_utf8_cli.inc # # Bug#21432 Database/Table name limited to 64 bytes, not chars, problems with multi-byte diff --git a/mysql-test/main/mysql_install_db_win_utf8.result b/mysql-test/main/mysql_install_db_win_utf8.result new file mode 100644 index 00000000000..744c982b291 --- /dev/null +++ b/mysql-test/main/mysql_install_db_win_utf8.result @@ -0,0 +1,14 @@ +Running bootstrap +Creating my.ini file +Removing default user +Allowing remote access for user root +Setting root password +Creation of the database was successful +# Kill the server +connect con1,localhost,root,パスワード,mysql; +SELECT @@datadir; +@@datadir +DATADIR/ +# Kill the server +connection default; +# restart diff --git a/mysql-test/main/mysql_install_db_win_utf8.test b/mysql-test/main/mysql_install_db_win_utf8.test new file mode 100644 index 00000000000..fc67b66d3ca --- /dev/null +++ b/mysql-test/main/mysql_install_db_win_utf8.test @@ -0,0 +1,35 @@ +--source include/windows.inc +--source include/check_utf8_cli.inc + +# Create database in tmp directory using mysql_install_db.exe, +# and start server from this directory. +let $ddir= $MYSQLTEST_VARDIR/tmp/датадир; +--error 0,1 +rmdir $ddir; +exec $MYSQL_INSTALL_DB_EXE --datadir=$ddir --password=パスワード -R; +--source include/kill_mysqld.inc + +# Note "restart" via MTR does not work, if server's command line has +# non-ASCII characters used (or, characters outside of ANSI codepage). +# This is a perl limitation, which is worked around in this test - +# the server started in background, via exec $MYSQLD + +--replace_result $MYSQLD MYSQLD $MYSQLTEST_VARDIR MYSQLTEST_VARDIR +exec $MYSQLD --defaults-file=$MYSQLTEST_VARDIR/my.cnf --defaults-group-suffix=.1 --datadir=$ddir --loose-innodb > NUL 2>&1 &; +--enable_reconnect +--source include/wait_until_connected_again.inc +--disable_reconnect + +connect (con1,localhost,root,パスワード,mysql); + +# Smoke test - check that we're actually using datadir +# we've created (i.e restart_parameters worked) +--replace_result $ddir DATADIR +SELECT @@datadir; +# restart in the original datadir again +--source include/kill_mysqld.inc +rmdir $ddir; + +connection default; +--source include/start_mysqld.inc + diff --git a/mysql-test/main/winservice.inc b/mysql-test/main/winservice.inc new file mode 100644 index 00000000000..42aab645cc8 --- /dev/null +++ b/mysql-test/main/winservice.inc @@ -0,0 +1,75 @@ +source include/check_windows_admin.inc; + +# The test uses return code from sc.exe utility, which are as follows +let $ERROR_SERVICE_DOES_NOT_EXIST= 1060; +let $ERROR_SERVICE_CANNOT_ACCEPT_CTRL=1061;# intermediate, during start or stop +let $ERROR_SERVICE_NOT_ACTIVE=1062;# service stopped + +let $sc_exe= C:\Windows\System32\sc.exe; +let $ddir= $MYSQLTEST_VARDIR/tmp/$datadir_name; +let $service_name=$service_name_prefix$MASTER_MYPORT; + + +error 0,1; +rmdir $ddir; + +--disable_result_log +error 0,$ERROR_SERVICE_DOES_NOT_EXIST; +exec $sc_exe delete $service_name; +--enable_result_log + +source include/kill_mysqld.inc; +echo # run mysql_install_db with --service parameter; +--disable_result_log +exec $MYSQL_INSTALL_DB_EXE --datadir=$ddir --port=$MASTER_MYPORT --password=$password --service=$service_name -R; +--enable_result_log + +echo # Start service; +--disable_result_log +exec $sc_exe start $service_name; +--enable_result_log + +enable_reconnect; +source include/wait_until_connected_again.inc; +disable_reconnect; + +echo # Connect with root user password=$password; +connect (con1,localhost,root,$password,mysql); + +# Smoke test - check that we're actually using datadir +# we've created (i.e restart_parameters worked) +replace_result $ddir DATADIR; +select @@datadir; + +echo # Stop service and wait until it is down; + +# stop service +--disable_result_log +exec $sc_exe stop $service_name; +# Wait until stopped +let $sys_errno=0; +while($sys_errno != $ERROR_SERVICE_NOT_ACTIVE) +{ + --error 0,$ERROR_SERVICE_CANNOT_ACCEPT_CTRL,$ERROR_SERVICE_NOT_ACTIVE + exec $sc_exe stop $service_name; + if($sys_errno != $ERROR_SERVICE_NOT_ACTIVE) + { + --real_sleep 0.1 + } +} +--enable_result_log + +echo # Delete service; +let $sys_errno=0; +--disable_result_log +exec $sc_exe delete $service_name; +--enable_result_log + +# Cleanup +source include/wait_until_disconnected.inc; +rmdir $ddir; + +#restart original server +connection default; +source include/start_mysqld.inc; + diff --git a/mysql-test/main/winservice_basic.result b/mysql-test/main/winservice_basic.result new file mode 100644 index 00000000000..a4de0080207 --- /dev/null +++ b/mysql-test/main/winservice_basic.result @@ -0,0 +1,12 @@ +# Kill the server +# run mysql_install_db with --service parameter +# Start service +# Connect with root user password=password +connect con1,localhost,root,$password,mysql; +select @@datadir; +@@datadir +DATADIR/ +# Stop service and wait until it is down +# Delete service +connection default; +# restart diff --git a/mysql-test/main/winservice_basic.test b/mysql-test/main/winservice_basic.test new file mode 100644 index 00000000000..33d072b4f70 --- /dev/null +++ b/mysql-test/main/winservice_basic.test @@ -0,0 +1,5 @@ +source include/windows.inc; +let $datadir_name=data; +let $service_name_prefix=mariadb; +let $password=password; +source winservice.inc; diff --git a/mysql-test/main/winservice_i18n.result b/mysql-test/main/winservice_i18n.result new file mode 100644 index 00000000000..873f0828b1f --- /dev/null +++ b/mysql-test/main/winservice_i18n.result @@ -0,0 +1,12 @@ +# Kill the server +# run mysql_install_db with --service parameter +# Start service +# Connect with root user password=パスワード +connect con1,localhost,root,$password,mysql; +select @@datadir; +@@datadir +DATADIR/ +# Stop service and wait until it is down +# Delete service +connection default; +# restart diff --git a/mysql-test/main/winservice_i18n.test b/mysql-test/main/winservice_i18n.test new file mode 100644 index 00000000000..c63b38fd2b8 --- /dev/null +++ b/mysql-test/main/winservice_i18n.test @@ -0,0 +1,7 @@ +source include/windows.inc; +source include/check_utf8_cli.inc; + +let $datadir_name=датадир; +let $service_name_prefix=mariadb_sörvis; +let $password=パスワード; +source winservice.inc; diff --git a/mysql-test/suite.pm b/mysql-test/suite.pm index 4cc6b410fa1..f30cc5ec431 100644 --- a/mysql-test/suite.pm +++ b/mysql-test/suite.pm @@ -87,6 +87,31 @@ sub skip_combinations { $skip{'main/ssl_verify_ip.test'} = 'x509v3 support required' unless $openssl_ver ge "1.0.2"; + sub utf8_command_line_ok() { + if (IS_WINDOWS) { + # Can use UTF8 on command line since Windows 10 1903 (10.0.18362) + # or if OS codepage is set to UTF8 + my($os_name, $os_major, $os_minor, $os_build, $os_id) = Win32::GetOSVersion(); + if($os_major lt 10){ + return 0; + } elsif($os_major gt 10 or $os_minor gt 0 or $os_build ge 18362){ + return 1; + } elsif(Win32::GetACP() eq 65001) { + return 1; + } + return 0; + } + return 1; + } + + $skip{'include/check_utf8_cli.inc'} = 'No utf8 command line support' + unless utf8_command_line_ok(); + + $skip{'include/no_utf8_cli.inc'} = 'Not tested with utf8 command line support' + unless !utf8_command_line_ok(); + + $skip{'include/check_windows_admin.inc'} = 'Requires admin privileges' + unless IS_WINDOWS and Win32::IsAdminUser(); %skip; } diff --git a/mysys/CMakeLists.txt b/mysys/CMakeLists.txt index 760c3c1475d..758243df10f 100644 --- a/mysys/CMakeLists.txt +++ b/mysys/CMakeLists.txt @@ -54,7 +54,6 @@ IF (WIN32) my_wincond.c my_winerr.c my_winfile.c - my_conio.c my_minidump.cc my_win_popen.cc) ENDIF() diff --git a/mysys/charset.c b/mysys/charset.c index 19cad76fdf4..da6180dccba 100644 --- a/mysys/charset.c +++ b/mysys/charset.c @@ -1209,30 +1209,17 @@ size_t escape_string_for_mysql(CHARSET_INFO *charset_info, #ifdef BACKSLASH_MBTAIL -static CHARSET_INFO *fs_cset_cache= NULL; - CHARSET_INFO *fs_character_set() { - if (!fs_cset_cache) - { - char buf[10]= "cp"; - GetLocaleInfo(LOCALE_SYSTEM_DEFAULT, LOCALE_IDEFAULTANSICODEPAGE, - buf+2, sizeof(buf)-3); - /* - We cannot call get_charset_by_name here - because fs_character_set() is executed before - LOCK_THD_charset mutex initialization, which - is used inside get_charset_by_name. - As we're now interested in cp932 only, - let's just detect it using strcmp(). - */ - fs_cset_cache= - #ifdef HAVE_CHARSET_cp932 - !strcmp(buf, "cp932") ? &my_charset_cp932_japanese_ci : - #endif - &my_charset_bin; - } - return fs_cset_cache; + static CHARSET_INFO *fs_cset_cache; + if (fs_cset_cache) + return fs_cset_cache; +#ifdef HAVE_CHARSET_cp932 + else if (GetACP() == 932) + return fs_cset_cache= &my_charset_cp932_japanese_ci; +#endif + else + return fs_cset_cache= &my_charset_bin; } #endif @@ -1393,8 +1380,8 @@ static const MY_CSET_OS_NAME charsets[] = #ifdef UNCOMMENT_THIS_WHEN_WL_WL_4024_IS_DONE {"cp54936", "gb18030", my_cs_exact}, #endif - {"cp65001", "utf8", my_cs_exact}, - + {"cp65001", "utf8mb4", my_cs_exact}, + {"cp65001", "utf8mb3", my_cs_approx}, #else /* not Windows */ {"646", "latin1", my_cs_approx}, /* Default on Solaris */ @@ -1517,9 +1504,15 @@ const char* my_default_csname() const char* csname = NULL; #ifdef _WIN32 char cpbuf[64]; - int cp = GetConsoleCP(); - if (cp == 0) - cp = GetACP(); + UINT cp; + if (GetACP() == CP_UTF8) + cp= CP_UTF8; + else + { + cp= GetConsoleCP(); + if (cp == 0) + cp= GetACP(); + } snprintf(cpbuf, sizeof(cpbuf), "cp%d", (int)cp); csname = my_os_charset_to_mysql_charset(cpbuf); #elif defined(HAVE_SETLOCALE) && defined(HAVE_NL_LANGINFO) @@ -1528,3 +1521,90 @@ const char* my_default_csname() #endif return csname ? csname : MYSQL_DEFAULT_CHARSET_NAME; } + + +#ifdef _WIN32 +/** + Extract codepage number from "cpNNNN" string, + and check that this codepage is supported. + + @return 0 - invalid codepage(or unsupported) + > 0 - valid codepage number. +*/ +static UINT get_codepage(const char *s) +{ + UINT cp; + if (s[0] != 'c' || s[1] != 'p') + { + DBUG_ASSERT(0); + return 0; + } + cp= strtoul(s + 2, NULL, 10); + if (!IsValidCodePage(cp)) + { + /* + Can happen also with documented CP, i.e 51936 + Perhaps differs from one machine to another. + */ + return 0; + } + return cp; +} + +static UINT mysql_charset_to_codepage(const char *my_cs_name) +{ + const MY_CSET_OS_NAME *csp; + UINT cp=0,tmp; + for (csp= charsets; csp->os_name; csp++) + { + if (!strcasecmp(csp->my_name, my_cs_name)) + { + switch (csp->param) + { + case my_cs_exact: + tmp= get_codepage(csp->os_name); + if (tmp) + return tmp; + break; + case my_cs_approx: + /* + don't return just yet, perhaps there is a better + (exact) match later. + */ + if (!cp) + cp= get_codepage(csp->os_name); + continue; + + default: + return 0; + } + } + } + return cp; +} + +/** Set console codepage for MariaDB's charset name */ +int my_set_console_cp(const char *csname) +{ + UINT cp; + if (fileno(stdout) < 0 || !isatty(fileno(stdout))) + return 0; + cp= mysql_charset_to_codepage(csname); + if (!cp) + { + /* No compatible os charset.*/ + return -1; + } + + if (GetConsoleOutputCP() != cp && !SetConsoleOutputCP(cp)) + { + return -1; + } + + if (GetConsoleCP() != cp && !SetConsoleCP(cp)) + { + return -1; + } + return 0; +} +#endif diff --git a/mysys/get_password.c b/mysys/get_password.c index 24befa6b5df..18286fd9e39 100644 --- a/mysys/get_password.c +++ b/mysys/get_password.c @@ -62,35 +62,58 @@ char *get_tty_password(const char *opt_message) { - char to[80]; - char *pos=to,*end=to+sizeof(to)-1; + wchar_t wbuf[80]; + char *to; + int to_len; + UINT cp; + wchar_t *pos=wbuf,*end=wbuf + array_elements(wbuf)-1; DBUG_ENTER("get_tty_password"); _cputs(opt_message ? opt_message : "Enter password: "); for (;;) { - char tmp; - tmp=_getch(); - if (tmp == '\b' || (int) tmp == 127) + int wc; + wc=_getwch(); + if (wc == '\b' || wc == 127) { - if (pos != to) + if (pos != wbuf) { - _cputs("\b \b"); - pos--; - continue; + _cputs("\b \b"); + pos--; + continue; } } - if (tmp == '\n' || tmp == '\r' || tmp == 3) + if (wc == '\n' || wc == '\r' || wc == 3 || pos == end) break; - if (iscntrl(tmp) || pos == end) + if (iswcntrl(wc)) continue; - _cputs("*"); - *(pos++) = tmp; + + /* Do not print '*' for half-unicode char(high surrogate)*/ + if (wc < 0xD800 || wc > 0xDBFF) + { + _cputs("*"); + } + *(pos++)= (wchar_t)wc; } - while (pos != to && isspace(pos[-1]) == ' ') - pos--; /* Allow dummy space at end */ *pos=0; _cputs("\n"); - DBUG_RETURN(my_strdup(PSI_INSTRUMENT_ME, to,MYF(MY_FAE))); + + /* + Allocate output string, and convert UTF16 password to output codepage. + */ + cp= GetACP() == CP_UTF8 ? CP_UTF8 : GetConsoleCP(); + + if (!(to_len= WideCharToMultiByte(cp, 0, wbuf, -1, NULL, 0, NULL, NULL))) + DBUG_RETURN(NULL); + + if (!(to= my_malloc(PSI_INSTRUMENT_ME, to_len, MYF(MY_FAE)))) + DBUG_RETURN(NULL); + + if (!WideCharToMultiByte(cp, 0, wbuf, -1, to, to_len, NULL, NULL)) + { + my_free(to); + DBUG_RETURN(NULL); + } + DBUG_RETURN(to); } #else diff --git a/mysys/my_conio.c b/mysys/my_conio.c deleted file mode 100644 index abcfa9798ec..00000000000 --- a/mysys/my_conio.c +++ /dev/null @@ -1,223 +0,0 @@ -/* Copyright (c) 2000, 2005, 2007 MySQL AB - Use is subject to license terms - - 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 */ - - -#include "mysys_priv.h" - -#ifdef _WIN32 - -static HANDLE my_coninpfh= 0; /* console input */ - -/* - functions my_pthread_auto_mutex_lock & my_pthread_auto_mutex_free - are experimental at this moment, they are intended to bring - ability of protecting code sections without necessity to explicitly - initialize synchronization object in one of threads - - if found useful they are to be exported in mysys -*/ - - -/* - int my_pthread_auto_mutex_lock(HANDLE* ph, const char* name, - int id, int time) - NOTES - creates a mutex with given name and tries to lock it time msec. - mutex name is appended with id to allow system wide or process wide - locks. Handle to created mutex returned in ph argument. - - RETURN - 0 thread owns mutex - <>0 error -*/ - -static -int my_pthread_auto_mutex_lock(HANDLE* ph, const char* name, int id, int time) -{ - DWORD res; - char tname[FN_REFLEN]; - - sprintf(tname, "%s-%08X", name, id); - - *ph= CreateMutex(NULL, FALSE, tname); - if (*ph == NULL) - return GetLastError(); - - res= WaitForSingleObject(*ph, time); - - if (res == WAIT_TIMEOUT) - return ERROR_SEM_TIMEOUT; - - if (res == WAIT_FAILED) - return GetLastError(); - - return 0; -} - -/* - int my_pthread_auto_mutex_free(HANDLE* ph) - - NOTES - releases a mutex. - - RETURN - 0 thread released mutex - <>0 error - -*/ -static -int my_pthread_auto_mutex_free(HANDLE* ph) -{ - if (*ph) - { - ReleaseMutex(*ph); - CloseHandle(*ph); - *ph= NULL; - } - - return 0; -} - - -#define pthread_auto_mutex_decl(name) \ - HANDLE __h##name= NULL; - -#define pthread_auto_mutex_lock(name, proc, time) \ - my_pthread_auto_mutex_lock(&__h##name, #name, (proc), (time)) - -#define pthread_auto_mutex_free(name) \ - my_pthread_auto_mutex_free(&__h##name) - - -/* - char* my_cgets() - - NOTES - Replaces _cgets from libc to support input of more than 255 chars. - Reads from the console via ReadConsole into buffer which - should be at least clen characters. - Actual length of string returned in plen. - - WARNING - my_cgets() does NOT check the pushback character buffer (i.e., _chbuf). - Thus, my_cgets() will not return any character that is pushed back by - the _ungetch() call. - - RETURN - string pointer ok - NULL Error - -*/ - -char* my_cgets(char *buffer, size_t clen, size_t* plen) -{ - ULONG state; - char *result; - DWORD plen_res; - CONSOLE_SCREEN_BUFFER_INFO csbi; - - pthread_auto_mutex_decl(my_conio_cs); - - /* lock the console for the current process*/ - if (pthread_auto_mutex_lock(my_conio_cs, GetCurrentProcessId(), INFINITE)) - { - /* can not lock console */ - pthread_auto_mutex_free(my_conio_cs); - return NULL; - } - - /* init console input */ - if (my_coninpfh == 0) - { - /* same handle will be used until process termination */ - my_coninpfh= CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, 0, NULL); - } - - if (my_coninpfh == INVALID_HANDLE_VALUE) - { - /* unlock the console */ - pthread_auto_mutex_free(my_conio_cs); - return(NULL); - } - - GetConsoleMode((HANDLE)my_coninpfh, &state); - SetConsoleMode((HANDLE)my_coninpfh, ENABLE_LINE_INPUT | - ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT); - - GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi); - - /* - there is no known way to determine allowed buffer size for input - though it is known it should not be more than 64K - so we cut 64K and try first size of screen buffer - if it is still to large we cut half of it and try again - later we may want to cycle from MY_MIN(clen, 65535) to allowed size - with small decrement to determine exact allowed buffer - */ - clen= MY_MIN(clen, 65535); - do - { - clen= MY_MIN(clen, (size_t) csbi.dwSize.X*csbi.dwSize.Y); - if (!ReadConsole((HANDLE)my_coninpfh, (LPVOID)buffer, (DWORD) clen - 1, &plen_res, - NULL)) - { - result= NULL; - clen>>= 1; - } - else - { - result= buffer; - break; - } - } - while (GetLastError() == ERROR_NOT_ENOUGH_MEMORY); - *plen= plen_res; - - /* We go here on error reading the string (Ctrl-C for example) */ - if (!*plen) - result= NULL; /* purecov: inspected */ - - if (result != NULL) - { - if (*plen > 1 && buffer[*plen - 2] == '\r') - { - *plen= *plen - 2; - } - else - { - if (*plen > 0 && buffer[*plen - 1] == '\r') - { - char tmp[3]; - DWORD tmplen= (DWORD)sizeof(tmp); - - *plen= *plen - 1; - /* read /n left in the buffer */ - ReadConsole((HANDLE)my_coninpfh, (LPVOID)tmp, tmplen, &tmplen, NULL); - } - } - buffer[*plen]= '\0'; - } - - SetConsoleMode((HANDLE)my_coninpfh, state); - /* unlock the console */ - pthread_auto_mutex_free(my_conio_cs); - - return result; -} - -#endif /* _WIN32 */ diff --git a/mysys/my_getopt.c b/mysys/my_getopt.c index 3fe025ba808..653fe844093 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,45 @@ 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; + if (!(len= strlen(value))) + 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 + { + 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 +603,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 +649,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 +750,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 +897,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/mysys/my_init.c b/mysys/my_init.c index d201d45a4ee..2f21bcb735f 100644 --- a/mysys/my_init.c +++ b/mysys/my_init.c @@ -34,6 +34,7 @@ #endif static void my_win_init(void); static my_bool win32_init_tcp_ip(); +static void setup_codepages(); #else #define my_win_init() #endif @@ -67,6 +68,69 @@ static ulong atoi_octal(const char *str) MYSQL_FILE *mysql_stdin= NULL; static MYSQL_FILE instrumented_stdin; +#ifdef _WIN32 +static UINT orig_console_cp, orig_console_output_cp; + +static void reset_console_cp(void) +{ + /* + We try not to call SetConsoleCP unnecessarily, to workaround a bug on + older Windows 10 (1803), which could switch truetype console fonts to + raster, eventhough SetConsoleCP would be a no-op (switch from UTF8 to UTF8). + */ + if (GetConsoleCP() != orig_console_cp) + SetConsoleCP(orig_console_cp); + if (GetConsoleOutputCP() != orig_console_output_cp) + SetConsoleOutputCP(orig_console_output_cp); +} + +/* + The below fixes discrepancies in console output and + command line parameter encoding. command line is in + ANSI codepage, output to console by default is in OEM, but + we like them to be in the same encoding. + + We do this only if current codepage is UTF8, i.e when we + know we're on Windows that can handle UTF8 well. +*/ +static void setup_codepages() +{ + UINT acp; + BOOL is_a_tty= fileno(stdout) >= 0 && isatty(fileno(stdout)); + + if (is_a_tty) + { + /* + Save console codepages, in case we change them, + to restore them on exit. + */ + orig_console_cp= GetConsoleCP(); + orig_console_output_cp= GetConsoleOutputCP(); + if (orig_console_cp && orig_console_output_cp) + atexit(reset_console_cp); + } + + if ((acp= GetACP()) != CP_UTF8) + return; + + /* + Use setlocale to make mbstowcs/mkdir/getcwd behave, see + https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setlocale-wsetlocale + */ + setlocale(LC_ALL, "en_US.UTF8"); + + if (is_a_tty && (orig_console_cp != acp || orig_console_output_cp != acp)) + { + /* + If ANSI codepage is UTF8, we actually want to switch console + to it as well. + */ + SetConsoleCP(acp); + SetConsoleOutputCP(acp); + } +} +#endif + /** Initialize my_sys functions, resources and variables @@ -337,6 +401,17 @@ static void my_win_init(void) _tzset(); + /* + We do not want text translation (LF->CRLF) + when stdout is console/terminal, it is buggy + */ + if (fileno(stdout) >= 0 && isatty(fileno(stdout))) + (void)setmode(fileno(stdout), O_BINARY); + + if (fileno(stderr) >= 0 && isatty(fileno(stderr))) + (void) setmode(fileno(stderr), O_BINARY); + + setup_codepages(); DBUG_VOID_RETURN; } diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 9ec686ef9d9..3c9b7246309 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -482,10 +482,11 @@ IF(WIN32) MYSQL_ADD_EXECUTABLE(mariadb-install-db mysql_install_db.cc ${CMAKE_CURRENT_BINARY_DIR}/mysql_bootstrap_sql.c + password.c COMPONENT Server ) SET_TARGET_PROPERTIES(mariadb-install-db PROPERTIES COMPILE_FLAGS -DINSTALL_PLUGINDIR=${INSTALL_PLUGINDIR}) - TARGET_LINK_LIBRARIES(mariadb-install-db mysys shlwapi) + TARGET_LINK_LIBRARIES(mariadb-install-db mysys mysys_ssl shlwapi) ADD_LIBRARY(winservice STATIC winservice.c) TARGET_LINK_LIBRARIES(winservice shell32) diff --git a/sql/mysql_install_db.cc b/sql/mysql_install_db.cc index f712e29b843..d2fcb5858a8 100644 --- a/sql/mysql_install_db.cc +++ b/sql/mysql_install_db.cc @@ -21,6 +21,7 @@ #include "mariadb.h" #include <my_getopt.h> #include <m_string.h> +#include <password.h> #include <windows.h> #include <shellapi.h> @@ -30,6 +31,7 @@ #include <sddl.h> struct IUnknown; #include <shlwapi.h> +#include <winservice.h> #include <string> @@ -442,16 +444,14 @@ static int create_myini() } -static const char update_root_passwd_part1[]= +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',PASSWORD("; -static const char update_root_passwd_part2[]= - ")) where User='root';\n"; -static const char remove_default_user_cmd[]= + "'$.authentication_string','%s') where User='root';\n"; +static constexpr char remove_default_user_cmd[]= "DELETE FROM mysql.user where User='';\n"; -static const char allow_remote_root_access_cmd[]= +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" @@ -870,18 +870,10 @@ static int create_db_instance(const char *datadir) /* 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); + 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); } @@ -916,7 +908,7 @@ end: auto sc_manager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (sc_manager) { - auto sc_handle= OpenServiceA(sc_manager,opt_service, DELETE); + auto sc_handle= OpenService(sc_manager,opt_service, DELETE); if (sc_handle) { DeleteService(sc_handle); diff --git a/sql/mysql_upgrade_service.cc b/sql/mysql_upgrade_service.cc index 7438ab131ea..02fae11a260 100644 --- a/sql/mysql_upgrade_service.cc +++ b/sql/mysql_upgrade_service.cc @@ -374,13 +374,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 @@ -465,13 +469,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, @@ -526,6 +525,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 e7e5af8a2c8..74c32082629 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..0d7bc603468 100644 --- a/sql/upgrade_conf_file.cc +++ b/sql/upgrade_conf_file.cc @@ -25,6 +25,8 @@ Note : the list below only includes the default-compiled server and none of the loadable plugins. */ +#include <my_global.h> +#include <my_sys.h> #include <windows.h> #include <initializer_list> #include <stdlib.h> @@ -158,51 +160,159 @@ 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) + +#define MY_INI_SECTION_SIZE 32 * 1024 + 3 + +static bool is_utf8_str(const char *s) +{ + MY_STRCOPY_STATUS status; + const struct charset_info_st *cs= &my_charset_utf8mb4_bin; + size_t len= strlen(s); + if (!len) + return true; + cs->cset->well_formed_char_length(cs, s, s + len, len, &status); + return status.m_well_formed_error_pos == nullptr; +} + + +static UINT get_system_acp() { -#define MY_INI_SECTION_SIZE 32*1024 +3 + 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; +} + + +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]; - for (const char *section_name : { "mysqld","server","mariadb" }) + DWORD size= GetPrivateProfileSection(section_name, section_data, + MY_INI_SECTION_SIZE, myini_path); + if (size == MY_INI_SECTION_SIZE - 2) { - DWORD size = GetPrivateProfileSection(section_name, section_data, MY_INI_SECTION_SIZE, myini_path); - if (size == MY_INI_SECTION_SIZE - 2) - { - return -1; - } + return -1; + } - for (char *keyval = section_data; *keyval; keyval += strlen(keyval) + 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++) + /*Convert a value, if it is not already UTF-8*/ + 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 true; +} + + +/** + 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; +} diff --git a/sql/winmain.cc b/sql/winmain.cc index f999767cb27..bdad409065d 100644 --- a/sql/winmain.cc +++ b/sql/winmain.cc @@ -55,6 +55,7 @@ #include <windows.h> #include <string> #include <cassert> +#include <winservice.h> static SERVICE_STATUS svc_status{SERVICE_WIN32_OWN_PROCESS}; static SERVICE_STATUS_HANDLE svc_status_handle; diff --git a/sql/winservice.c b/sql/winservice.c index a11087e5cd5..d4e3bb0944d 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 @@ -284,16 +298,9 @@ int get_mysql_service_properties(const wchar_t *bin_path, } } - if (props->datadir[0]) - { - normalize_path(props->datadir, MAX_PATH); - /* Check if datadir really exists */ - if (GetFileAttributes(props->datadir) == INVALID_FILE_ATTRIBUTES) - goto end; - } - else + if (props->datadir[0] == 0 || fix_and_check_datadir(props)) { - /* 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 f9ab3eda332..a14ee73d146 100644 --- a/sql/winservice.h +++ b/sql/winservice.h @@ -17,11 +17,22 @@ /* Extract properties of a windows service binary path */ +#pragma once + #ifdef __cplusplus extern "C" { #endif -#include <windows.h> +#include <windows.h> +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4995) +#endif +#include <winsvc.h> +#ifdef _MSC_VER +#pragma warning(pop) +#endif + typedef struct mysqld_service_properties_st { char mysqld_exe[MAX_PATH]; @@ -32,9 +43,171 @@ typedef struct mysqld_service_properties_st int version_patch; } mysqld_service_properties; -extern int get_mysql_service_properties(const wchar_t *bin_path, +extern int get_mysql_service_properties(const wchar_t *bin_path, mysqld_service_properties *props); + +#if !defined(UNICODE) +/* + The following wrappers workaround Windows bugs + with CreateService/OpenService with ANSI codepage UTF8. + + Apparently, these function in ANSI mode, for this codepage only + do *not* behave as expected (as-if string parameters were + converted to UTF16 and "wide" function were called) +*/ +#include <malloc.h> +static inline wchar_t* awstrdup(const char *str) +{ + if (!str) + return NULL; + size_t len= strlen(str) + 1; + wchar_t *wstr= (wchar_t *) malloc(sizeof(wchar_t)*len); + if (MultiByteToWideChar(GetACP(), 0, str, (int)len, wstr, (int)len) == 0) + { + free(wstr); + return NULL; + } + return wstr; +} + +#define AWSTRDUP(dest, src) \ + dest= awstrdup(src); \ + if (src && !dest) \ + { \ + ok= FALSE; \ + last_error = ERROR_OUTOFMEMORY; \ + goto end; \ + } + +static inline SC_HANDLE my_OpenService(SC_HANDLE hSCManager, LPCSTR lpServiceName, DWORD dwDesiredAccess) +{ + wchar_t *w_ServiceName= NULL; + BOOL ok=TRUE; + DWORD last_error=0; + SC_HANDLE sch=NULL; + + AWSTRDUP(w_ServiceName, lpServiceName); + sch= OpenServiceW(hSCManager, w_ServiceName, dwDesiredAccess); + if (!sch) + { + ok= FALSE; + last_error= GetLastError(); + } + +end: + free(w_ServiceName); + if (!ok) + SetLastError(last_error); + return sch; +} + +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) +{ + wchar_t *w_ServiceName= NULL; + wchar_t *w_DisplayName= NULL; + wchar_t *w_BinaryPathName= NULL; + wchar_t *w_LoadOrderGroup= NULL; + wchar_t *w_Dependencies= NULL; + wchar_t *w_ServiceStartName= NULL; + wchar_t *w_Password= NULL; + SC_HANDLE sch = NULL; + DWORD last_error=0; + BOOL ok= TRUE; + + AWSTRDUP(w_ServiceName,lpServiceName); + AWSTRDUP(w_DisplayName,lpDisplayName); + AWSTRDUP(w_BinaryPathName, lpBinaryPathName); + AWSTRDUP(w_LoadOrderGroup, lpLoadOrderGroup); + AWSTRDUP(w_Dependencies, lpDependencies); + AWSTRDUP(w_ServiceStartName, lpServiceStartName); + AWSTRDUP(w_Password, lpPassword); + + sch= CreateServiceW( + hSCManager, w_ServiceName, w_DisplayName, dwDesiredAccess, dwServiceType, + dwStartType, dwErrorControl, w_BinaryPathName, w_LoadOrderGroup, + lpdwTagId, w_Dependencies, w_ServiceStartName, w_Password); + if(!sch) + { + ok= FALSE; + last_error= GetLastError(); + } + +end: + free(w_ServiceName); + free(w_DisplayName); + free(w_BinaryPathName); + free(w_LoadOrderGroup); + free(w_Dependencies); + free(w_ServiceStartName); + free(w_Password); + + if (!ok) + SetLastError(last_error); + return sch; +} + +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) +{ + wchar_t *w_DisplayName= NULL; + wchar_t *w_BinaryPathName= NULL; + wchar_t *w_LoadOrderGroup= NULL; + wchar_t *w_Dependencies= NULL; + wchar_t *w_ServiceStartName= NULL; + wchar_t *w_Password= NULL; + SC_HANDLE sch = NULL; + DWORD last_error=0; + BOOL ok= TRUE; + + AWSTRDUP(w_DisplayName, lpDisplayName); + AWSTRDUP(w_BinaryPathName, lpBinaryPathName); + AWSTRDUP(w_LoadOrderGroup, lpLoadOrderGroup); + AWSTRDUP(w_Dependencies, lpDependencies); + AWSTRDUP(w_ServiceStartName, lpServiceStartName); + AWSTRDUP(w_Password, lpPassword); + + ok= ChangeServiceConfigW( + hService, dwServiceType, dwStartType, dwErrorControl, w_BinaryPathName, + w_LoadOrderGroup, lpdwTagId, w_Dependencies, w_ServiceStartName, + w_Password, w_DisplayName); + if (!ok) + { + last_error= GetLastError(); + } + +end: + free(w_DisplayName); + free(w_BinaryPathName); + free(w_LoadOrderGroup); + free(w_Dependencies); + free(w_ServiceStartName); + free(w_Password); + + if (last_error) + SetLastError(last_error); + return ok; +} +#undef AWSTRDUP + +#undef OpenService +#define OpenService my_OpenService +#undef ChangeServiceConfig +#define ChangeServiceConfig my_ChangeServiceConfig +#undef CreateService +#define CreateService my_CreateService +#endif + #ifdef __cplusplus } #endif diff --git a/win/packaging/extra.wxs.in b/win/packaging/extra.wxs.in index d003cea26d6..d99f7cbb0bd 100644 --- a/win/packaging/extra.wxs.in +++ b/win/packaging/extra.wxs.in @@ -464,7 +464,7 @@ Section="mysqld" Name="my.ini" Key="character-set-server" - Value="utf8" /> + Value="utf8mb4" /> </Component> <!-- Shortcuts in program menu (mysql client etc) --> 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) diff --git a/win/upgrade_wizard/upgradeDlg.cpp b/win/upgrade_wizard/upgradeDlg.cpp index 80f7c18f757..10a1787c231 100644 --- a/win/upgrade_wizard/upgradeDlg.cpp +++ b/win/upgrade_wizard/upgradeDlg.cpp @@ -15,6 +15,7 @@ #include <vector> #include <winservice.h> +#include <locale.h> using namespace std; @@ -141,24 +142,24 @@ void CUpgradeDlg::PopulateServicesList() ErrorExit("OpenSCManager failed"); } - static BYTE buf[64*1024]; + static BYTE buf[2*64*1024]; static BYTE configBuffer[8*1024]; DWORD bufsize= sizeof(buf); DWORD bufneed; DWORD num_services; - BOOL ok= EnumServicesStatusEx(scm, SC_ENUM_PROCESS_INFO, SERVICE_WIN32, + BOOL ok= EnumServicesStatusExW(scm, SC_ENUM_PROCESS_INFO, SERVICE_WIN32, SERVICE_STATE_ALL, buf, bufsize, &bufneed, &num_services, NULL, NULL); if(!ok) ErrorExit("EnumServicesStatusEx failed"); - LPENUM_SERVICE_STATUS_PROCESS info = - (LPENUM_SERVICE_STATUS_PROCESS)buf; + LPENUM_SERVICE_STATUS_PROCESSW info = + (LPENUM_SERVICE_STATUS_PROCESSW)buf; int index=-1; for (ULONG i=0; i < num_services; i++) { - SC_HANDLE service= OpenService(scm, info[i].lpServiceName, + SC_HANDLE service= OpenServiceW(scm, info[i].lpServiceName, SERVICE_QUERY_CONFIG); if (!service) continue; @@ -187,7 +188,11 @@ void CUpgradeDlg::PopulateServicesList() ServiceProperties props; props.myini= service_props.inifile; props.datadir= service_props.datadir; - props.servicename = info[i].lpServiceName; + char service_name_buf[1024]; + WideCharToMultiByte(GetACP(), 0, info[i].lpServiceName, -1, + service_name_buf, sizeof(service_name_buf), + 0, 0); + props.servicename= service_name_buf; if (service_props.version_major) { char ver[64]; @@ -198,7 +203,7 @@ void CUpgradeDlg::PopulateServicesList() else props.version= "<unknown>"; - index = m_Services.AddString(info[i].lpServiceName); + index = m_Services.AddString(service_name_buf); services.resize(index+1); services[index] = props; } @@ -267,6 +272,11 @@ BOOL CUpgradeDlg::OnInitDialog() m_Progress.ShowWindow(SW_HIDE); m_Ok.EnableWindow(FALSE); + if (GetACP() == CP_UTF8) + { + /* Required for mbstowcs, used in some functions.*/ + setlocale(LC_ALL, "en_US.UTF8"); + } PopulateServicesList(); return TRUE; // return TRUE unless you set the focus to a control } |