diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c index 91d7481528..9721ce9e0a 100644 --- a/src/backend/commands/dbcommands.c +++ b/src/backend/commands/dbcommands.c @@ -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); diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c index 9f112e8c5c..0920c13cd9 100644 --- a/src/backend/utils/adt/pg_locale.c +++ b/src/backend/utils/adt/pg_locale.c @@ -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 diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c index 99cf5b43e9..7a8006e4eb 100644 --- a/src/bin/initdb/initdb.c +++ b/src/bin/initdb/initdb.c @@ -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 diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h index 448c01a697..3c38aa2729 100644 --- a/src/include/utils/pg_locale.h +++ b/src/include/utils/pg_locale.h @@ -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);