Re: [RFC] newlib/libc/include/langinfo.h: nl_langinfo enum off by one error causing pointer overwrite

Brian Inglis <[email protected]>
Newsgroups gmane.comp.lib.newlib
Organization Systematic Software
Message-ID <[email protected]>
On 2024-08-22 02:07, Corinna Vinschen wrote:
> Hi Brian,
> 
> On Aug 21 17:58, Brian Inglis wrote:
>> if __HAVE_LOCALE_INFO__ is defined, then _NL_MESSAGES_CODESET is defined
>> instead of _NL_CTYPE_CODESET
> 
> There is no _NL_CTYPE_CODESET, only CODESET, and the value of
> _NL_MESSAGES_CODESET is what it is for backward compatibility.
> 
> The values must not change.

What about the pointer positions in the array?

>> demonstration of pointer overwrite by nl_langinfo dump program generated
>> with additional langinfo.h hack after fix:
> 
> Can you please provide your STC?

Attached, with log, assuming current langinfo.h.
Also attached is my original program, for use with appropriately fixed and/or 
hacked up langinfo.h, with my nl_items label array adjusted to match.

>> if __HAVE_LOCALE_INFO__ is not defined, then _NL_MESSAGES_CODESET is
>> also not defined, so it is unclear if _NL_MESSAGES_CODESET should be
>> defined to _NL_CTYPE_CODESET if neither __HAVE_LOCALE_INFO__ nor
>> __HAVE_LOCALE_INFO_EXTENDED__ are defined, or added as another field
>> depending on those definitions
> 
> As I wrote above, the values must not change.  And given _NL_CTYPE_CODESET
> doesn't really exist (but CODESET does), I'm not sure I understand the
> problem here.  As on Linux we have a value _NL_CTYPE_CODESET_NAME which
> is equivalent to CODESET.

An instance of a CODESET item and pointer exists, before the wide messages 
fields, and not accounted for in the enum defined in langinfo.h, used to 
retrieve the values by nl_langinfo(3).

What is shown in the enum as the _NL_MESSAGE_CODESET item appears to be 
generated from the LC_CTYPE locale entry, and added to the buffer and pointer 
array after the _NL_CTYPE_MB_CUR_MAX pointer, before the _NL_CTYPE_OUTDIGIT?_MB 
item pointers.

What is not shown in the enum is the CODESET item generated from the LC_MESSAGES 
locale entry, and added to the buffer and pointer array after the 
_NL_MONETARY_WNEGATIVE_SIGN pointer before the _NL_MESSAGES_WYESEXPR pointer.

As a result, the underlying pointers in the pointer array are off by one in the 
wide messages fields, and the _NL_COLLATE_CODESET pointer clobbers what was 
stored in the _NL_MESSAGES_WNOSTR position in the pointer array.

This is shown below from the STC log attached: you can see in the dump that the 
underlying data from the current WNOSTR item, has L"yes" followed by L"no" L"", 
and the following COLLATE_CODESET item looks normal:

MESSAGES_WYESEXPR                呕ⵆ8孞ㄫ她佯]孞〭乮]敹s潮帀嬀⬀㄀礀夀漀伀崀
5455 '呕' 2d46 'ⵆ' 0038 '8' 5b5e '孞' 312b 'ㄫ' 5979 '她' 4f6f '佯' 005d ']'
55 'U' 54 'T' 46 'F' 2d '-' 38 '8' 00 '' 5e '^' 5b '[' 2b '+' 31 '1' 79 'y' 59 
'Y' 6f 'o' 4f 'O' 5d ']' 00 ''
MESSAGES_WNOEXPR                 ^[+1yYoO]
005e '^' 005b '[' 002b '+' 0031 '1' 0079 'y' 0059 'Y' 006f 'o' 004f 'O'
5e '^' 00 '' 5b '[' 00 '' 2b '+' 00 '' 31 '1' 00 '' 79 'y' 00 '' 59 'Y' 00 '' 6f 
'o' 00 '' 4f 'O' 00 ''
MESSAGES_WYESSTR                 ^[-0nN]
005e '^' 005b '[' 002d '-' 0030 '0' 006e 'n' 004e 'N' 005d ']' 0000 ''
5e '^' 00 '' 5b '[' 00 '' 2d '-' 00 '' 30 '0' 00 '' 6e 'n' 00 '' 4e 'N' 00 '' 5d 
']' 00 '' 00 '' 00 ''
MESSAGES_WNOSTR                  yes
0079 'y' 0065 'e' 0073 's' 0000 '' 006e 'n' 006f 'o' 0000 '' 0000 ''
79 'y' 00 '' 65 'e' 00 '' 73 's' 00 '' 00 '' 00 '' 6e 'n' 00 '' 6f 'o' 00 '' 00 
'' 00 '' 00 '' 00 ''
COLLATE_CODESET                  UTF-8
5455 '呕' 2d46 'ⵆ' 0038 '8' 0000 '' 0000 '' 0000 '' 0000 '' 0000 ''
55 'U' 54 'T' 46 'F' 2d '-' 38 '8' 00 '' 00 '' 00 '' 00 '' 00 '' 00 '' 00 '' 00 
'' 00 '' 00 '' 00 ''

So the generated locale data contains a (message) codeset preceding the wide 
message fields, and this is missing from the enum, so that the following item 
pointers are off by one, and the collate codeset pointer and item look correct, 
as the pointer clobbered that for WNOSTR.

Additional langinfo.h hack after fix to show existing problem:

     $ diff -4 /usr{/src/newlib-cygwin/newlib/libc,}/include/langinfo.h
     --- /usr/src/newlib-cygwin/newlib/libc/include/langinfo.h       2024-08-21 
17:11:00.852069900 -0600
     +++ /usr/include/langinfo.h     2024-08-21 16:59:59.609998900 -0600
     @@ -301,9 +301,9 @@ enum
        _NL_MESSAGES_CODESET,
        _NL_MESSAGES_WYESEXPR,
        _NL_MESSAGES_WNOEXPR,
        _NL_MESSAGES_WYESSTR,
     -  _NL_MESSAGES_WNOSTR,
     +#define _NL_MESSAGES_WNOSTR _NL_MESSAGES_WYESSTR

        _NL_COLLATE_CODESET,

Demonstration of pointer overwrite by nl_langinfo dump program run with patch 
and above hack:

MESSAGES_CODESET                 UTF-8
MESSAGES_WYESEXPR                ^[+1yY]
MESSAGES_WNOEXPR                 ^[-0nN]
MESSAGES_WYESSTR                 yes
79 y 65 e 73 s 0 ^@ 6e n 6f o 0 ^@ 0 ^@	    # %hx %C dump
COLLATE_CODESET                  UTF-8

If __HAVE_LOCALE_INFO__ is not defined, then _NL_MESSAGES_CODESET is
also not defined, so it is unclear if _NL_MESSAGES_CODESET should be
defined to _NL_CTYPE_CODESET if neither __HAVE_LOCALE_INFO__ nor
__HAVE_LOCALE_INFO_EXTENDED__ are defined, or added as another field
depending on those definitions?

-- 
Take care. Thanks, Brian Inglis              Calgary, Alberta, Canada

La perfection est atteinte                   Perfection is achieved
non pas lorsqu'il n'y a plus rien à ajouter  not when there is no more to add
mais lorsqu'il n'y a plus rien à retirer     but when there is no more to cut
                                 -- Antoine de Saint-Exupéry
langinfo-enum-missing-codeset-pointer-clobber-stc.c (text/plain, 5.2 KB)
/*!/usr/bin/gcc -pipe -Wp,-D_FORTIFY_SOURCE=3 -fanalyzer -fsanitize-recover=all
 *		-fstack-check -fstack-protector-all --param=ssp-buffer-size=4
 *		-ggdb -Og -Wall -Wextra -Wformat=2 -Wformat-overflow=2
 *		-Werror=format-security
 * langinfo-enum-missing-codeset-pointer-clobber-stc.c - show missing langinfo enum entry
 */



#define _GNU_SOURCE

#include <locale.h>
#include <langinfo.h>
#include <stdio.h>

static const char *nl_items[] = 
{
    "CODESET",
    "D_T_FMT",			/* string for formatting date and time */
    "D_FMT",			/* date format string */
    "T_FMT",			/* time format string */
    "T_FMT_AMPM",		/* a.m. or p.m. time formatting string */
    "AM_STR",			/* Ante Meridian affix */
    "PM_STR",			/* Post Meridian affix */
/* week day names */
    "DAY_1",
    "DAY_2",
    "DAY_3",
    "DAY_4",
    "DAY_5",
    "DAY_6",
    "DAY_7",
/* abbreviated week day names */
    "ABDAY_1",
    "ABDAY_2",
    "ABDAY_3",
    "ABDAY_4",
    "ABDAY_5",
    "ABDAY_6",
    "ABDAY_7",
/* month names */
    "MON_1",
    "MON_2",
    "MON_3",
    "MON_4",
    "MON_5",
    "MON_6",
    "MON_7",
    "MON_8",
    "MON_9",
    "MON_10",
    "MON_11",
    "MON_12",
/* abbreviated month names */
    "ABMON_1",
    "ABMON_2",
    "ABMON_3",
    "ABMON_4",
    "ABMON_5",
    "ABMON_6",
    "ABMON_7",
    "ABMON_8",
    "ABMON_9",
    "ABMON_10",
    "ABMON_11",
    "ABMON_12",
    "ERA",			/* era description segments */
    "ERA_D_FMT",		/* era date format string */
    "ERA_D_T_FMT",		/* era date and time format string */
    "ERA_T_FMT",		/* era time format string */
    "ALT_DIGITS",		/* alternative symbols for digits */
    "RADIXCHAR",		/* radix char */
    "THOUSEP",			/* separator for thousands */
    "YESEXPR",			/* affirmative response expression */
    "NOEXPR",			/* negative response expression */
    "YESSTR",			/* affirmative response for yes/no queries */
    "NOSTR",			/* negative response for yes/no queries */
    "CRNCYSTR",			/* currency symbol */
    "D_MD_ORDER",		/* month/day order (BSD extension) */
    [84] = "DATE_FMT",		/* date fmt used by date(1) (GNU extension) */
    "MB_CUR_MAX",		/* char value */
    "MESSAGES_CODESET",
/*  "CTYPE_CODESET", */
    "LOCALE_EXTENDED_FIRST_ENTRY",
    "OUTDIGIT0_MB",
    "OUTDIGIT1_MB",
    "OUTDIGIT2_MB",
    "OUTDIGIT3_MB",
    "OUTDIGIT4_MB",
    "OUTDIGIT5_MB",
    "OUTDIGIT6_MB",
    "OUTDIGIT7_MB",
    "OUTDIGIT8_MB",
    "OUTDIGIT9_MB",
    "OUTDIGIT0_WC",
    "OUTDIGIT1_WC",
    "OUTDIGIT2_WC",
    "OUTDIGIT3_WC",
    "OUTDIGIT4_WC",
    "OUTDIGIT5_WC",
    "OUTDIGIT6_WC",
    "OUTDIGIT7_WC",
    "OUTDIGIT8_WC",
    "OUTDIGIT9_WC",
    "TIME_CODESET",
    "WMON_1",
    "WMON_2",
    "WMON_3",
    "WMON_4",
    "WMON_5",
    "WMON_6",
    "WMON_7",
    "WMON_8",
    "WMON_9",
    "WMON_10",
    "WMON_11",
    "WMON_12",
    "WMONTH_1",
    "WMONTH_2",
    "WMONTH_3",
    "WMONTH_4",
    "WMONTH_5",
    "WMONTH_6",
    "WMONTH_7",
    "WMONTH_8",
    "WMONTH_9",
    "WMONTH_10",
    "WMONTH_11",
    "WMONTH_12",
    "WWDAY_1",
    "WWDAY_2",
    "WWDAY_3",
    "WWDAY_4",
    "WWDAY_5",
    "WWDAY_6",
    "WWDAY_7",
    "WWEEKDAY_1",
    "WWEEKDAY_2",
    "WWEEKDAY_3",
    "WWEEKDAY_4",
    "WWEEKDAY_5",
    "WWEEKDAY_6",
    "WWEEKDAY_7",
    "WT_FMT",
    "WD_FMT",
    "WD_T_FMT",
    "WAM_STR",
    "WPM_STR",
    "WDATE_FMT",
    "WT_FMT_AMPM",
    "WERA",
    "WERA_D_FMT",
    "WERA_D_T_FMT",
    "WERA_T_FMT",
    "WALT_DIGITS",
    "NUMERIC_CODESET",
    "NUMERIC_GROUPING",
    "NUMERIC_DECIMAL_POINT_WC",
    "NUMERIC_THOUSANDS_SEP_WC",
    "MONETARY_INT_CURR_SYMBOL",
    "MONETARY_CURRENCY_SYMBOL",
    "MONETARY_MON_DECIMAL_POINT",
    "MONETARY_MON_THOUSANDS_SEP",
    "MONETARY_MON_GROUPING",
    "MONETARY_POSITIVE_SIGN",
    "MONETARY_NEGATIVE_SIGN",
    "MONETARY_INT_FRAC_DIGITS",
    "MONETARY_FRAC_DIGITS",
    "MONETARY_P_CS_PRECEDES",
    "MONETARY_P_SEP_BY_SPACE",
    "MONETARY_N_CS_PRECEDES",
    "MONETARY_N_SEP_BY_SPACE",
    "MONETARY_P_SIGN_POSN",
    "MONETARY_N_SIGN_POSN",
    "MONETARY_INT_P_CS_PRECEDES",
    "MONETARY_INT_P_SEP_BY_SPACE",
    "MONETARY_INT_N_CS_PRECEDES",
    "MONETARY_INT_N_SEP_BY_SPACE",
    "MONETARY_INT_P_SIGN_POSN",
    "MONETARY_INT_N_SIGN_POSN",
    "MONETARY_CODESET",
    "MONETARY_WINT_CURR_SYMBOL",
    "MONETARY_WCURRENCY_SYMBOL",
    "MONETARY_WMON_DECIMAL_POINT",
    "MONETARY_WMON_THOUSANDS_SEP",
    "MONETARY_WPOSITIVE_SIGN",
    "MONETARY_WNEGATIVE_SIGN",
/*  "MESSAGES_CODESET", */
    "MESSAGES_WYESEXPR",
    "MESSAGES_WNOEXPR",
    "MESSAGES_WYESSTR",
    "MESSAGES_WNOSTR",
    "COLLATE_CODESET",
    "LOCALE_EXTENDED_LAST_ENTRY",
};


int
main (void)
{
   char		*locale	= setlocale (LC_ALL, "");
   struct lconv *lconv	= localeconv ();

    for (nl_item ni = _NL_MESSAGES_WYESEXPR; ni <= _NL_COLLATE_CODESET; ++ni)
    {
	char	*item = nl_langinfo (ni);

	if (item && *item)
	{
	    if (_NL_MESSAGES_WYESEXPR <= ni && ni <= _NL_MESSAGES_WNOSTR)
	    {
		printf ("%-32s %S\n", nl_items[ni], (wchar_t *)item);
	    } else {
		printf ("%-32s %s\n", nl_items[ni], item);
	    }

	    for (wchar_t *g = (wchar_t *)item; g < (wchar_t *)item + 8; ++g)
	    {
		printf ("%04hx '%C' ", *g, *g);
	    }

	    printf ("\n");

	    for (char *g = item; g < item + 16; ++g)
	    {
		printf ("%02hhx '%c' ", *g, *g);
	    }

	    printf ("\n");
	}
    }
}
langinfo-enum-missing-codeset-pointer-clobber-stc.log (application/octet-stream, 1.2 KB) - not displayed
locale_langinfo.c (text/plain, 11.5 KB)
/*!/usr/bin/gcc -pipe -Wp,-D_FORTIFY_SOURCE=3 -fanalyzer -fsanitize-recover=all
 *		-fstack-check -fstack-protector-all --param=ssp-buffer-size=4
 *		-ggdb -Og -Wall -Wextra -Wformat=2 -Wformat-overflow=2
 *		-Werror=format-security
 * locale_langinfo.c -  show values for formatting numeric and monetary
 *			quantities, dates/times, and messages
 */



#define _GNU_SOURCE

#include <locale.h>
#include <langinfo.h>
#include <stdio.h>



static const char *nl_items[] = 
{
    "CODESET",
    "D_T_FMT",			/* string for formatting date and time */
    "D_FMT",			/* date format string */
    "T_FMT",			/* time format string */
    "T_FMT_AMPM",		/* a.m. or p.m. time formatting string */
    "AM_STR",			/* Ante Meridian affix */
    "PM_STR",			/* Post Meridian affix */
/* week day names */
    "DAY_1",
    "DAY_2",
    "DAY_3",
    "DAY_4",
    "DAY_5",
    "DAY_6",
    "DAY_7",
/* abbreviated week day names */
    "ABDAY_1",
    "ABDAY_2",
    "ABDAY_3",
    "ABDAY_4",
    "ABDAY_5",
    "ABDAY_6",
    "ABDAY_7",
/* month names */
    "MON_1",
    "MON_2",
    "MON_3",
    "MON_4",
    "MON_5",
    "MON_6",
    "MON_7",
    "MON_8",
    "MON_9",
    "MON_10",
    "MON_11",
    "MON_12",
/* abbreviated month names */
    "ABMON_1",
    "ABMON_2",
    "ABMON_3",
    "ABMON_4",
    "ABMON_5",
    "ABMON_6",
    "ABMON_7",
    "ABMON_8",
    "ABMON_9",
    "ABMON_10",
    "ABMON_11",
    "ABMON_12",
    "ERA",			/* era description segments */
    "ERA_D_FMT",		/* era date format string */
    "ERA_D_T_FMT",		/* era date and time format string */
    "ERA_T_FMT",		/* era time format string */
    "ALT_DIGITS",		/* alternative symbols for digits */
    "RADIXCHAR",		/* radix char */
    "THOUSEP",			/* separator for thousands */
    "YESEXPR",			/* affirmative response expression */
    "NOEXPR",			/* negative response expression */
    "YESSTR",			/* affirmative response for yes/no queries */
    "NOSTR",			/* negative response for yes/no queries */
    "CRNCYSTR",			/* currency symbol */
    "D_MD_ORDER",		/* month/day order (BSD extension) */
    [84] = "DATE_FMT",		/* date fmt used by date(1) (GNU extension) */
    "MB_CUR_MAX",		/* char value */
    "CTYPE_CODESET",
    "LOCALE_EXTENDED_FIRST_ENTRY",
    "OUTDIGIT0_MB",
    "OUTDIGIT1_MB",
    "OUTDIGIT2_MB",
    "OUTDIGIT3_MB",
    "OUTDIGIT4_MB",
    "OUTDIGIT5_MB",
    "OUTDIGIT6_MB",
    "OUTDIGIT7_MB",
    "OUTDIGIT8_MB",
    "OUTDIGIT9_MB",
    "OUTDIGIT0_WC",
    "OUTDIGIT1_WC",
    "OUTDIGIT2_WC",
    "OUTDIGIT3_WC",
    "OUTDIGIT4_WC",
    "OUTDIGIT5_WC",
    "OUTDIGIT6_WC",
    "OUTDIGIT7_WC",
    "OUTDIGIT8_WC",
    "OUTDIGIT9_WC",
    "TIME_CODESET",
    "WMON_1",
    "WMON_2",
    "WMON_3",
    "WMON_4",
    "WMON_5",
    "WMON_6",
    "WMON_7",
    "WMON_8",
    "WMON_9",
    "WMON_10",
    "WMON_11",
    "WMON_12",
    "WMONTH_1",
    "WMONTH_2",
    "WMONTH_3",
    "WMONTH_4",
    "WMONTH_5",
    "WMONTH_6",
    "WMONTH_7",
    "WMONTH_8",
    "WMONTH_9",
    "WMONTH_10",
    "WMONTH_11",
    "WMONTH_12",
    "WWDAY_1",
    "WWDAY_2",
    "WWDAY_3",
    "WWDAY_4",
    "WWDAY_5",
    "WWDAY_6",
    "WWDAY_7",
    "WWEEKDAY_1",
    "WWEEKDAY_2",
    "WWEEKDAY_3",
    "WWEEKDAY_4",
    "WWEEKDAY_5",
    "WWEEKDAY_6",
    "WWEEKDAY_7",
    "WT_FMT",
    "WD_FMT",
    "WD_T_FMT",
    "WAM_STR",
    "WPM_STR",
    "WDATE_FMT",
    "WT_FMT_AMPM",
    "WERA",
    "WERA_D_FMT",
    "WERA_D_T_FMT",
    "WERA_T_FMT",
    "WALT_DIGITS",
    "NUMERIC_CODESET",
    "NUMERIC_GROUPING",
    "NUMERIC_DECIMAL_POINT_WC",
    "NUMERIC_THOUSANDS_SEP_WC",
    "MONETARY_INT_CURR_SYMBOL",
    "MONETARY_CURRENCY_SYMBOL",
    "MONETARY_MON_DECIMAL_POINT",
    "MONETARY_MON_THOUSANDS_SEP",
    "MONETARY_MON_GROUPING",
    "MONETARY_POSITIVE_SIGN",
    "MONETARY_NEGATIVE_SIGN",
    "MONETARY_INT_FRAC_DIGITS",
    "MONETARY_FRAC_DIGITS",
    "MONETARY_P_CS_PRECEDES",
    "MONETARY_P_SEP_BY_SPACE",
    "MONETARY_N_CS_PRECEDES",
    "MONETARY_N_SEP_BY_SPACE",
    "MONETARY_P_SIGN_POSN",
    "MONETARY_N_SIGN_POSN",
    "MONETARY_INT_P_CS_PRECEDES",
    "MONETARY_INT_P_SEP_BY_SPACE",
    "MONETARY_INT_N_CS_PRECEDES",
    "MONETARY_INT_N_SEP_BY_SPACE",
    "MONETARY_INT_P_SIGN_POSN",
    "MONETARY_INT_N_SIGN_POSN",
    "MONETARY_CODESET",
    "MONETARY_WINT_CURR_SYMBOL",
    "MONETARY_WCURRENCY_SYMBOL",
    "MONETARY_WMON_DECIMAL_POINT",
    "MONETARY_WMON_THOUSANDS_SEP",
    "MONETARY_WPOSITIVE_SIGN",
    "MONETARY_WNEGATIVE_SIGN",
    "MESSAGES_CODESET",
    "MESSAGES_WYESEXPR",
    "MESSAGES_WNOEXPR",
    "MESSAGES_WYESSTR",
    "COLLATE_CODESET",
    "MESSAGES_WNOSTR",
    "LOCALE_EXTENDED_LAST_ENTRY",
};


static const char *locale_categories[] = 
{
    "LC_ALL",
    "LC_COLLATE",
    "LC_CTYPE",
    "LC_MONETARY",
    "LC_NUMERIC",
    "LC_TIME",
    "LC_MESSAGES",
};



int
main (void)
{
/*
 * LC_ALL_MASK
 * LC_COLLATE_MASK
 * LC_CTYPE_MASK
 * LC_MONETARY_MASK
 * LC_NUMERIC_MASK
 * LC_TIME_MASK
 * LC_MESSAGES_MASK
 *
 * LC_GLOBAL_LOCALE
 */
    char	 *	locale	= setlocale (LC_ALL, "");
    struct lconv *	lconv	= localeconv ();

/*
 *     The elements of grouping and mon_grouping are interpreted according to the following:
 *     {CHAR_MAX}  No further grouping is to be performed.
 *     0           The previous element is to be repeatedly used for the remainder of the digits.
 *     other       The integer value is the number of digits that comprise the current group. The next element is exam‐
 *                 ined to determine the size of the next group of digits before the current group.
 *     The values of p_sep_by_space, n_sep_by_space, int_p_sep_by_space, and int_n_sep_by_space are interpreted accord‐
 *     ing to the following:
 *     0     No space separates the currency symbol and value.
 *     1     If the currency symbol and sign string are adjacent, a space separates them from the value;  otherwise,  a
 *           space separates the currency symbol from the value.
 *     2     If  the currency symbol and sign string are adjacent, a space separates them; otherwise, a space separates
 *           the sign string from the value.
 *     For int_p_sep_by_space and int_n_sep_by_space, the fourth character of int_curr_symbol  is  used  instead  of  a
 *     space.
 *     The  values  of  p_sign_posn, n_sign_posn, int_p_sign_posn, and int_n_sign_posn are interpreted according to the
 *     following:
 *     0     Parentheses surround the quantity and currency_symbol or int_curr_symbol.
 *     1     The sign string precedes the quantity and currency_symbol or int_curr_symbol.
 *     2     The sign string succeeds the quantity and currency_symbol or int_curr_symbol.
 *     3     The sign string immediately precedes the currency_symbol or int_curr_symbol.
 *     4     The sign string immediately succeeds the currency_symbol or int_curr_symbol.
 */

    printf ("locale '%s'  conv numeric "
	    " >0 '%s' <0 '%s'"
	    " group ",
	    locale,
	    lconv->positive_sign,	lconv->negative_sign);

    for (char *	g = lconv->grouping; /*BREAK*/; ++g)
    {
	printf ("%hhx ", *g);
	if (!*g || ~1 == *g)	break;
    }

    printf (" sep %s frac %s\n"
	    " >0 posn %hhx before %hhx sp %hhx "
	    " <0 posn %hhx before %hhx sp %hhx\n"
	    " money '%s'"
	    " group ",
	    lconv->thousands_sep,	lconv->decimal_point,
	    lconv->p_sign_posn,		lconv->p_cs_precedes,	    lconv->p_sep_by_space,
	    lconv->n_sign_posn,		lconv->n_cs_precedes,	    lconv->n_sep_by_space,
	    lconv->currency_symbol);

    for (char *	g = lconv->mon_grouping; /*BREAK*/; ++g)
    {
	printf ("%hhx ", *g);
	if (!*g || ~1 == *g)	break;
    }

    printf (" sep %s frac %s"
	    " digits %hhx\n"
	    " intl '%s'"
	    " >0 posn %hhx before %hhx sp %hhx "
	    " <0 posn %hhx before %hhx sp %hhx "
	    " digits %hhx\n",
	    lconv->mon_thousands_sep,	lconv->mon_decimal_point,   lconv->frac_digits,
	    lconv->int_curr_symbol,
	    lconv->int_p_sign_posn,	lconv->int_p_cs_precedes,   lconv->int_p_sep_by_space,
	    lconv->int_n_sign_posn,	lconv->int_n_cs_precedes,   lconv->int_n_sep_by_space,
	    lconv->int_frac_digits);

    for (int lc = LC_COLLATE; lc <= LC_MESSAGES; ++lc)
    {
	char	*item = nl_langinfo (NL_LOCALE_NAME(lc));
	printf ("%-12s '%s'\n", locale_categories[lc], item);
    }

    for (nl_item ni = CODESET; ni < _NL_LOCALE_EXTENDED_LAST_ENTRY; ++ni)
    {
	char	*item = nl_langinfo (ni);

	if (item && *item)
	{
	    if (*item >= ' ')
	    {
		if ((_NL_CTYPE_OUTDIGIT0_WC <= ni &&
		     ni <= _NL_CTYPE_OUTDIGIT9_WC)
		||  (_NL_TIME_WMON_1 <= ni &&
		     ni <= _NL_TIME_WALT_DIGITS)
		||  (_NL_MONETARY_WINT_CURR_SYMBOL <= ni &&
		     ni <= _NL_MONETARY_WNEGATIVE_SIGN)
		||  (_NL_MESSAGES_WYESEXPR <= ni &&
		     ni <= _NL_MESSAGES_WNOSTR))
		{
		    printf ("%-32s %S\n", nl_items[ni], item);

		    if (_NL_MESSAGES_WNOSTR == ni)
		    {
			for (wchar_t *	g = item; g < (wchar_t *)item + 8; ++g)
			{
			    printf ("%hx %C ", *g, *g);
			}

			printf ("\n");
		    }
		} else {
		    printf ("%-32s %s\n", nl_items[ni], item);
		}
	    } else {
		printf ("%-32s ", nl_items[ni]);

		for (char *	g = item; /*BREAK*/; ++g)
		{
		    printf ("%hhx ", *g);
		    /* single character for monetary flags */
		    if (_NL_MONETARY_INT_FRAC_DIGITS <= ni &&
			ni <= _NL_MONETARY_INT_N_SIGN_POSN)	break;
		    /* one or more characters for grouping etc */
		    if (!*g || ~1 == *g)	break;
		}

		printf ("\n");
	    }
	}
    }
}



#if 0
  _NL_CTYPE_CODESET_NAME = 0,	/* codeset name */
#define CODESET _NL_CTYPE_CODESET_NAME
...
  _NL_TIME_DATE_FMT = 84,	/* date fmt used by date(1) (GNU extension) */
#define _DATE_FMT _NL_TIME_DATE_FMT
  _NL_CTYPE_MB_CUR_MAX,
  _NL_MESSAGES_CODESET,
  _NL_LOCALE_EXTENDED_FIRST_ENTRY,
  _NL_CTYPE_OUTDIGIT0_MB,
  _NL_CTYPE_OUTDIGIT1_MB,
  _NL_CTYPE_OUTDIGIT2_MB,
  _NL_CTYPE_OUTDIGIT3_MB,
  _NL_CTYPE_OUTDIGIT4_MB,
  _NL_CTYPE_OUTDIGIT5_MB,
  _NL_CTYPE_OUTDIGIT6_MB,
  _NL_CTYPE_OUTDIGIT7_MB,
  _NL_CTYPE_OUTDIGIT8_MB,
  _NL_CTYPE_OUTDIGIT9_MB,
  _NL_CTYPE_OUTDIGIT0_WC,
  _NL_CTYPE_OUTDIGIT1_WC,
  _NL_CTYPE_OUTDIGIT2_WC,
  _NL_CTYPE_OUTDIGIT3_WC,
  _NL_CTYPE_OUTDIGIT4_WC,
  _NL_CTYPE_OUTDIGIT5_WC,
  _NL_CTYPE_OUTDIGIT6_WC,
  _NL_CTYPE_OUTDIGIT7_WC,
  _NL_CTYPE_OUTDIGIT8_WC,
  _NL_CTYPE_OUTDIGIT9_WC,
  _NL_TIME_CODESET,
  _NL_TIME_WMON_1,
  _NL_TIME_WMON_2,
  _NL_TIME_WMON_3,
  _NL_TIME_WMON_4,
  _NL_TIME_WMON_5,
  _NL_TIME_WMON_6,
  _NL_TIME_WMON_7,
  _NL_TIME_WMON_8,
  _NL_TIME_WMON_9,
  _NL_TIME_WMON_10,
  _NL_TIME_WMON_11,
  _NL_TIME_WMON_12,
  _NL_TIME_WMONTH_1,
  _NL_TIME_WMONTH_2,
  _NL_TIME_WMONTH_3,
  _NL_TIME_WMONTH_4,
  _NL_TIME_WMONTH_5,
  _NL_TIME_WMONTH_6,
  _NL_TIME_WMONTH_7,
  _NL_TIME_WMONTH_8,
  _NL_TIME_WMONTH_9,
  _NL_TIME_WMONTH_10,
  _NL_TIME_WMONTH_11,
  _NL_TIME_WMONTH_12,
  _NL_TIME_WWDAY_1,
  _NL_TIME_WWDAY_2,
  _NL_TIME_WWDAY_3,
  _NL_TIME_WWDAY_4,
  _NL_TIME_WWDAY_5,
  _NL_TIME_WWDAY_6,
  _NL_TIME_WWDAY_7,
  _NL_TIME_WWEEKDAY_1,
  _NL_TIME_WWEEKDAY_2,
  _NL_TIME_WWEEKDAY_3,
  _NL_TIME_WWEEKDAY_4,
  _NL_TIME_WWEEKDAY_5,
  _NL_TIME_WWEEKDAY_6,
  _NL_TIME_WWEEKDAY_7,
  _NL_TIME_WT_FMT,
  _NL_TIME_WD_FMT,
  _NL_TIME_WD_T_FMT,
  _NL_TIME_WAM_STR,
  _NL_TIME_WPM_STR,
  _NL_TIME_WDATE_FMT,
  _NL_TIME_WT_FMT_AMPM,
  _NL_TIME_WERA,
  _NL_TIME_WERA_D_FMT,
  _NL_TIME_WERA_D_T_FMT,
  _NL_TIME_WERA_T_FMT,
  _NL_TIME_WALT_DIGITS,
...
  _NL_MONETARY_INT_FRAC_DIGITS,
  _NL_MONETARY_FRAC_DIGITS,
  _NL_MONETARY_P_CS_PRECEDES,
  _NL_MONETARY_P_SEP_BY_SPACE,
  _NL_MONETARY_N_CS_PRECEDES,
  _NL_MONETARY_N_SEP_BY_SPACE,
  _NL_MONETARY_P_SIGN_POSN,
  _NL_MONETARY_N_SIGN_POSN,
  _NL_MONETARY_INT_P_CS_PRECEDES,
  _NL_MONETARY_INT_P_SEP_BY_SPACE,
  _NL_MONETARY_INT_N_CS_PRECEDES,
  _NL_MONETARY_INT_N_SEP_BY_SPACE,
  _NL_MONETARY_INT_P_SIGN_POSN,
  _NL_MONETARY_INT_N_SIGN_POSN,
...
  _NL_LOCALE_EXTENDED_LAST_ENTRY
  _NL_LOCALE_NAME_BASE 100000
#endif
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.