2011-09-01 13:02:40 +02:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* win32setlocale.c
|
|
|
|
* Wrapper to work around bugs in Windows setlocale() implementation
|
|
|
|
*
|
2018-01-03 05:30:12 +01:00
|
|
|
* Copyright (c) 2011-2018, PostgreSQL Global Development Group
|
2011-09-01 13:02:40 +02:00
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
|
|
|
* src/port/win32setlocale.c
|
|
|
|
*
|
|
|
|
*
|
2014-10-24 20:10:13 +02:00
|
|
|
* The setlocale() function in Windows is broken in two ways. First, it
|
|
|
|
* has a problem with locale names that have a dot in the country name. For
|
|
|
|
* example:
|
2011-09-01 13:02:40 +02:00
|
|
|
*
|
|
|
|
* "Chinese (Traditional)_Hong Kong S.A.R..950"
|
|
|
|
*
|
2014-10-24 20:10:13 +02:00
|
|
|
* For some reason, setlocale() doesn't accept that as argument, even though
|
|
|
|
* setlocale(LC_ALL, NULL) returns exactly that. Fortunately, it accepts
|
|
|
|
* various alternative names for such countries, so to work around the broken
|
|
|
|
* setlocale() function, we map the troublemaking locale names to accepted
|
|
|
|
* aliases, before calling setlocale().
|
|
|
|
*
|
|
|
|
* The second problem is that the locale name for "Norwegian (Bokmål)"
|
|
|
|
* contains a non-ASCII character. That's problematic, because it's not clear
|
|
|
|
* what encoding the locale name itself is supposed to be in, when you
|
|
|
|
* haven't yet set a locale. Also, it causes problems when the cluster
|
|
|
|
* contains databases with different encodings, as the locale name is stored
|
|
|
|
* in the pg_database system catalog. To work around that, when setlocale()
|
|
|
|
* returns that locale name, map it to a pure-ASCII alias for the same
|
|
|
|
* locale.
|
2011-09-01 13:02:40 +02:00
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "c.h"
|
|
|
|
|
|
|
|
#undef setlocale
|
|
|
|
|
|
|
|
struct locale_map
|
|
|
|
{
|
2014-10-24 20:10:13 +02:00
|
|
|
/*
|
|
|
|
* String in locale name to replace. Can be a single string (end is NULL),
|
2015-05-24 03:35:49 +02:00
|
|
|
* or separate start and end strings. If two strings are given, the locale
|
|
|
|
* name must contain both of them, and everything between them is
|
|
|
|
* replaced. This is used for a poor-man's regexp search, allowing
|
2014-10-24 20:10:13 +02:00
|
|
|
* replacement of "start.*end".
|
|
|
|
*/
|
|
|
|
const char *locale_name_start;
|
|
|
|
const char *locale_name_end;
|
|
|
|
|
|
|
|
const char *replacement; /* string to replace the match with */
|
2011-09-01 13:02:40 +02:00
|
|
|
};
|
|
|
|
|
2014-10-24 20:10:13 +02:00
|
|
|
/*
|
|
|
|
* Mappings applied before calling setlocale(), to the argument.
|
|
|
|
*/
|
|
|
|
static const struct locale_map locale_map_argument[] = {
|
2011-09-01 13:02:40 +02:00
|
|
|
/*
|
|
|
|
* "HKG" is listed here:
|
|
|
|
* http://msdn.microsoft.com/en-us/library/cdax410z%28v=vs.71%29.aspx
|
|
|
|
* (Country/Region Strings).
|
|
|
|
*
|
|
|
|
* "ARE" is the ISO-3166 three-letter code for U.A.E. It is not on the
|
|
|
|
* above list, but seems to work anyway.
|
|
|
|
*/
|
2014-10-24 20:10:13 +02:00
|
|
|
{"Hong Kong S.A.R.", NULL, "HKG"},
|
|
|
|
{"U.A.E.", NULL, "ARE"},
|
2011-09-01 13:02:40 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* The ISO-3166 country code for Macau S.A.R. is MAC, but Windows doesn't
|
2012-06-10 21:20:04 +02:00
|
|
|
* seem to recognize that. And Macau isn't listed in the table of accepted
|
|
|
|
* abbreviations linked above. Fortunately, "ZHM" seems to be accepted as
|
|
|
|
* an alias for "Chinese (Traditional)_Macau S.A.R..950". I'm not sure
|
|
|
|
* where "ZHM" comes from, must be some legacy naming scheme. But hey, it
|
|
|
|
* works.
|
2011-09-01 13:02:40 +02:00
|
|
|
*
|
|
|
|
* Note that unlike HKG and ARE, ZHM is an alias for the *whole* locale
|
|
|
|
* name, not just the country part.
|
|
|
|
*
|
|
|
|
* Some versions of Windows spell it "Macau", others "Macao".
|
|
|
|
*/
|
2014-10-24 20:10:13 +02:00
|
|
|
{"Chinese (Traditional)_Macau S.A.R..950", NULL, "ZHM"},
|
|
|
|
{"Chinese_Macau S.A.R..950", NULL, "ZHM"},
|
|
|
|
{"Chinese (Traditional)_Macao S.A.R..950", NULL, "ZHM"},
|
|
|
|
{"Chinese_Macao S.A.R..950", NULL, "ZHM"},
|
|
|
|
{NULL, NULL, NULL}
|
2011-09-01 13:02:40 +02:00
|
|
|
};
|
|
|
|
|
2014-10-24 20:10:13 +02:00
|
|
|
/*
|
|
|
|
* Mappings applied after calling setlocale(), to its return value.
|
|
|
|
*/
|
|
|
|
static const struct locale_map locale_map_result[] = {
|
|
|
|
/*
|
|
|
|
* "Norwegian (Bokmål)" locale name contains the a-ring character.
|
|
|
|
* Map it to a pure-ASCII alias.
|
|
|
|
*
|
|
|
|
* It's not clear what encoding setlocale() uses when it returns the
|
|
|
|
* locale name, so to play it safe, we search for "Norwegian (Bok*l)".
|
|
|
|
*/
|
Another attempt at fixing Windows Norwegian locale.
Previous fix mapped "Norwegian (Bokmål)" locale, which contains a non-ASCII
character, to the pure ASCII alias "norwegian-bokmal". However, it turns
out that more recent versions of the CRT library, in particular MSVCR110
(Visual Studio 2012), changed the behaviour of setlocale() so that if
you pass "norwegian-bokmal" to setlocale, it returns "Norwegian_Norway".
That meant trouble, when setlocale(..., NULL) first returned
"Norwegian (Bokmål)_Norway", which we mapped to "norwegian-bokmal_Norway",
but another call to setlocale(..., "norwegian-bokmal_Norway") returned
"Norwegian_Norway". That caused PostgreSQL to think that they are different
locales, and therefore not compatible. That caused initdb to fail at
CREATE DATABASE.
Older CRT versions seem to accept "Norwegian_Norway" too, so change the
mapping to return "Norwegian_Norway" instead of "norwegian-bokmal".
Backpatch to 9.2 like the previous attempt. We haven't made a release that
includes the previous fix yet, so we don't need to worry about changing the
locale of existing clusters from "norwegian-bokmal" to "Norwegian_Norway".
(Doing any mapping like this at all requires changing the locale of
existing databases; the release notes need to include instructions for
that).
2015-01-16 11:12:49 +01:00
|
|
|
{"Norwegian (Bokm", "l)_Norway", "Norwegian_Norway"},
|
2014-10-24 20:10:13 +02:00
|
|
|
{NULL, NULL, NULL}
|
|
|
|
};
|
|
|
|
|
|
|
|
#define MAX_LOCALE_NAME_LEN 100
|
|
|
|
|
2014-11-03 03:43:20 +01:00
|
|
|
static const char *
|
2017-06-21 20:39:04 +02:00
|
|
|
map_locale(const struct locale_map *map, const char *locale)
|
2011-09-01 13:02:40 +02:00
|
|
|
{
|
2014-10-24 20:10:13 +02:00
|
|
|
static char aliasbuf[MAX_LOCALE_NAME_LEN];
|
2011-09-01 13:02:40 +02:00
|
|
|
int i;
|
|
|
|
|
|
|
|
/* Check if the locale name matches any of the problematic ones. */
|
2014-10-24 20:10:13 +02:00
|
|
|
for (i = 0; map[i].locale_name_start != NULL; i++)
|
2011-09-01 13:02:40 +02:00
|
|
|
{
|
2014-10-24 20:10:13 +02:00
|
|
|
const char *needle_start = map[i].locale_name_start;
|
|
|
|
const char *needle_end = map[i].locale_name_end;
|
|
|
|
const char *replacement = map[i].replacement;
|
2011-09-01 13:02:40 +02:00
|
|
|
char *match;
|
2014-10-24 20:10:13 +02:00
|
|
|
char *match_start = NULL;
|
|
|
|
char *match_end = NULL;
|
2011-09-01 13:02:40 +02:00
|
|
|
|
2014-10-24 20:10:13 +02:00
|
|
|
match = strstr(locale, needle_start);
|
|
|
|
if (match)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Found a match for the first part. If this was a two-part
|
|
|
|
* replacement, find the second part.
|
|
|
|
*/
|
|
|
|
match_start = match;
|
|
|
|
if (needle_end)
|
|
|
|
{
|
|
|
|
match = strstr(match_start + strlen(needle_start), needle_end);
|
|
|
|
if (match)
|
|
|
|
match_end = match + strlen(needle_end);
|
|
|
|
else
|
|
|
|
match_start = NULL;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
match_end = match_start + strlen(needle_start);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (match_start)
|
2011-09-01 13:02:40 +02:00
|
|
|
{
|
|
|
|
/* Found a match. Replace the matched string. */
|
2014-10-24 20:10:13 +02:00
|
|
|
int matchpos = match_start - locale;
|
2012-06-10 21:20:04 +02:00
|
|
|
int replacementlen = strlen(replacement);
|
2014-10-24 20:10:13 +02:00
|
|
|
char *rest = match_end;
|
2012-06-10 21:20:04 +02:00
|
|
|
int restlen = strlen(rest);
|
2011-09-01 13:02:40 +02:00
|
|
|
|
2014-10-24 20:10:13 +02:00
|
|
|
/* check that the result fits in the static buffer */
|
|
|
|
if (matchpos + replacementlen + restlen + 1 > MAX_LOCALE_NAME_LEN)
|
2011-09-01 13:02:40 +02:00
|
|
|
return NULL;
|
|
|
|
|
2014-10-24 20:10:13 +02:00
|
|
|
memcpy(&aliasbuf[0], &locale[0], matchpos);
|
|
|
|
memcpy(&aliasbuf[matchpos], replacement, replacementlen);
|
|
|
|
/* includes null terminator */
|
|
|
|
memcpy(&aliasbuf[matchpos + replacementlen], rest, restlen + 1);
|
2011-09-01 13:02:40 +02:00
|
|
|
|
2014-10-24 20:10:13 +02:00
|
|
|
return aliasbuf;
|
2011-09-01 13:02:40 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-24 20:10:13 +02:00
|
|
|
/* no match, just return the original string */
|
|
|
|
return locale;
|
|
|
|
}
|
|
|
|
|
|
|
|
char *
|
|
|
|
pgwin32_setlocale(int category, const char *locale)
|
|
|
|
{
|
2014-11-03 03:43:20 +01:00
|
|
|
const char *argument;
|
2014-10-24 20:10:13 +02:00
|
|
|
char *result;
|
|
|
|
|
|
|
|
if (locale == NULL)
|
|
|
|
argument = NULL;
|
2011-09-01 13:02:40 +02:00
|
|
|
else
|
2014-10-24 20:10:13 +02:00
|
|
|
argument = map_locale(locale_map_argument, locale);
|
|
|
|
|
|
|
|
/* Call the real setlocale() function */
|
|
|
|
result = setlocale(category, argument);
|
|
|
|
|
2014-11-03 03:43:20 +01:00
|
|
|
/*
|
|
|
|
* setlocale() is specified to return a "char *" that the caller is
|
|
|
|
* forbidden to modify, so casting away the "const" is innocuous.
|
|
|
|
*/
|
2014-10-24 20:10:13 +02:00
|
|
|
if (result)
|
2014-11-03 03:43:20 +01:00
|
|
|
result = (char *) map_locale(locale_map_result, result);
|
2011-09-01 13:02:40 +02:00
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|