Index: head/lib/libc/stdlib/strfmon.c =================================================================== --- head/lib/libc/stdlib/strfmon.c +++ head/lib/libc/stdlib/strfmon.c @@ -92,7 +92,8 @@ } while (0) #define GRPSEP do { \ - *--bufend = thousands_sep; \ + bufend -= thousands_sep_size; \ + memcpy(bufend, thousands_sep, thousands_sep_size); \ groups++; \ } while (0) @@ -520,7 +521,7 @@ return (chars); } -/* convert double to ASCII */ +/* convert double to locale-encoded string */ static char * __format_grouped_double(double value, int *flags, int left_prec, int right_prec, int pad_char) { @@ -536,19 +537,24 @@ struct lconv *lc = localeconv(); char *grouping; - char decimal_point; - char thousands_sep; + const char *decimal_point; + const char *thousands_sep; + size_t decimal_point_size; + size_t thousands_sep_size; int groups = 0; grouping = lc->mon_grouping; - decimal_point = *lc->mon_decimal_point; - if (decimal_point == '\0') - decimal_point = *lc->decimal_point; - thousands_sep = *lc->mon_thousands_sep; - if (thousands_sep == '\0') - thousands_sep = *lc->thousands_sep; + decimal_point = lc->mon_decimal_point; + if (*decimal_point == '\0') + decimal_point = lc->decimal_point; + thousands_sep = lc->mon_thousands_sep; + if (*thousands_sep == '\0') + thousands_sep = lc->thousands_sep; + decimal_point_size = strlen(decimal_point); + thousands_sep_size = strlen(thousands_sep); + /* fill left_prec with default value */ if (left_prec == -1) left_prec = 0; @@ -574,7 +580,8 @@ return (NULL); /* make sure that we've enough space for result string */ - bufsize = avalue_size * 2 + 1; + bufsize = avalue_size * (1 + thousands_sep_size) + decimal_point_size + + 1; rslt = calloc(1, bufsize); if (rslt == NULL) { free(avalue); @@ -593,12 +600,13 @@ bufend -= right_prec; memcpy(bufend, avalue + avalue_size+padded-right_prec, right_prec); - *--bufend = decimal_point; + bufend -= decimal_point_size; + memcpy(bufend, decimal_point, decimal_point_size); avalue_size -= (right_prec + 1); } if ((*flags & NEED_GROUPING) && - thousands_sep != '\0' && /* XXX: need investigation */ + thousands_sep_size > 0 && /* XXX: need investigation */ *grouping != CHAR_MAX && *grouping > 0) { while (avalue_size > (int)*grouping) { @@ -626,8 +634,9 @@ } else { bufend -= avalue_size; memcpy(bufend, avalue+padded, avalue_size); + /* decrease assumed $decimal_point */ if (right_prec == 0) - padded--; /* decrease assumed $decimal_point */ + padded -= decimal_point_size; } /* do padding with pad_char */ Index: head/lib/libc/tests/stdlib/Makefile =================================================================== --- head/lib/libc/tests/stdlib/Makefile +++ head/lib/libc/tests/stdlib/Makefile @@ -6,6 +6,7 @@ ATF_TESTS_C+= mergesort_test ATF_TESTS_C+= qsort_test ATF_TESTS_C+= set_constraint_handler_s_test +ATF_TESTS_C+= strfmon_test ATF_TESTS_C+= tsearch_test .if ${COMPILER_FEATURES:Mc++11} ATF_TESTS_CXX+= cxa_thread_atexit_test Index: head/lib/libc/tests/stdlib/strfmon_test.c =================================================================== --- head/lib/libc/tests/stdlib/strfmon_test.c +++ head/lib/libc/tests/stdlib/strfmon_test.c @@ -0,0 +1,71 @@ +/*- + * Copyright (C) 2018 Conrad Meyer + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include + +#include + +ATF_TC_WITHOUT_HEAD(strfmon_locale_thousands); +ATF_TC_BODY(strfmon_locale_thousands, tc) +{ + char actual[40], expected[40]; + struct lconv *lc; + const char *ts; + double n; + + setlocale(LC_MONETARY, "sv_SE.UTF-8"); + + lc = localeconv(); + + ts = lc->mon_thousands_sep; + if (strlen(ts) == 0) + ts = lc->thousands_sep; + + if (strlen(ts) < 2) + atf_tc_skip("multi-byte thousands-separator not found"); + + n = 1234.56; + strfmon(actual, sizeof(actual), "%i", n); + + strcpy(expected, "1"); + strlcat(expected, ts, sizeof(expected)); + strlcat(expected, "234", sizeof(expected)); + + /* We're just testing the thousands separator, not all of strmon. */ + actual[strlen(expected)] = '\0'; + ATF_CHECK_STREQ(expected, actual); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, strfmon_locale_thousands); + return (atf_no_error()); +}