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)