@@ -382,51 +382,91 @@ assign_locale_messages(const char *newval, void *extra)
382
382
383
383
/*
384
384
* 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).
386
386
*/
387
387
static void
388
388
free_struct_lconv (struct lconv * s )
389
389
{
390
- if (s -> currency_symbol )
391
- free (s -> currency_symbol );
392
390
if (s -> decimal_point )
393
391
free (s -> decimal_point );
394
- if (s -> grouping )
395
- free (s -> grouping );
396
392
if (s -> thousands_sep )
397
393
free (s -> thousands_sep );
394
+ if (s -> grouping )
395
+ free (s -> grouping );
398
396
if (s -> int_curr_symbol )
399
397
free (s -> int_curr_symbol );
398
+ if (s -> currency_symbol )
399
+ free (s -> currency_symbol );
400
400
if (s -> mon_decimal_point )
401
401
free (s -> mon_decimal_point );
402
- if (s -> mon_grouping )
403
- free (s -> mon_grouping );
404
402
if (s -> mon_thousands_sep )
405
403
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 );
408
406
if (s -> positive_sign )
409
407
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;
410
440
}
411
441
412
442
413
443
/*
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
415
445
* database encoding.
416
446
*/
417
- static char *
418
- db_encoding_strdup (int encoding , const char * str )
447
+ static void
448
+ db_encoding_convert (int encoding , char * * str )
419
449
{
420
450
char * pstr ;
421
451
char * mstr ;
422
452
423
453
/* 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 */
425
459
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 ;
428
468
429
- return mstr ;
469
+ pfree ( pstr ) ;
430
470
}
431
471
432
472
@@ -440,13 +480,10 @@ PGLC_localeconv(void)
440
480
static struct lconv CurrentLocaleConv ;
441
481
static bool CurrentLocaleConvAllocated = false;
442
482
struct lconv * extlconv ;
483
+ struct lconv worklconv ;
484
+ bool trouble = false;
443
485
char * save_lc_monetary ;
444
486
char * save_lc_numeric ;
445
- char * decimal_point ;
446
- char * grouping ;
447
- char * thousands_sep ;
448
- int encoding ;
449
-
450
487
#ifdef WIN32
451
488
char * save_lc_ctype ;
452
489
#endif
@@ -462,6 +499,20 @@ PGLC_localeconv(void)
462
499
CurrentLocaleConvAllocated = false;
463
500
}
464
501
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
+
465
516
/* Save user's values of monetary and numeric locales */
466
517
save_lc_monetary = setlocale (LC_MONETARY , NULL );
467
518
if (save_lc_monetary )
@@ -477,7 +528,7 @@ PGLC_localeconv(void)
477
528
* Ideally, monetary and numeric local symbols could be returned in any
478
529
* server encoding. Unfortunately, the WIN32 API does not allow
479
530
* 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:
481
532
*
482
533
* http://msdn.microsoft.com/en-us/library/x99tb11d.aspx
483
534
*
@@ -487,7 +538,7 @@ PGLC_localeconv(void)
487
538
*
488
539
* Therefore, we set LC_CTYPE to match LC_NUMERIC or LC_MONETARY (which
489
540
* 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
491
542
* this is for the Euro symbol.
492
543
*
493
544
* Perhaps someday we will use GetLocaleInfoW() which returns values in
@@ -499,18 +550,20 @@ PGLC_localeconv(void)
499
550
if (save_lc_ctype )
500
551
save_lc_ctype = pstrdup (save_lc_ctype );
501
552
553
+ /* Here begins the critical section where we must not throw error */
554
+
502
555
/* use numeric to set the ctype */
503
556
setlocale (LC_CTYPE , locale_numeric );
504
557
#endif
505
558
506
559
/* Get formatting information for numeric */
507
560
setlocale (LC_NUMERIC , locale_numeric );
508
561
extlconv = localeconv ();
509
- encoding = pg_get_encoding_from_locale (locale_numeric , true);
510
562
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 );
514
567
515
568
#ifdef WIN32
516
569
/* use monetary to set the ctype */
@@ -520,52 +573,111 @@ PGLC_localeconv(void)
520
573
/* Get formatting information for monetary */
521
574
setlocale (LC_MONETARY , locale_monetary );
522
575
extlconv = localeconv ();
523
- encoding = pg_get_encoding_from_locale (locale_monetary , true);
524
576
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 ;
543
594
544
595
/* Try to restore internal settings */
545
596
if (save_lc_monetary )
546
597
{
547
598
if (!setlocale (LC_MONETARY , save_lc_monetary ))
548
- elog (WARNING , "failed to restore old locale" );
549
- pfree (save_lc_monetary );
599
+ trouble = true;
550
600
}
551
601
552
602
if (save_lc_numeric )
553
603
{
554
604
if (!setlocale (LC_NUMERIC , save_lc_numeric ))
555
- elog (WARNING , "failed to restore old locale" );
556
- pfree (save_lc_numeric );
605
+ trouble = true;
557
606
}
558
607
559
608
#ifdef WIN32
560
609
/* Try to restore internal ctype settings */
561
610
if (save_lc_ctype )
562
611
{
563
612
if (!setlocale (LC_CTYPE , save_lc_ctype ))
564
- elog (WARNING , "failed to restore old locale" );
565
- pfree (save_lc_ctype );
613
+ trouble = true;
566
614
}
567
615
#endif
568
616
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;
569
681
CurrentLocaleConvValid = true;
570
682
return & CurrentLocaleConv ;
571
683
}
0 commit comments