Skip to content

Commit a169155

Browse files
author
Amit Kapila
committed
Fix the MSVC build for versions 2015 and later.
Visual Studio 2015 and later versions should still be able to do the same as Visual Studio 2012, but the declaration of locale_name is missing in _locale_t, causing the code compilation to fail, hence this falls back instead on to enumerating all system locales by using EnumSystemLocalesEx to find the required locale name.  If the input argument is in Unix-style then we can get ISO Locale name directly by using GetLocaleInfoEx() with LCType as LOCALE_SNAME. In passing, change the documentation references of the now obsolete links. Note that this problem occurs only with NLS enabled builds. Author: Juan José Santamaría Flecha, Davinder Singh and Amit Kapila Reviewed-by: Ranier Vilela and Amit Kapila Backpatch-through: 9.5 Discussion: https://postgr.es/m/CAHzhFSFoJEWezR96um4-rg5W6m2Rj9Ud2CNZvV4NWc9tXV7aXQ@mail.gmail.com
1 parent cee9cad commit a169155

File tree

1 file changed

+203
-33
lines changed

1 file changed

+203
-33
lines changed

src/backend/utils/adt/pg_locale.c

+203-33
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ pg_perm_setlocale(int category, const char *locale)
234234
result = IsoLocaleName(locale);
235235
if (result == NULL)
236236
result = (char *) locale;
237+
elog(DEBUG3, "IsoLocaleName() executed; locale: \"%s\"", result);
237238
#endif /* WIN32 */
238239
break;
239240
#endif /* LC_MESSAGES */
@@ -971,65 +972,234 @@ cache_locale_time(void)
971972
* string. Furthermore, msvcr110.dll changed the undocumented _locale_t
972973
* content to carry locale names instead of locale identifiers.
973974
*
974-
* MinGW headers declare _create_locale(), but msvcrt.dll lacks that symbol.
975-
* IsoLocaleName() always fails in a MinGW-built postgres.exe, so only
976-
* Unix-style values of the lc_messages GUC can elicit localized messages. In
977-
* particular, every lc_messages setting that initdb can select automatically
978-
* will yield only C-locale messages. XXX This could be fixed by running the
979-
* fully-qualified locale name through a lookup table.
975+
* Visual Studio 2015 should still be able to do the same as Visual Studio
976+
* 2012, but the declaration of locale_name is missing in _locale_t, causing
977+
* this code compilation to fail, hence this falls back instead on to
978+
* enumerating all system locales by using EnumSystemLocalesEx to find the
979+
* required locale name. If the input argument is in Unix-style then we can
980+
* get ISO Locale name directly by using GetLocaleInfoEx() with LCType as
981+
* LOCALE_SNAME.
982+
*
983+
* MinGW headers declare _create_locale(), but msvcrt.dll lacks that symbol in
984+
* releases before Windows 8. IsoLocaleName() always fails in a MinGW-built
985+
* postgres.exe, so only Unix-style values of the lc_messages GUC can elicit
986+
* localized messages. In particular, every lc_messages setting that initdb
987+
* can select automatically will yield only C-locale messages. XXX This could
988+
* be fixed by running the fully-qualified locale name through a lookup table.
980989
*
981990
* This function returns a pointer to a static buffer bearing the converted
982991
* name or NULL if conversion fails.
983992
*
984-
* [1] http://msdn.microsoft.com/en-us/library/windows/desktop/dd373763.aspx
985-
* [2] http://msdn.microsoft.com/en-us/library/windows/desktop/dd373814.aspx
993+
* [1] https://docs.microsoft.com/en-us/windows/win32/intl/locale-identifiers
994+
* [2] https://docs.microsoft.com/en-us/windows/win32/intl/locale-names
995+
*/
996+
997+
#if _MSC_VER >= 1900
998+
/*
999+
* Callback function for EnumSystemLocalesEx() in get_iso_localename().
1000+
*
1001+
* This function enumerates all system locales, searching for one that matches
1002+
* an input with the format: <Language>[_<Country>], e.g.
1003+
* English[_United States]
1004+
*
1005+
* The input is a three wchar_t array as an LPARAM. The first element is the
1006+
* locale_name we want to match, the second element is an allocated buffer
1007+
* where the Unix-style locale is copied if a match is found, and the third
1008+
* element is the search status, 1 if a match was found, 0 otherwise.
1009+
*/
1010+
static BOOL CALLBACK
1011+
search_locale_enum(LPWSTR pStr, DWORD dwFlags, LPARAM lparam)
1012+
{
1013+
wchar_t test_locale[LOCALE_NAME_MAX_LENGTH];
1014+
wchar_t **argv;
1015+
1016+
(void) (dwFlags);
1017+
1018+
argv = (wchar_t **) lparam;
1019+
*argv[2] = (wchar_t) 0;
1020+
1021+
memset(test_locale, 0, sizeof(test_locale));
1022+
1023+
/* Get the name of the <Language> in English */
1024+
if (GetLocaleInfoEx(pStr, LOCALE_SENGLISHLANGUAGENAME,
1025+
test_locale, LOCALE_NAME_MAX_LENGTH))
1026+
{
1027+
/*
1028+
* If the enumerated locale does not have a hyphen ("en") OR the
1029+
* lc_message input does not have an underscore ("English"), we only
1030+
* need to compare the <Language> tags.
1031+
*/
1032+
if (wcsrchr(pStr, '-') == NULL || wcsrchr(argv[0], '_') == NULL)
1033+
{
1034+
if (_wcsicmp(argv[0], test_locale) == 0)
1035+
{
1036+
wcscpy(argv[1], pStr);
1037+
*argv[2] = (wchar_t) 1;
1038+
return FALSE;
1039+
}
1040+
}
1041+
1042+
/*
1043+
* We have to compare a full <Language>_<Country> tag, so we append
1044+
* the underscore and name of the country/region in English, e.g.
1045+
* "English_United States".
1046+
*/
1047+
else
1048+
{
1049+
size_t len;
1050+
1051+
wcscat(test_locale, L"_");
1052+
len = wcslen(test_locale);
1053+
if (GetLocaleInfoEx(pStr, LOCALE_SENGLISHCOUNTRYNAME,
1054+
test_locale + len,
1055+
LOCALE_NAME_MAX_LENGTH - len))
1056+
{
1057+
if (_wcsicmp(argv[0], test_locale) == 0)
1058+
{
1059+
wcscpy(argv[1], pStr);
1060+
*argv[2] = (wchar_t) 1;
1061+
return FALSE;
1062+
}
1063+
}
1064+
}
1065+
}
1066+
1067+
return TRUE;
1068+
}
1069+
1070+
/*
1071+
* This function converts a Windows locale name to an ISO formatted version
1072+
* for Visual Studio 2015 or greater.
1073+
*
1074+
* Returns NULL, if no valid conversion was found.
9861075
*/
9871076
static char *
988-
IsoLocaleName(const char *winlocname)
1077+
get_iso_localename(const char *winlocname)
9891078
{
990-
#ifdef _MSC_VER
991-
static char iso_lc_messages[32];
992-
_locale_t loct = NULL;
1079+
wchar_t wc_locale_name[LOCALE_NAME_MAX_LENGTH];
1080+
wchar_t buffer[LOCALE_NAME_MAX_LENGTH];
1081+
static char iso_lc_messages[LOCALE_NAME_MAX_LENGTH];
1082+
char *period;
1083+
int len;
1084+
int ret_val;
9931085

994-
if (pg_strcasecmp("c", winlocname) == 0 ||
995-
pg_strcasecmp("posix", winlocname) == 0)
1086+
/*
1087+
* Valid locales have the following syntax:
1088+
* <Language>[_<Country>[.<CodePage>]]
1089+
*
1090+
* GetLocaleInfoEx can only take locale name without code-page and for the
1091+
* purpose of this API the code-page doesn't matter.
1092+
*/
1093+
period = strchr(winlocname, '.');
1094+
if (period != NULL)
1095+
len = period - winlocname;
1096+
else
1097+
len = pg_mbstrlen(winlocname);
1098+
1099+
memset(wc_locale_name, 0, sizeof(wc_locale_name));
1100+
memset(buffer, 0, sizeof(buffer));
1101+
MultiByteToWideChar(CP_ACP, 0, winlocname, len, wc_locale_name,
1102+
LOCALE_NAME_MAX_LENGTH);
1103+
1104+
/*
1105+
* If the lc_messages is already an Unix-style string, we have a direct
1106+
* match with LOCALE_SNAME, e.g. en-US, en_US.
1107+
*/
1108+
ret_val = GetLocaleInfoEx(wc_locale_name, LOCALE_SNAME, (LPWSTR) &buffer,
1109+
LOCALE_NAME_MAX_LENGTH);
1110+
if (!ret_val)
9961111
{
997-
strcpy(iso_lc_messages, "C");
998-
return iso_lc_messages;
1112+
/*
1113+
* Search for a locale in the system that matches language and country
1114+
* name.
1115+
*/
1116+
wchar_t *argv[3];
1117+
1118+
argv[0] = wc_locale_name;
1119+
argv[1] = buffer;
1120+
argv[2] = (wchar_t *) &ret_val;
1121+
EnumSystemLocalesEx(search_locale_enum, LOCALE_WINDOWS, (LPARAM) argv,
1122+
NULL);
9991123
}
10001124

1001-
loct = _create_locale(LC_CTYPE, winlocname);
1002-
if (loct != NULL)
1125+
if (ret_val)
10031126
{
10041127
size_t rc;
10051128
char *hyphen;
10061129

10071130
/* Locale names use only ASCII, any conversion locale suffices. */
1008-
rc = wchar2char(iso_lc_messages, loct->locinfo->locale_name[LC_CTYPE],
1009-
sizeof(iso_lc_messages), NULL);
1010-
_free_locale(loct);
1131+
rc = wchar2char(iso_lc_messages, buffer, sizeof(iso_lc_messages), NULL);
10111132
if (rc == -1 || rc == sizeof(iso_lc_messages))
10121133
return NULL;
10131134

10141135
/*
1015-
* Since the message catalogs sit on a case-insensitive filesystem, we
1016-
* need not standardize letter case here. So long as we do not ship
1017-
* message catalogs for which it would matter, we also need not
1018-
* translate the script/variant portion, e.g. uz-Cyrl-UZ to
1019-
* uz_UZ@cyrillic. Simply replace the hyphen with an underscore.
1020-
*
1021-
* Note that the locale name can be less-specific than the value we
1022-
* would derive under earlier Visual Studio releases. For example,
1023-
* French_France.1252 yields just "fr". This does not affect any of
1024-
* the country-specific message catalogs available as of this writing
1025-
* (pt_BR, zh_CN, zh_TW).
1136+
* Simply replace the hyphen with an underscore. See comments in
1137+
* IsoLocaleName.
10261138
*/
10271139
hyphen = strchr(iso_lc_messages, '-');
10281140
if (hyphen)
10291141
*hyphen = '_';
10301142
return iso_lc_messages;
10311143
}
1032-
#endif /* _MSC_VER */
1144+
1145+
return NULL;
1146+
}
1147+
#endif /* _MSC_VER >= 1900 */
1148+
1149+
static char *
1150+
IsoLocaleName(const char *winlocname)
1151+
{
1152+
#if defined(_MSC_VER)
1153+
static char iso_lc_messages[LOCALE_NAME_MAX_LENGTH];
1154+
1155+
if (pg_strcasecmp("c", winlocname) == 0 ||
1156+
pg_strcasecmp("posix", winlocname) == 0)
1157+
{
1158+
strcpy(iso_lc_messages, "C");
1159+
return iso_lc_messages;
1160+
}
1161+
else
1162+
{
1163+
#if (_MSC_VER >= 1900) /* Visual Studio 2015 or later */
1164+
return get_iso_localename(winlocname);
1165+
#else
1166+
_locale_t loct;
1167+
1168+
loct = _create_locale(LC_CTYPE, winlocname);
1169+
if (loct != NULL)
1170+
{
1171+
size_t rc;
1172+
char *hyphen;
1173+
1174+
/* Locale names use only ASCII, any conversion locale suffices. */
1175+
rc = wchar2char(iso_lc_messages, loct->locinfo->locale_name[LC_CTYPE],
1176+
sizeof(iso_lc_messages), NULL);
1177+
_free_locale(loct);
1178+
if (rc == -1 || rc == sizeof(iso_lc_messages))
1179+
return NULL;
1180+
1181+
/*
1182+
* Since the message catalogs sit on a case-insensitive
1183+
* filesystem, we need not standardize letter case here. So long
1184+
* as we do not ship message catalogs for which it would matter,
1185+
* we also need not translate the script/variant portion, e.g.
1186+
* uz-Cyrl-UZ to uz_UZ@cyrillic. Simply replace the hyphen with
1187+
* an underscore.
1188+
*
1189+
* Note that the locale name can be less-specific than the value
1190+
* we would derive under earlier Visual Studio releases. For
1191+
* example, French_France.1252 yields just "fr". This does not
1192+
* affect any of the country-specific message catalogs available
1193+
* as of this writing (pt_BR, zh_CN, zh_TW).
1194+
*/
1195+
hyphen = strchr(iso_lc_messages, '-');
1196+
if (hyphen)
1197+
*hyphen = '_';
1198+
return iso_lc_messages;
1199+
}
1200+
#endif /* Visual Studio 2015 or later */
1201+
}
1202+
#endif /* defined(_MSC_VER) */
10331203
return NULL; /* Not supported on this version of msvc/mingw */
10341204
}
10351205
#endif /* WIN32 && LC_MESSAGES */

0 commit comments

Comments
 (0)