Replace empty locale name with implied value in CREATE DATABASE and initdb.
setlocale() accepts locale name "" as meaning "the locale specified by the process's environment variables". Historically we've accepted that for Postgres' locale settings, too. However, it's fairly unsafe to store an empty string in a new database's pg_database.datcollate or datctype fields, because then the interpretation could vary across postmaster restarts, possibly resulting in index corruption and other unpleasantness. Instead, we should expand "" to whatever it means at the moment of calling CREATE DATABASE, which we can do by saving the value returned by setlocale(). For consistency, make initdb set up the initial lc_xxx parameter values the same way. initdb was already doing the right thing for empty locale names, but it did not replace non-empty names with setlocale results. On a platform where setlocale chooses to canonicalize the spellings of locale names, this would result in annoying inconsistency. (It seems that popular implementations of setlocale don't do such canonicalization, which is a pity, but the POSIX spec certainly allows it to be done.) The same risk of inconsistency leads me to not venture back-patching this, although it could certainly be seen as a longstanding bug. Per report from Jeff Davis, though this is not his proposed patch.
This commit is contained in:
parent
8279eb4191
commit
c7cea267de
@ -123,6 +123,7 @@ createdb(const CreatedbStmt *stmt)
|
||||
const char *dbtemplate = NULL;
|
||||
char *dbcollate = NULL;
|
||||
char *dbctype = NULL;
|
||||
char *canonname;
|
||||
int encoding = -1;
|
||||
int dbconnlimit = -1;
|
||||
int notherbackends;
|
||||
@ -318,15 +319,17 @@ createdb(const CreatedbStmt *stmt)
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("invalid server encoding %d", encoding)));
|
||||
|
||||
/* Check that the chosen locales are valid */
|
||||
if (!check_locale(LC_COLLATE, dbcollate))
|
||||
/* Check that the chosen locales are valid, and get canonical spellings */
|
||||
if (!check_locale(LC_COLLATE, dbcollate, &canonname))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("invalid locale name %s", dbcollate)));
|
||||
if (!check_locale(LC_CTYPE, dbctype))
|
||||
dbcollate = canonname;
|
||||
if (!check_locale(LC_CTYPE, dbctype, &canonname))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("invalid locale name %s", dbctype)));
|
||||
dbctype = canonname;
|
||||
|
||||
check_encoding_locale_matches(encoding, dbcollate, dbctype);
|
||||
|
||||
|
@ -222,29 +222,43 @@ pg_perm_setlocale(int category, const char *locale)
|
||||
|
||||
/*
|
||||
* Is the locale name valid for the locale category?
|
||||
*
|
||||
* If successful, and canonname isn't NULL, a palloc'd copy of the locale's
|
||||
* canonical name is stored there. This is especially useful for figuring out
|
||||
* what locale name "" means (ie, the server environment value). (Actually,
|
||||
* it seems that on most implementations that's the only thing it's good for;
|
||||
* we could wish that setlocale gave back a canonically spelled version of
|
||||
* the locale name, but typically it doesn't.)
|
||||
*/
|
||||
bool
|
||||
check_locale(int category, const char *value)
|
||||
check_locale(int category, const char *locale, char **canonname)
|
||||
{
|
||||
char *save;
|
||||
bool ret;
|
||||
char *res;
|
||||
|
||||
if (canonname)
|
||||
*canonname = NULL; /* in case of failure */
|
||||
|
||||
save = setlocale(category, NULL);
|
||||
if (!save)
|
||||
return false; /* won't happen, we hope */
|
||||
|
||||
/* save may be pointing at a modifiable scratch variable, see above */
|
||||
/* save may be pointing at a modifiable scratch variable, see above. */
|
||||
save = pstrdup(save);
|
||||
|
||||
/* set the locale with setlocale, to see if it accepts it. */
|
||||
ret = (setlocale(category, value) != NULL);
|
||||
res = setlocale(category, locale);
|
||||
|
||||
/* save canonical name if requested. */
|
||||
if (res && canonname)
|
||||
*canonname = pstrdup(res);
|
||||
|
||||
/* restore old value. */
|
||||
if (!setlocale(category, save))
|
||||
elog(WARNING, "failed to restore old locale");
|
||||
elog(WARNING, "failed to restore old locale \"%s\"", save);
|
||||
pfree(save);
|
||||
|
||||
return ret;
|
||||
return (res != NULL);
|
||||
}
|
||||
|
||||
|
||||
@ -262,7 +276,7 @@ check_locale(int category, const char *value)
|
||||
bool
|
||||
check_locale_monetary(char **newval, void **extra, GucSource source)
|
||||
{
|
||||
return check_locale(LC_MONETARY, *newval);
|
||||
return check_locale(LC_MONETARY, *newval, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
@ -274,7 +288,7 @@ assign_locale_monetary(const char *newval, void *extra)
|
||||
bool
|
||||
check_locale_numeric(char **newval, void **extra, GucSource source)
|
||||
{
|
||||
return check_locale(LC_NUMERIC, *newval);
|
||||
return check_locale(LC_NUMERIC, *newval, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
@ -286,7 +300,7 @@ assign_locale_numeric(const char *newval, void *extra)
|
||||
bool
|
||||
check_locale_time(char **newval, void **extra, GucSource source)
|
||||
{
|
||||
return check_locale(LC_TIME, *newval);
|
||||
return check_locale(LC_TIME, *newval, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
@ -322,7 +336,7 @@ check_locale_messages(char **newval, void **extra, GucSource source)
|
||||
* On Windows, we can't even check the value, so accept blindly
|
||||
*/
|
||||
#if defined(LC_MESSAGES) && !defined(WIN32)
|
||||
return check_locale(LC_MESSAGES, *newval);
|
||||
return check_locale(LC_MESSAGES, *newval, NULL);
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
|
@ -213,7 +213,8 @@ static void trapsig(int signum);
|
||||
static void check_ok(void);
|
||||
static char *escape_quotes(const char *src);
|
||||
static int locale_date_order(const char *locale);
|
||||
static bool check_locale_name(const char *locale);
|
||||
static bool check_locale_name(int category, const char *locale,
|
||||
char **canonname);
|
||||
static bool check_locale_encoding(const char *locale, int encoding);
|
||||
static void setlocales(void);
|
||||
static void usage(const char *progname);
|
||||
@ -2240,33 +2241,52 @@ locale_date_order(const char *locale)
|
||||
}
|
||||
|
||||
/*
|
||||
* check if given string is a valid locale specifier
|
||||
* Is the locale name valid for the locale category?
|
||||
*
|
||||
* this should match the backend check_locale() function
|
||||
* If successful, and canonname isn't NULL, a malloc'd copy of the locale's
|
||||
* canonical name is stored there. This is especially useful for figuring out
|
||||
* what locale name "" means (ie, the environment value). (Actually,
|
||||
* it seems that on most implementations that's the only thing it's good for;
|
||||
* we could wish that setlocale gave back a canonically spelled version of
|
||||
* the locale name, but typically it doesn't.)
|
||||
*
|
||||
* this should match the backend's check_locale() function
|
||||
*/
|
||||
static bool
|
||||
check_locale_name(const char *locale)
|
||||
check_locale_name(int category, const char *locale, char **canonname)
|
||||
{
|
||||
bool ret;
|
||||
int category = LC_CTYPE;
|
||||
char *save;
|
||||
char *res;
|
||||
|
||||
if (canonname)
|
||||
*canonname = NULL; /* in case of failure */
|
||||
|
||||
save = setlocale(category, NULL);
|
||||
if (!save)
|
||||
return false; /* should not happen; */
|
||||
return false; /* won't happen, we hope */
|
||||
|
||||
/* save may be pointing at a modifiable scratch variable, so copy it. */
|
||||
save = xstrdup(save);
|
||||
|
||||
ret = (setlocale(category, locale) != NULL);
|
||||
/* set the locale with setlocale, to see if it accepts it. */
|
||||
res = setlocale(category, locale);
|
||||
|
||||
setlocale(category, save);
|
||||
/* save canonical name if requested. */
|
||||
if (res && canonname)
|
||||
*canonname = xstrdup(res);
|
||||
|
||||
/* restore old value. */
|
||||
if (!setlocale(category, save))
|
||||
fprintf(stderr, _("%s: failed to restore old locale \"%s\"\n"),
|
||||
progname, save);
|
||||
free(save);
|
||||
|
||||
/* should we exit here? */
|
||||
if (!ret)
|
||||
fprintf(stderr, _("%s: invalid locale name \"%s\"\n"), progname, locale);
|
||||
if (res == NULL)
|
||||
fprintf(stderr, _("%s: invalid locale name \"%s\"\n"),
|
||||
progname, locale);
|
||||
|
||||
return ret;
|
||||
return (res != NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -2308,11 +2328,13 @@ check_locale_encoding(const char *locale, int user_enc)
|
||||
/*
|
||||
* set up the locale variables
|
||||
*
|
||||
* assumes we have called setlocale(LC_ALL,"")
|
||||
* assumes we have called setlocale(LC_ALL, "") -- see set_pglocale_pgservice
|
||||
*/
|
||||
static void
|
||||
setlocales(void)
|
||||
{
|
||||
char *canonname;
|
||||
|
||||
/* set empty lc_* values to locale config if set */
|
||||
|
||||
if (strlen(locale) > 0)
|
||||
@ -2332,32 +2354,42 @@ setlocales(void)
|
||||
}
|
||||
|
||||
/*
|
||||
* override absent/invalid config settings from initdb's locale settings
|
||||
* canonicalize locale names, and override any missing/invalid values from
|
||||
* our current environment
|
||||
*/
|
||||
|
||||
if (strlen(lc_ctype) == 0 || !check_locale_name(lc_ctype))
|
||||
if (check_locale_name(LC_CTYPE, lc_ctype, &canonname))
|
||||
lc_ctype = canonname;
|
||||
else
|
||||
lc_ctype = xstrdup(setlocale(LC_CTYPE, NULL));
|
||||
if (strlen(lc_collate) == 0 || !check_locale_name(lc_collate))
|
||||
if (check_locale_name(LC_COLLATE, lc_collate, &canonname))
|
||||
lc_collate = canonname;
|
||||
else
|
||||
lc_collate = xstrdup(setlocale(LC_COLLATE, NULL));
|
||||
if (strlen(lc_numeric) == 0 || !check_locale_name(lc_numeric))
|
||||
if (check_locale_name(LC_NUMERIC, lc_numeric, &canonname))
|
||||
lc_numeric = canonname;
|
||||
else
|
||||
lc_numeric = xstrdup(setlocale(LC_NUMERIC, NULL));
|
||||
if (strlen(lc_time) == 0 || !check_locale_name(lc_time))
|
||||
if (check_locale_name(LC_TIME, lc_time, &canonname))
|
||||
lc_time = canonname;
|
||||
else
|
||||
lc_time = xstrdup(setlocale(LC_TIME, NULL));
|
||||
if (strlen(lc_monetary) == 0 || !check_locale_name(lc_monetary))
|
||||
if (check_locale_name(LC_MONETARY, lc_monetary, &canonname))
|
||||
lc_monetary = canonname;
|
||||
else
|
||||
lc_monetary = xstrdup(setlocale(LC_MONETARY, NULL));
|
||||
if (strlen(lc_messages) == 0 || !check_locale_name(lc_messages))
|
||||
#if defined(LC_MESSAGES) && !defined(WIN32)
|
||||
{
|
||||
/* when available get the current locale setting */
|
||||
if (check_locale_name(LC_MESSAGES, lc_messages, &canonname))
|
||||
lc_messages = canonname;
|
||||
else
|
||||
lc_messages = xstrdup(setlocale(LC_MESSAGES, NULL));
|
||||
}
|
||||
#else
|
||||
{
|
||||
/* when not available, get the CTYPE setting */
|
||||
/* when LC_MESSAGES is not available, use the LC_CTYPE setting */
|
||||
if (check_locale_name(LC_CTYPE, lc_messages, &canonname))
|
||||
lc_messages = canonname;
|
||||
else
|
||||
lc_messages = xstrdup(setlocale(LC_CTYPE, NULL));
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
|
@ -42,7 +42,7 @@ extern void assign_locale_numeric(const char *newval, void *extra);
|
||||
extern bool check_locale_time(char **newval, void **extra, GucSource source);
|
||||
extern void assign_locale_time(const char *newval, void *extra);
|
||||
|
||||
extern bool check_locale(int category, const char *locale);
|
||||
extern bool check_locale(int category, const char *locale, char **canonname);
|
||||
extern char *pg_perm_setlocale(int category, const char *locale);
|
||||
|
||||
extern bool lc_collate_is_c(Oid collation);
|
||||
|
Loading…
Reference in New Issue
Block a user