* m4/newlocale.m4 (gl_FUNC_NEWLOCALE): Test for the "null base" bug.
Set REPLACE_NEWLOCALE to 1 if it has the bug.
* lib/newlocale.c (newlocale): Add alternative implementation that uses
the system's newlocale().
* modules/newlocale (configure.ac): Consider REPLACE_NEWLOCALE.
* tests/test-newlocale.c: Include <langinfo.h>.
(main): Verify fix for the "null base" bug.
* modules/newlocale-tests (configure.ac): Test for nl_langinfo_l.
* doc/posix-functions/newlocale.texi: Mention the "null base" bug.
+2025-02-14 Bruno Haible <bruno@clisp.org>
+
+ newlocale: Work around macOS, NetBSD, Solaris 11 OpenIndiana bug.
+ * m4/newlocale.m4 (gl_FUNC_NEWLOCALE): Test for the "null base" bug.
+ Set REPLACE_NEWLOCALE to 1 if it has the bug.
+ * lib/newlocale.c (newlocale): Add alternative implementation that uses
+ the system's newlocale().
+ * modules/newlocale (configure.ac): Consider REPLACE_NEWLOCALE.
+ * tests/test-newlocale.c: Include <langinfo.h>.
+ (main): Verify fix for the "null base" bug.
+ * modules/newlocale-tests (configure.ac): Test for nl_langinfo_l.
+ * doc/posix-functions/newlocale.texi: Mention the "null base" bug.
+
2025-02-14 Bruno Haible <bruno@clisp.org>
newlocale, freelocale: Tweak configuration.
This function is useless because the @code{locale_t} type is not defined
on some platforms:
z/OS.
+@item
+When the third argument is NULL, this function uses locale category data
+from the current locale instead of from the "C" locale on some platforms:
+macOS, NetBSD 10.0, Solaris 11 OpenIndiana.
@end itemize
Portability problems not fixed by Gnulib:
#include <locale.h>
#include <errno.h>
-#include <stdlib.h>
-#include <string.h>
-#include "localename.h"
+#if HAVE_NEWLOCALE
+/* Only provide workarounds. */
+
+locale_t
+newlocale (int category_mask, const char *name, locale_t base)
+# undef newlocale
+{
+ if ((category_mask & ~LC_ALL_MASK) != 0)
+ {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (category_mask != LC_ALL_MASK && base == NULL)
+ base = newlocale (LC_ALL_MASK, "C", NULL);
+
+ return newlocale (category_mask, name, base);
+}
+
+#else
+/* Implement from scratch. */
+
+# include <stdlib.h>
+# include <string.h>
+
+# include "localename.h"
locale_t
newlocale (int category_mask, const char *name, locale_t base)
if (strcmp (name, "POSIX") == 0)
name = "C";
-#if !HAVE_WINDOWS_LOCALE_T
+# if !HAVE_WINDOWS_LOCALE_T
/* In this case, the only NAMEs that we support are "C" and (equivalently)
"POSIX". */
if (category_mask != 0 && strcmp (name, "C") != 0)
errno = ENOENT;
return NULL;
}
-#endif
+# endif
int i;
int err;
if (strcmp (lcname, "C") == 0)
{
result->category[i].is_c_locale = true;
-#if HAVE_WINDOWS_LOCALE_T
+# if HAVE_WINDOWS_LOCALE_T
/* Just to initialize it. */
result->category[i].system_locale = NULL;
-#endif
+# endif
}
else
{
result->category[i].is_c_locale = false;
-#if HAVE_WINDOWS_LOCALE_T
+# if HAVE_WINDOWS_LOCALE_T
if (log2_lcmask == gl_log2_lc_mask (LC_MESSAGES))
result->category[i].system_locale = NULL;
else
goto fail_with_err;
}
}
-#endif
+# endif
}
}
else
goto fail_with_err;
}
result->category[i].is_c_locale = true;
-#if HAVE_WINDOWS_LOCALE_T
+# if HAVE_WINDOWS_LOCALE_T
/* Just to initialize it. */
result->category[i].system_locale = NULL;
-#endif
+# endif
}
}
}
int log2_lcmask = gl_index_to_log2_lcmask (i);
if ((category_mask & (1 << log2_lcmask)) != 0)
{
-#if HAVE_WINDOWS_LOCALE_T
+# if HAVE_WINDOWS_LOCALE_T
if (!(i == gl_log2_lcmask_to_index (gl_log2_lc_mask (LC_MESSAGES))
|| base->category[i].is_c_locale))
/* Documentation:
<https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/free-locale> */
_free_locale (base->category[i].system_locale);
-#endif
+# endif
free (base->category[i].name);
base->category[i] = result->category[i];
fail_with_err:
while (--i >= 0)
{
-#if HAVE_WINDOWS_LOCALE_T
+# if HAVE_WINDOWS_LOCALE_T
if (!(i == gl_log2_lcmask_to_index (gl_log2_lc_mask (LC_MESSAGES))
|| result->category[i].is_c_locale))
/* Documentation:
<https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/free-locale> */
_free_locale (result->category[i].system_locale);
-#endif
+# endif
free (result->category[i].name);
}
if (base == NULL)
errno = err;
return NULL;
}
+
+#endif
# newlocale.m4
-# serial 2
+# serial 3
dnl Copyright (C) 2025 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
gl_cv_onwards_func_newlocale='future OS version'
gl_func_newlocale=no
fi
- if test $gl_func_newlocale != yes; then
+ if test $gl_func_newlocale = yes; then
+ dnl Check against the macOS, NetBSD, Solaris 11 OpenIndiana bug:
+ dnl When the third argument is NULL, newlocale() uses locale category data
+ dnl from the current locale instead of from the "C" locale.
+ gl_CHECK_FUNCS_ANDROID([nl_langinfo_l], [[#include <langinfo.h>]])
+ if test $ac_cv_func_nl_langinfo_l = yes; then
+ AC_REQUIRE([AC_CANONICAL_HOST])
+ AC_CACHE_CHECK([whether newlocale with a null base works],
+ [gl_cv_func_newlocale_works],
+ [dnl Prepare a guess, used when cross-compiling or when specific locales
+ dnl are not available.
+ case "$host_os" in
+ darwin* | netbsd* | solaris*)
+ gl_cv_func_newlocale_works="guessing no" ;;
+ *)
+ gl_cv_func_newlocale_works="guessing yes" ;;
+ esac
+ AC_RUN_IFELSE(
+ [AC_LANG_SOURCE([[
+#include <locale.h>
+#if HAVE_XLOCALE_H
+# include <xlocale.h>
+#endif
+#include <langinfo.h>
+int main ()
+{
+ locale_t l1 = newlocale (LC_TIME_MASK, "en_US.UTF-8", NULL);
+ if (l1 != NULL)
+ {
+ if (setlocale (LC_ALL, "fr_FR.UTF-8") != NULL)
+ {
+ locale_t l1a = newlocale (LC_TIME_MASK, "en_US.UTF-8", NULL);
+ const char *radixchar1a = nl_langinfo_l (RADIXCHAR, l1a);
+ return (*radixchar1a != '.');
+ }
+ }
+ return 2;
+}]])],
+ [gl_cv_func_newlocale_works=yes],
+ [if test $? = 1; then
+ gl_cv_func_newlocale_works=no
+ fi
+ ],
+ [])
+ ])
+ case "$gl_cv_func_newlocale_works" in
+ *yes) ;;
+ *) REPLACE_NEWLOCALE=1 ;;
+ esac
+ fi
+ else
HAVE_NEWLOCALE=0
case "$gl_cv_onwards_func_newlocale" in
future*) REPLACE_NEWLOCALE=1 ;;
configure.ac:
gl_FUNC_NEWLOCALE
-gl_CONDITIONAL([GL_COND_OBJ_NEWLOCALE], [test $HAVE_LOCALE_T = 0])
+gl_CONDITIONAL([GL_COND_OBJ_NEWLOCALE],
+ [test $HAVE_LOCALE_T = 0 || { test $REPLACE_NEWLOCALE = 1 && test "$gt_localename_enhances_locale_funcs" != yes; }])
AM_COND_IF([GL_COND_OBJ_NEWLOCALE], [
gl_PREREQ_NEWLOCALE
])
Depends-on:
configure.ac:
+gl_CHECK_FUNCS_ANDROID([nl_langinfo_l], [[#include <langinfo.h>]])
Makefile.am:
TESTS += test-newlocale
#include "signature.h"
SIGNATURE_CHECK (newlocale, locale_t, (int, const char *, locale_t));
+#if HAVE_NL_LANGINFO_L
+# include <langinfo.h>
+#endif
+
#include "macros.h"
#if defined _WIN32 && !defined __CYGWIN__
int
main ()
{
- locale_t l1 = newlocale (LC_TIME_MASK, LOCALE1, NULL);
- locale_t l2 = newlocale (LC_MESSAGES_MASK, LOCALE2, l1);
- locale_t l3 = newlocale (LC_TIME_MASK, LOCALE3, l2);
- (void) l3;
+ {
+ locale_t l1 = newlocale (LC_TIME_MASK, LOCALE1, NULL);
+ locale_t l2 = newlocale (LC_MESSAGES_MASK, LOCALE2, l1);
+ locale_t l3 = newlocale (LC_TIME_MASK, LOCALE3, l2);
+ (void) l3;
+ }
+
+#if HAVE_NL_LANGINFO_L
+ /* Verify that when the base argument is NULL, "the data for all sections
+ not requested by category_mask shall be taken from the POSIX locale". */
+ {
+ locale_t l1 = newlocale (LC_TIME_MASK, LOCALE1, NULL);
+ if (l1 != NULL)
+ {
+ const char *radixchar1 = nl_langinfo_l (RADIXCHAR, l1);
+ ASSERT (*radixchar1 == '.');
+ if (setlocale (LC_ALL, LOCALE2) != NULL)
+ {
+ radixchar1 = nl_langinfo_l (RADIXCHAR, l1);
+ ASSERT (*radixchar1 == '.');
+ locale_t l1a = newlocale (LC_TIME_MASK, LOCALE1, NULL);
+ const char *radixchar1a = nl_langinfo_l (RADIXCHAR, l1a);
+ ASSERT (*radixchar1a == '.');
+ }
+ }
+ }
+#endif
return test_exit_status;
}