@@ -358,53 +358,93 @@ assign_locale_messages(const char *newval, void *extra)
358
358
359
359
/*
360
360
* Frees the malloced content of a struct lconv. (But not the struct
361
- * itself.)
361
+ * itself.) It's important that this not throw elog(ERROR).
362
362
*/
363
363
static void
364
364
free_struct_lconv (struct lconv * s )
365
365
{
366
- if (s -> currency_symbol )
367
- free (s -> currency_symbol );
368
366
if (s -> decimal_point )
369
367
free (s -> decimal_point );
370
- if (s -> grouping )
371
- free (s -> grouping );
372
368
if (s -> thousands_sep )
373
369
free (s -> thousands_sep );
370
+ if (s -> grouping )
371
+ free (s -> grouping );
374
372
if (s -> int_curr_symbol )
375
373
free (s -> int_curr_symbol );
374
+ if (s -> currency_symbol )
375
+ free (s -> currency_symbol );
376
376
if (s -> mon_decimal_point )
377
377
free (s -> mon_decimal_point );
378
- if (s -> mon_grouping )
379
- free (s -> mon_grouping );
380
378
if (s -> mon_thousands_sep )
381
379
free (s -> mon_thousands_sep );
382
- if (s -> negative_sign )
383
- free (s -> negative_sign );
380
+ if (s -> mon_grouping )
381
+ free (s -> mon_grouping );
384
382
if (s -> positive_sign )
385
383
free (s -> positive_sign );
384
+ if (s -> negative_sign )
385
+ free (s -> negative_sign );
386
+ }
387
+
388
+ /*
389
+ * Check that all fields of a struct lconv (or at least, the ones we care
390
+ * about) are non-NULL. The field list must match free_struct_lconv().
391
+ */
392
+ static bool
393
+ struct_lconv_is_valid (struct lconv * s )
394
+ {
395
+ if (s -> decimal_point == NULL )
396
+ return false;
397
+ if (s -> thousands_sep == NULL )
398
+ return false;
399
+ if (s -> grouping == NULL )
400
+ return false;
401
+ if (s -> int_curr_symbol == NULL )
402
+ return false;
403
+ if (s -> currency_symbol == NULL )
404
+ return false;
405
+ if (s -> mon_decimal_point == NULL )
406
+ return false;
407
+ if (s -> mon_thousands_sep == NULL )
408
+ return false;
409
+ if (s -> mon_grouping == NULL )
410
+ return false;
411
+ if (s -> positive_sign == NULL )
412
+ return false;
413
+ if (s -> negative_sign == NULL )
414
+ return false;
415
+ return true;
386
416
}
387
417
388
418
389
419
/*
390
- * Return a strdup'ed string converted from the specified encoding to the
420
+ * Convert the strdup'd string at *str from the specified encoding to the
391
421
* database encoding.
392
422
*/
393
- static char *
394
- db_encoding_strdup (int encoding , const char * str )
423
+ static void
424
+ db_encoding_convert (int encoding , char * * str )
395
425
{
396
426
char * pstr ;
397
427
char * mstr ;
398
428
399
429
/* convert the string to the database encoding */
400
430
pstr = (char * ) pg_do_encoding_conversion (
401
- (unsigned char * ) str , strlen (str ),
431
+ (unsigned char * ) * str , strlen (* str ),
402
432
encoding , GetDatabaseEncoding ());
433
+ if (pstr == * str )
434
+ return ; /* no conversion happened */
435
+
436
+ /* need it malloc'd not palloc'd */
403
437
mstr = strdup (pstr );
404
- if (pstr != str )
405
- pfree (pstr );
438
+ if (mstr == NULL )
439
+ ereport (ERROR ,
440
+ (errcode (ERRCODE_OUT_OF_MEMORY ),
441
+ errmsg ("out of memory" )));
406
442
407
- return mstr ;
443
+ /* replace old string */
444
+ free (* str );
445
+ * str = mstr ;
446
+
447
+ pfree (pstr );
408
448
}
409
449
410
450
@@ -418,13 +458,10 @@ PGLC_localeconv(void)
418
458
static struct lconv CurrentLocaleConv ;
419
459
static bool CurrentLocaleConvAllocated = false;
420
460
struct lconv * extlconv ;
461
+ struct lconv worklconv ;
462
+ bool trouble = false;
421
463
char * save_lc_monetary ;
422
464
char * save_lc_numeric ;
423
- char * decimal_point ;
424
- char * grouping ;
425
- char * thousands_sep ;
426
- int encoding ;
427
-
428
465
#ifdef WIN32
429
466
char * save_lc_ctype ;
430
467
#endif
@@ -440,6 +477,20 @@ PGLC_localeconv(void)
440
477
CurrentLocaleConvAllocated = false;
441
478
}
442
479
480
+ /*
481
+ * This is tricky because we really don't want to risk throwing error
482
+ * while the locale is set to other than our usual settings. Therefore,
483
+ * the process is: collect the usual settings, set locale to special
484
+ * setting, copy relevant data into worklconv using strdup(), restore
485
+ * normal settings, convert data to desired encoding, and finally stash
486
+ * the collected data in CurrentLocaleConv. This makes it safe if we
487
+ * throw an error during encoding conversion or run out of memory anywhere
488
+ * in the process. All data pointed to by struct lconv members is
489
+ * allocated with strdup, to avoid premature elog(ERROR) and to allow
490
+ * using a single cleanup routine.
491
+ */
492
+ memset (& worklconv , 0 , sizeof (worklconv ));
493
+
443
494
/* Save user's values of monetary and numeric locales */
444
495
save_lc_monetary = setlocale (LC_MONETARY , NULL );
445
496
if (save_lc_monetary )
@@ -455,7 +506,7 @@ PGLC_localeconv(void)
455
506
* Ideally, monetary and numeric local symbols could be returned in any
456
507
* server encoding. Unfortunately, the WIN32 API does not allow
457
508
* setlocale() to return values in a codepage/CTYPE that uses more than
458
- * two bytes per character, like UTF-8:
509
+ * two bytes per character, such as UTF-8:
459
510
*
460
511
* http://msdn.microsoft.com/en-us/library/x99tb11d.aspx
461
512
*
@@ -465,7 +516,7 @@ PGLC_localeconv(void)
465
516
*
466
517
* Therefore, we set LC_CTYPE to match LC_NUMERIC or LC_MONETARY (which
467
518
* cannot be UTF8), call localeconv(), and then convert from the
468
- * numeric/monitary LC_CTYPE to the server encoding. One example use of
519
+ * numeric/monetary LC_CTYPE to the server encoding. One example use of
469
520
* this is for the Euro symbol.
470
521
*
471
522
* Perhaps someday we will use GetLocaleInfoW() which returns values in
@@ -477,18 +528,20 @@ PGLC_localeconv(void)
477
528
if (save_lc_ctype )
478
529
save_lc_ctype = pstrdup (save_lc_ctype );
479
530
531
+ /* Here begins the critical section where we must not throw error */
532
+
480
533
/* use numeric to set the ctype */
481
534
setlocale (LC_CTYPE , locale_numeric );
482
535
#endif
483
536
484
537
/* Get formatting information for numeric */
485
538
setlocale (LC_NUMERIC , locale_numeric );
486
539
extlconv = localeconv ();
487
- encoding = pg_get_encoding_from_locale (locale_numeric , true);
488
540
489
- decimal_point = db_encoding_strdup (encoding , extlconv -> decimal_point );
490
- thousands_sep = db_encoding_strdup (encoding , extlconv -> thousands_sep );
491
- grouping = strdup (extlconv -> grouping );
541
+ /* Must copy data now in case setlocale() overwrites it */
542
+ worklconv .decimal_point = strdup (extlconv -> decimal_point );
543
+ worklconv .thousands_sep = strdup (extlconv -> thousands_sep );
544
+ worklconv .grouping = strdup (extlconv -> grouping );
492
545
493
546
#ifdef WIN32
494
547
/* use monetary to set the ctype */
@@ -498,52 +551,111 @@ PGLC_localeconv(void)
498
551
/* Get formatting information for monetary */
499
552
setlocale (LC_MONETARY , locale_monetary );
500
553
extlconv = localeconv ();
501
- encoding = pg_get_encoding_from_locale (locale_monetary , true);
502
554
503
- /*
504
- * Must copy all values since restoring internal settings may overwrite
505
- * localeconv()'s results. Note that if we were to fail within this
506
- * sequence before reaching "CurrentLocaleConvAllocated = true", we could
507
- * leak some memory --- but not much, so it's not worth agonizing over.
508
- */
509
- CurrentLocaleConv = * extlconv ;
510
- CurrentLocaleConv .decimal_point = decimal_point ;
511
- CurrentLocaleConv .grouping = grouping ;
512
- CurrentLocaleConv .thousands_sep = thousands_sep ;
513
- CurrentLocaleConv .int_curr_symbol = db_encoding_strdup (encoding , extlconv -> int_curr_symbol );
514
- CurrentLocaleConv .currency_symbol = db_encoding_strdup (encoding , extlconv -> currency_symbol );
515
- CurrentLocaleConv .mon_decimal_point = db_encoding_strdup (encoding , extlconv -> mon_decimal_point );
516
- CurrentLocaleConv .mon_grouping = strdup (extlconv -> mon_grouping );
517
- CurrentLocaleConv .mon_thousands_sep = db_encoding_strdup (encoding , extlconv -> mon_thousands_sep );
518
- CurrentLocaleConv .negative_sign = db_encoding_strdup (encoding , extlconv -> negative_sign );
519
- CurrentLocaleConv .positive_sign = db_encoding_strdup (encoding , extlconv -> positive_sign );
520
- CurrentLocaleConvAllocated = true;
555
+ /* Must copy data now in case setlocale() overwrites it */
556
+ worklconv .int_curr_symbol = strdup (extlconv -> int_curr_symbol );
557
+ worklconv .currency_symbol = strdup (extlconv -> currency_symbol );
558
+ worklconv .mon_decimal_point = strdup (extlconv -> mon_decimal_point );
559
+ worklconv .mon_thousands_sep = strdup (extlconv -> mon_thousands_sep );
560
+ worklconv .mon_grouping = strdup (extlconv -> mon_grouping );
561
+ worklconv .positive_sign = strdup (extlconv -> positive_sign );
562
+ worklconv .negative_sign = strdup (extlconv -> negative_sign );
563
+ /* Copy scalar fields as well */
564
+ worklconv .int_frac_digits = extlconv -> int_frac_digits ;
565
+ worklconv .frac_digits = extlconv -> frac_digits ;
566
+ worklconv .p_cs_precedes = extlconv -> p_cs_precedes ;
567
+ worklconv .p_sep_by_space = extlconv -> p_sep_by_space ;
568
+ worklconv .n_cs_precedes = extlconv -> n_cs_precedes ;
569
+ worklconv .n_sep_by_space = extlconv -> n_sep_by_space ;
570
+ worklconv .p_sign_posn = extlconv -> p_sign_posn ;
571
+ worklconv .n_sign_posn = extlconv -> n_sign_posn ;
521
572
522
573
/* Try to restore internal settings */
523
574
if (save_lc_monetary )
524
575
{
525
576
if (!setlocale (LC_MONETARY , save_lc_monetary ))
526
- elog (WARNING , "failed to restore old locale" );
527
- pfree (save_lc_monetary );
577
+ trouble = true;
528
578
}
529
579
530
580
if (save_lc_numeric )
531
581
{
532
582
if (!setlocale (LC_NUMERIC , save_lc_numeric ))
533
- elog (WARNING , "failed to restore old locale" );
534
- pfree (save_lc_numeric );
583
+ trouble = true;
535
584
}
536
585
537
586
#ifdef WIN32
538
587
/* Try to restore internal ctype settings */
539
588
if (save_lc_ctype )
540
589
{
541
590
if (!setlocale (LC_CTYPE , save_lc_ctype ))
542
- elog (WARNING , "failed to restore old locale" );
543
- pfree (save_lc_ctype );
591
+ trouble = true;
544
592
}
545
593
#endif
546
594
595
+ /*
596
+ * At this point we've done our best to clean up, and can call functions
597
+ * that might possibly throw errors with a clean conscience. But let's
598
+ * make sure we don't leak any already-strdup'd fields in worklconv.
599
+ */
600
+ PG_TRY ();
601
+ {
602
+ int encoding ;
603
+
604
+ /*
605
+ * Report it if we failed to restore anything. Perhaps this should be
606
+ * FATAL, rather than continuing with bad locale settings?
607
+ */
608
+ if (trouble )
609
+ elog (WARNING , "failed to restore old locale" );
610
+
611
+ /* Release the pstrdup'd locale names */
612
+ if (save_lc_monetary )
613
+ pfree (save_lc_monetary );
614
+ if (save_lc_numeric )
615
+ pfree (save_lc_numeric );
616
+ #ifdef WIN32
617
+ if (save_lc_ctype )
618
+ pfree (save_lc_ctype );
619
+ #endif
620
+
621
+ /* If any of the preceding strdup calls failed, complain now. */
622
+ if (!struct_lconv_is_valid (& worklconv ))
623
+ ereport (ERROR ,
624
+ (errcode (ERRCODE_OUT_OF_MEMORY ),
625
+ errmsg ("out of memory" )));
626
+
627
+ /*
628
+ * Now we must perform encoding conversion from whatever's associated
629
+ * with the locale into the database encoding.
630
+ */
631
+ encoding = pg_get_encoding_from_locale (locale_numeric , true);
632
+
633
+ db_encoding_convert (encoding , & worklconv .decimal_point );
634
+ db_encoding_convert (encoding , & worklconv .thousands_sep );
635
+ /* grouping is not text and does not require conversion */
636
+
637
+ encoding = pg_get_encoding_from_locale (locale_monetary , true);
638
+
639
+ db_encoding_convert (encoding , & worklconv .int_curr_symbol );
640
+ db_encoding_convert (encoding , & worklconv .currency_symbol );
641
+ db_encoding_convert (encoding , & worklconv .mon_decimal_point );
642
+ db_encoding_convert (encoding , & worklconv .mon_thousands_sep );
643
+ /* mon_grouping is not text and does not require conversion */
644
+ db_encoding_convert (encoding , & worklconv .positive_sign );
645
+ db_encoding_convert (encoding , & worklconv .negative_sign );
646
+ }
647
+ PG_CATCH ();
648
+ {
649
+ free_struct_lconv (& worklconv );
650
+ PG_RE_THROW ();
651
+ }
652
+ PG_END_TRY ();
653
+
654
+ /*
655
+ * Everything is good, so save the results.
656
+ */
657
+ CurrentLocaleConv = worklconv ;
658
+ CurrentLocaleConvAllocated = true;
547
659
CurrentLocaleConvValid = true;
548
660
return & CurrentLocaleConv ;
549
661
}
0 commit comments