/* Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved. 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mysys_priv.h" #include "my_sys.h" #ifdef _WIN32 /* Windows console handling */ /* TODO : Find a relationship between the following two macros and get rid of one. */ /* Maximum line length on Windows console */ #define MAX_CONSOLE_LINE_SIZE 65535 /* Maximum number of characters that can be entered on single line in the console (including \r\n). */ #define MAX_NUM_OF_CHARS_TO_READ 24530 /** Determine if a file is a windows console @param file Input stream @return @retval 0 if file is not Windows console @retval 1 if file is Windows console */ my_bool my_win_is_console(FILE *file) { DWORD mode; if (GetConsoleMode((HANDLE) _get_osfhandle(_fileno(file)), &mode)) return 1; return 0; } /** Read line from Windows console using Unicode API and translate input to session character set. Note, as Windows API breaks supplementary characters into two wchar_t pieces, we cannot read and convert individual wchar_t values separately. So let's use a buffer for Unicode console input, and then convert it to "cs" in a single shot. String is terminated with '\0' character. @param cs [IN] Character string to convert to. @param mbbuf [OUT] Write input data here. @param mbbufsize [IN] Number of bytes available in mbbuf. @param nread [OUT] Number of bytes read. @retval Pointer to mbbuf, or NULL on I/0 error. */ char * my_win_console_readline(const CHARSET_INFO *cs, char *mbbuf, size_t mbbufsize, size_t *nread) { uint dummy_errors; static wchar_t u16buf[MAX_CONSOLE_LINE_SIZE + 1]; size_t mblen= 0; DWORD console_mode; DWORD nchars; HANDLE console= GetStdHandle(STD_INPUT_HANDLE); DBUG_ASSERT(mbbufsize > 0); /* Need space for at least trailing '\0' */ GetConsoleMode(console, &console_mode); SetConsoleMode(console, ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT); if (!ReadConsoleW(console, u16buf, MAX_NUM_OF_CHARS_TO_READ, &nchars, NULL)) { SetConsoleMode(console, console_mode); return NULL; } *nread= nchars; /* Set length of string */ if (nchars >= 2 && u16buf[nchars - 2] == L'\r') nchars-= 2; else if ((nchars == MAX_NUM_OF_CHARS_TO_READ) && (u16buf[nchars - 1] == L'\r')) /* Special case 1 - \r\n straddles the boundary */ nchars--; else if ((nchars == 1) && (u16buf[0] == L'\n')) /* Special case 2 - read a single '\n'*/ nchars--; SetConsoleMode(console, console_mode); /* Convert Unicode to session character set */ if (nchars != 0) mblen= my_convert(mbbuf, mbbufsize - 1, cs, (const char *) u16buf, nchars * sizeof(wchar_t), &my_charset_utf16le_bin, &dummy_errors); DBUG_ASSERT(mblen < mbbufsize); /* Safety */ mbbuf[mblen]= 0; return mbbuf; } /** Translate client charset to Windows wchars for console I/O. Unlike copy_and_convert(), in case of a wrong multi-byte sequence we don't print '?' character, we fallback to ISO-8859-1 instead. This gives a better idea how binary data (e.g. BLOB) look like. @param cs Character set of the input string @param from Input string @param from_length Length of the input string @param to[OUT] Write Unicode data here @param to_chars Number of characters available in "to" */ static size_t my_mbstou16s(const CHARSET_INFO *cs, const uchar * from, size_t from_length, wchar_t *to, size_t to_chars) { const CHARSET_INFO *to_cs= &my_charset_utf16le_bin; const uchar *from_end= from + from_length; wchar_t *to_orig= to, *to_end= to + to_chars; my_charset_conv_mb_wc mb_wc= cs->cset->mb_wc; my_charset_conv_wc_mb wc_mb= to_cs->cset->wc_mb; while (from < from_end) { int cnvres; my_wc_t wc; if ((cnvres= (*mb_wc)(cs, &wc, from, from_end)) > 0) { if (!wc) break; from+= cnvres; } else if (cnvres == MY_CS_ILSEQ) { wc= (my_wc_t) (uchar) *from; /* Fallback to ISO-8859-1 */ from+= 1; } else if (cnvres > MY_CS_TOOSMALL) { /* A correct multibyte sequence detected But it doesn't have Unicode mapping. */ wc= '?'; from+= (-cnvres); /* Note: cnvres is negative here */ } else /* Incomplete character */ { wc= (my_wc_t) (uchar) *from; /* Fallback to ISO-8859-1 */ from+= 1; } outp: if ((cnvres= (*wc_mb)(to_cs, wc, (uchar *) to, (uchar *) to_end)) > 0) { /* We can never convert only a part of wchar_t */ DBUG_ASSERT((cnvres % sizeof(wchar_t)) == 0); /* cnvres returns number of bytes, convert to number of wchar_t's */ to+= cnvres / sizeof(wchar_t); } else if (cnvres == MY_CS_ILUNI && wc != '?') { wc= '?'; goto outp; } else break; /* Not enough space */ } return to - to_orig; } /** Write a string in the given character set to Windows console. As Window breaks supplementary characters into two parts, we cannot use a simple loop sending the result of cs->cset->mb_wc() to console. So we converts string from client charset to an array of wchar_t, then write the array to console in a single shot. @param cs Character set of the string @param data String to print @param datalen Length of input string in bytes */ void my_win_console_write(const CHARSET_INFO *cs, const char *data, size_t datalen) { static wchar_t u16buf[MAX_CONSOLE_LINE_SIZE + 1]; size_t nchars= my_mbstou16s(cs, (const uchar *) data, datalen, u16buf, sizeof(u16buf)); DWORD nwritten; WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), u16buf, (DWORD) nchars, &nwritten, NULL); } /** Write a single-byte character to console. Note: one should not send parts of the same multi-byte character in separate consequent my_win_console_putc() calls. For multi-byte characters use my_win_colsole_write() instead. @param cs Character set of the input character @param c Character (single byte) */ void my_win_console_putc(const CHARSET_INFO *cs, int c) { char ch= (char) c; my_win_console_write(cs, &ch, 1); } /** Write a 0-terminated string to Windows console. @param cs Character set of the string to print @param data String to print */ void my_win_console_fputs(const CHARSET_INFO *cs, const char *data) { my_win_console_write(cs, data, strlen(data)); } /* Handle formatted output on the Windows console. */ void my_win_console_vfprintf(const CHARSET_INFO *cs, const char *fmt, va_list args) { static char buff[MAX_CONSOLE_LINE_SIZE + 1]; size_t len= vsnprintf(buff, sizeof(buff) - 1, fmt, args); my_win_console_write(cs, buff, len); } #include /** Translate Unicode command line parameters to the given character set (Typically to utf8mb4). Translated parameters are allocated using my_once_alloc(). @param tocs Character set to convert parameters to. @param[OUT] argc Write number of parameters here @param[OUT] argv Write pointer to allocated parameters here. */ int my_win_translate_command_line_args(const CHARSET_INFO *cs, int *argc, char ***argv) { int i, ac; char **av; wchar_t *command_line= GetCommandLineW(); wchar_t **wargs= CommandLineToArgvW(command_line, &ac); size_t nbytes= (ac + 1) * sizeof(char *); /* Allocate new command line parameter */ av= (char **) my_once_alloc(nbytes, MYF(MY_ZEROFILL)); for(i= 0; i < ac; i++) { uint dummy_errors; size_t arg_len= wcslen(wargs[i]); size_t len, alloced_len= arg_len * cs->mbmaxlen + 1; av[i]= (char *) my_once_alloc(alloced_len, MYF(0)); len= my_convert(av[i], alloced_len, cs, (const char *) wargs[i], arg_len * sizeof(wchar_t), &my_charset_utf16le_bin, &dummy_errors); DBUG_ASSERT(len < alloced_len); av[i][len]= '\0'; } *argv= av; *argc= ac; /* Cleanup on exit */ LocalFree((HLOCAL) wargs); return 0; } #endif /* _WIN32 */