@@ -382,51 +382,91 @@ assign_locale_messages(const char *newval, void *extra)
382382
383383/*
384384 * Frees the malloced content of a struct lconv. (But not the struct
385- * itself.)
385+ * itself.) It's important that this not throw elog(ERROR).
386386 */
387387static void
388388free_struct_lconv (struct lconv * s )
389389{
390- if (s -> currency_symbol )
391- free (s -> currency_symbol );
392390 if (s -> decimal_point )
393391 free (s -> decimal_point );
394- if (s -> grouping )
395- free (s -> grouping );
396392 if (s -> thousands_sep )
397393 free (s -> thousands_sep );
394+ if (s -> grouping )
395+ free (s -> grouping );
398396 if (s -> int_curr_symbol )
399397 free (s -> int_curr_symbol );
398+ if (s -> currency_symbol )
399+ free (s -> currency_symbol );
400400 if (s -> mon_decimal_point )
401401 free (s -> mon_decimal_point );
402- if (s -> mon_grouping )
403- free (s -> mon_grouping );
404402 if (s -> mon_thousands_sep )
405403 free (s -> mon_thousands_sep );
406- if (s -> negative_sign )
407- free (s -> negative_sign );
404+ if (s -> mon_grouping )
405+ free (s -> mon_grouping );
408406 if (s -> positive_sign )
409407 free (s -> positive_sign );
408+ if (s -> negative_sign )
409+ free (s -> negative_sign );
410+ }
411+
412+ /*
413+ * Check that all fields of a struct lconv (or at least, the ones we care
414+ * about) are non-NULL. The field list must match free_struct_lconv().
415+ */
416+ static bool
417+ struct_lconv_is_valid (struct lconv * s )
418+ {
419+ if (s -> decimal_point == NULL )
420+ return false;
421+ if (s -> thousands_sep == NULL )
422+ return false;
423+ if (s -> grouping == NULL )
424+ return false;
425+ if (s -> int_curr_symbol == NULL )
426+ return false;
427+ if (s -> currency_symbol == NULL )
428+ return false;
429+ if (s -> mon_decimal_point == NULL )
430+ return false;
431+ if (s -> mon_thousands_sep == NULL )
432+ return false;
433+ if (s -> mon_grouping == NULL )
434+ return false;
435+ if (s -> positive_sign == NULL )
436+ return false;
437+ if (s -> negative_sign == NULL )
438+ return false;
439+ return true;
410440}
411441
412442
413443/*
414- * Return a strdup'ed string converted from the specified encoding to the
444+ * Convert the strdup'd string at *str from the specified encoding to the
415445 * database encoding.
416446 */
417- static char *
418- db_encoding_strdup (int encoding , const char * str )
447+ static void
448+ db_encoding_convert (int encoding , char * * str )
419449{
420450 char * pstr ;
421451 char * mstr ;
422452
423453 /* convert the string to the database encoding */
424- pstr = pg_any_to_server (str , strlen (str ), encoding );
454+ pstr = pg_any_to_server (* str , strlen (* str ), encoding );
455+ if (pstr == * str )
456+ return ; /* no conversion happened */
457+
458+ /* need it malloc'd not palloc'd */
425459 mstr = strdup (pstr );
426- if (pstr != str )
427- pfree (pstr );
460+ if (mstr == NULL )
461+ ereport (ERROR ,
462+ (errcode (ERRCODE_OUT_OF_MEMORY ),
463+ errmsg ("out of memory" )));
464+
465+ /* replace old string */
466+ free (* str );
467+ * str = mstr ;
428468
429- return mstr ;
469+ pfree ( pstr ) ;
430470}
431471
432472
@@ -440,13 +480,10 @@ PGLC_localeconv(void)
440480 static struct lconv CurrentLocaleConv ;
441481 static bool CurrentLocaleConvAllocated = false;
442482 struct lconv * extlconv ;
483+ struct lconv worklconv ;
484+ bool trouble = false;
443485 char * save_lc_monetary ;
444486 char * save_lc_numeric ;
445- char * decimal_point ;
446- char * grouping ;
447- char * thousands_sep ;
448- int encoding ;
449-
450487#ifdef WIN32
451488 char * save_lc_ctype ;
452489#endif
@@ -462,6 +499,20 @@ PGLC_localeconv(void)
462499 CurrentLocaleConvAllocated = false;
463500 }
464501
502+ /*
503+ * This is tricky because we really don't want to risk throwing error
504+ * while the locale is set to other than our usual settings. Therefore,
505+ * the process is: collect the usual settings, set locale to special
506+ * setting, copy relevant data into worklconv using strdup(), restore
507+ * normal settings, convert data to desired encoding, and finally stash
508+ * the collected data in CurrentLocaleConv. This makes it safe if we
509+ * throw an error during encoding conversion or run out of memory anywhere
510+ * in the process. All data pointed to by struct lconv members is
511+ * allocated with strdup, to avoid premature elog(ERROR) and to allow
512+ * using a single cleanup routine.
513+ */
514+ memset (& worklconv , 0 , sizeof (worklconv ));
515+
465516 /* Save user's values of monetary and numeric locales */
466517 save_lc_monetary = setlocale (LC_MONETARY , NULL );
467518 if (save_lc_monetary )
@@ -477,7 +528,7 @@ PGLC_localeconv(void)
477528 * Ideally, monetary and numeric local symbols could be returned in any
478529 * server encoding. Unfortunately, the WIN32 API does not allow
479530 * setlocale() to return values in a codepage/CTYPE that uses more than
480- * two bytes per character, like UTF-8:
531+ * two bytes per character, such as UTF-8:
481532 *
482533 * http://msdn.microsoft.com/en-us/library/x99tb11d.aspx
483534 *
@@ -487,7 +538,7 @@ PGLC_localeconv(void)
487538 *
488539 * Therefore, we set LC_CTYPE to match LC_NUMERIC or LC_MONETARY (which
489540 * cannot be UTF8), call localeconv(), and then convert from the
490- * numeric/monitary LC_CTYPE to the server encoding. One example use of
541+ * numeric/monetary LC_CTYPE to the server encoding. One example use of
491542 * this is for the Euro symbol.
492543 *
493544 * Perhaps someday we will use GetLocaleInfoW() which returns values in
@@ -499,18 +550,20 @@ PGLC_localeconv(void)
499550 if (save_lc_ctype )
500551 save_lc_ctype = pstrdup (save_lc_ctype );
501552
553+ /* Here begins the critical section where we must not throw error */
554+
502555 /* use numeric to set the ctype */
503556 setlocale (LC_CTYPE , locale_numeric );
504557#endif
505558
506559 /* Get formatting information for numeric */
507560 setlocale (LC_NUMERIC , locale_numeric );
508561 extlconv = localeconv ();
509- encoding = pg_get_encoding_from_locale (locale_numeric , true);
510562
511- decimal_point = db_encoding_strdup (encoding , extlconv -> decimal_point );
512- thousands_sep = db_encoding_strdup (encoding , extlconv -> thousands_sep );
513- grouping = strdup (extlconv -> grouping );
563+ /* Must copy data now in case setlocale() overwrites it */
564+ worklconv .decimal_point = strdup (extlconv -> decimal_point );
565+ worklconv .thousands_sep = strdup (extlconv -> thousands_sep );
566+ worklconv .grouping = strdup (extlconv -> grouping );
514567
515568#ifdef WIN32
516569 /* use monetary to set the ctype */
@@ -520,52 +573,111 @@ PGLC_localeconv(void)
520573 /* Get formatting information for monetary */
521574 setlocale (LC_MONETARY , locale_monetary );
522575 extlconv = localeconv ();
523- encoding = pg_get_encoding_from_locale (locale_monetary , true);
524576
525- /*
526- * Must copy all values since restoring internal settings may overwrite
527- * localeconv()'s results. Note that if we were to fail within this
528- * sequence before reaching "CurrentLocaleConvAllocated = true", we could
529- * leak some memory --- but not much, so it's not worth agonizing over.
530- */
531- CurrentLocaleConv = * extlconv ;
532- CurrentLocaleConv .decimal_point = decimal_point ;
533- CurrentLocaleConv .grouping = grouping ;
534- CurrentLocaleConv .thousands_sep = thousands_sep ;
535- CurrentLocaleConv .int_curr_symbol = db_encoding_strdup (encoding , extlconv -> int_curr_symbol );
536- CurrentLocaleConv .currency_symbol = db_encoding_strdup (encoding , extlconv -> currency_symbol );
537- CurrentLocaleConv .mon_decimal_point = db_encoding_strdup (encoding , extlconv -> mon_decimal_point );
538- CurrentLocaleConv .mon_grouping = strdup (extlconv -> mon_grouping );
539- CurrentLocaleConv .mon_thousands_sep = db_encoding_strdup (encoding , extlconv -> mon_thousands_sep );
540- CurrentLocaleConv .negative_sign = db_encoding_strdup (encoding , extlconv -> negative_sign );
541- CurrentLocaleConv .positive_sign = db_encoding_strdup (encoding , extlconv -> positive_sign );
542- CurrentLocaleConvAllocated = true;
577+ /* Must copy data now in case setlocale() overwrites it */
578+ worklconv .int_curr_symbol = strdup (extlconv -> int_curr_symbol );
579+ worklconv .currency_symbol = strdup (extlconv -> currency_symbol );
580+ worklconv .mon_decimal_point = strdup (extlconv -> mon_decimal_point );
581+ worklconv .mon_thousands_sep = strdup (extlconv -> mon_thousands_sep );
582+ worklconv .mon_grouping = strdup (extlconv -> mon_grouping );
583+ worklconv .positive_sign = strdup (extlconv -> positive_sign );
584+ worklconv .negative_sign = strdup (extlconv -> negative_sign );
585+ /* Copy scalar fields as well */
586+ worklconv .int_frac_digits = extlconv -> int_frac_digits ;
587+ worklconv .frac_digits = extlconv -> frac_digits ;
588+ worklconv .p_cs_precedes = extlconv -> p_cs_precedes ;
589+ worklconv .p_sep_by_space = extlconv -> p_sep_by_space ;
590+ worklconv .n_cs_precedes = extlconv -> n_cs_precedes ;
591+ worklconv .n_sep_by_space = extlconv -> n_sep_by_space ;
592+ worklconv .p_sign_posn = extlconv -> p_sign_posn ;
593+ worklconv .n_sign_posn = extlconv -> n_sign_posn ;
543594
544595 /* Try to restore internal settings */
545596 if (save_lc_monetary )
546597 {
547598 if (!setlocale (LC_MONETARY , save_lc_monetary ))
548- elog (WARNING , "failed to restore old locale" );
549- pfree (save_lc_monetary );
599+ trouble = true;
550600 }
551601
552602 if (save_lc_numeric )
553603 {
554604 if (!setlocale (LC_NUMERIC , save_lc_numeric ))
555- elog (WARNING , "failed to restore old locale" );
556- pfree (save_lc_numeric );
605+ trouble = true;
557606 }
558607
559608#ifdef WIN32
560609 /* Try to restore internal ctype settings */
561610 if (save_lc_ctype )
562611 {
563612 if (!setlocale (LC_CTYPE , save_lc_ctype ))
564- elog (WARNING , "failed to restore old locale" );
565- pfree (save_lc_ctype );
613+ trouble = true;
566614 }
567615#endif
568616
617+ /*
618+ * At this point we've done our best to clean up, and can call functions
619+ * that might possibly throw errors with a clean conscience. But let's
620+ * make sure we don't leak any already-strdup'd fields in worklconv.
621+ */
622+ PG_TRY ();
623+ {
624+ int encoding ;
625+
626+ /*
627+ * Report it if we failed to restore anything. Perhaps this should be
628+ * FATAL, rather than continuing with bad locale settings?
629+ */
630+ if (trouble )
631+ elog (WARNING , "failed to restore old locale" );
632+
633+ /* Release the pstrdup'd locale names */
634+ if (save_lc_monetary )
635+ pfree (save_lc_monetary );
636+ if (save_lc_numeric )
637+ pfree (save_lc_numeric );
638+ #ifdef WIN32
639+ if (save_lc_ctype )
640+ pfree (save_lc_ctype );
641+ #endif
642+
643+ /* If any of the preceding strdup calls failed, complain now. */
644+ if (!struct_lconv_is_valid (& worklconv ))
645+ ereport (ERROR ,
646+ (errcode (ERRCODE_OUT_OF_MEMORY ),
647+ errmsg ("out of memory" )));
648+
649+ /*
650+ * Now we must perform encoding conversion from whatever's associated
651+ * with the locale into the database encoding.
652+ */
653+ encoding = pg_get_encoding_from_locale (locale_numeric , true);
654+
655+ db_encoding_convert (encoding , & worklconv .decimal_point );
656+ db_encoding_convert (encoding , & worklconv .thousands_sep );
657+ /* grouping is not text and does not require conversion */
658+
659+ encoding = pg_get_encoding_from_locale (locale_monetary , true);
660+
661+ db_encoding_convert (encoding , & worklconv .int_curr_symbol );
662+ db_encoding_convert (encoding , & worklconv .currency_symbol );
663+ db_encoding_convert (encoding , & worklconv .mon_decimal_point );
664+ db_encoding_convert (encoding , & worklconv .mon_thousands_sep );
665+ /* mon_grouping is not text and does not require conversion */
666+ db_encoding_convert (encoding , & worklconv .positive_sign );
667+ db_encoding_convert (encoding , & worklconv .negative_sign );
668+ }
669+ PG_CATCH ();
670+ {
671+ free_struct_lconv (& worklconv );
672+ PG_RE_THROW ();
673+ }
674+ PG_END_TRY ();
675+
676+ /*
677+ * Everything is good, so save the results.
678+ */
679+ CurrentLocaleConv = worklconv ;
680+ CurrentLocaleConvAllocated = true;
569681 CurrentLocaleConvValid = true;
570682 return & CurrentLocaleConv ;
571683}
0 commit comments